From ad7cd618eb4bd7c55525e974dfb091ef1e0b236b Mon Sep 17 00:00:00 2001 From: Vineeth Sai Date: Sun, 12 Jul 2026 14:54:44 -0700 Subject: [PATCH 1/2] Fix multipleOf crashing on Decimal instances and huge integers 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. --- jsonschema/_keywords.py | 14 ++++++++------ jsonschema/tests/test_validators.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/jsonschema/_keywords.py b/jsonschema/_keywords.py index f30f95419..78339ff03 100644 --- a/jsonschema/_keywords.py +++ b/jsonschema/_keywords.py @@ -169,15 +169,17 @@ def multipleOf(validator, dB, instance, schema): return if isinstance(dB, float): - quotient = instance / dB try: + quotient = instance / dB failed = int(quotient) != quotient - except OverflowError: - # When `instance` is large and `dB` is less than one, - # quotient can overflow to infinity; and then casting to int - # raises an error. + except (OverflowError, TypeError): + # When `instance` is large and `dB` is less than one, the + # quotient can overflow to infinity (or dividing a huge integer + # can overflow outright), and then casting to int raises an + # OverflowError; and a `Decimal` instance cannot be divided by a + # float `dB` at all, which raises a TypeError. # - # In this case we fall back to Fraction logic, which is + # In either case we fall back to Fraction logic, which is # exact and cannot overflow. The performance is also # acceptable: we try the fast all-float option first, and # we know that fraction(dB) can have at most a few hundred diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index 7d8a4c5cd..bb408618f 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -816,6 +816,29 @@ def test_heterogeneous_properties_unevaluatedProperties(self): ) +class TestMultipleOf(TestCase): + def test_it_does_not_crash_on_decimals_or_large_integers(self): + # ``multipleOf`` with a float divisor divides the instance by the + # divisor. The division used to sit outside the guard that only + # caught OverflowError, so a Decimal instance raised TypeError + # (Decimal / float) and a very large integer raised OverflowError at + # the division itself, instead of validating. + Validator = validators.Draft202012Validator + + # Both of these are valid (10.0 is a multiple of 2.5, and every + # integer is a multiple of 0.5) and must not raise. + Validator({"multipleOf": 2.5}).validate(Decimal("10.0")) + Validator({"multipleOf": 0.5}).validate(10**400) + + # The Fraction fallback still rejects genuine non-multiples. + self.assertFalse( + Validator({"multipleOf": 3.3}).is_valid(Decimal("10")), + ) + self.assertFalse( + Validator({"multipleOf": 0.3}).is_valid(10**400), + ) + + class TestValidationErrorDetails(TestCase): # TODO: These really need unit tests for each individual keyword, rather # than just these higher level tests. From f5d429379c0fe08911d879978473c0d24c2a2860 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:55:09 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jsonschema/tests/test_validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py index bb408618f..bc291b085 100644 --- a/jsonschema/tests/test_validators.py +++ b/jsonschema/tests/test_validators.py @@ -832,7 +832,7 @@ def test_it_does_not_crash_on_decimals_or_large_integers(self): # The Fraction fallback still rejects genuine non-multiples. self.assertFalse( - Validator({"multipleOf": 3.3}).is_valid(Decimal("10")), + Validator({"multipleOf": 3.3}).is_valid(Decimal(10)), ) self.assertFalse( Validator({"multipleOf": 0.3}).is_valid(10**400),