Skip to content

Fix multipleOf crashing on Decimal instances and large integers#1515

Open
vineethsaivs wants to merge 2 commits into
python-jsonschema:mainfrom
vineethsaivs:fix/multipleof-decimal-crash
Open

Fix multipleOf crashing on Decimal instances and large integers#1515
vineethsaivs wants to merge 2 commits into
python-jsonschema:mainfrom
vineethsaivs:fix/multipleof-decimal-crash

Conversation

@vineethsaivs

Copy link
Copy Markdown

The bug

multipleOf with a float divisor raises an uncaught exception (instead of validating) for two reasonable inputs:

import json
from decimal import Decimal
from jsonschema import Draft202012Validator as V

# (a) a Decimal instance, e.g. from json.loads(text, parse_float=Decimal)
list(V({"multipleOf": 2.5}).iter_errors(Decimal("10.0")))
# TypeError: unsupported operand type(s) for /: 'decimal.Decimal' and 'float'

# (b) a very large integer instance
list(V({"multipleOf": 0.5}).iter_errors(10**400))
# OverflowError: int too large to convert to float

Both should validate successfully: 10.0 is a multiple of 2.5, and every integer is a multiple of 0.5. Decimal is a supported instance type (it is a numbers.Number, and there is an existing test_it_can_validate_with_decimals), and Decimal instances arise naturally from json.loads(text, parse_float=Decimal).

Root cause

In the float branch of multipleOf (jsonschema/_keywords.py), the division was placed outside the try, and the except only caught OverflowError:

if isinstance(dB, float):
    quotient = instance / dB          # <- outside the guard
    try:
        failed = int(quotient) != quotient
    except OverflowError:
        ...
        failed = (Fraction(instance) / Fraction(dB)).denominator != 1

So the division could raise uncaught: TypeError for Decimal / float, and OverflowError when a huge integer is divided by a float (the division itself overflows, before the int() cast the guard was written for).

Fix

Move the division inside the try and also catch TypeError, so both cases fall back to the exact, overflow-proof Fraction computation that was already there:

if isinstance(dB, float):
    try:
        quotient = instance / dB
        failed = int(quotient) != quotient
    except (OverflowError, TypeError):
        ...
        failed = (Fraction(instance) / Fraction(dB)).denominator != 1

The else (instance % dB) path and the fast float path are unchanged.

Tests

Added TestMultipleOf.test_it_does_not_crash_on_decimals_or_large_integers to jsonschema/tests/test_validators.py. It fails before the change (TypeError at the division) and passes after; it also checks the Fraction fallback still rejects genuine non-multiples (Decimal("10") vs 3.3, 10**400 vs 0.3). The full test_validators.py (304) and the official Test Suite multipleOf cases (55) pass. Happy to add a CHANGELOG entry if you'd like.

vineethsaivs and others added 2 commits July 12, 2026 14:54
When multipleOf has a float divisor, the validator divides the instance
by it. The division sat outside the try that guarded the subsequent
int() cast, and the except only caught OverflowError. As a result:

- a Decimal instance (which jsonschema supports, e.g. from
  json.loads(text, parse_float=Decimal)) raised TypeError, because
  Decimal / float is unsupported; and
- a very large integer instance raised OverflowError at the division
  itself (int too large to convert to float), which the Fraction
  fallback was meant to cover but never reached.

Move the division inside the try and also catch TypeError, so both fall
back to the exact Fraction computation. 10.0 (as Decimal) is correctly
reported a multiple of 2.5, and every integer a multiple of 0.5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant