diff --git a/jsonschema/_keywords.py b/jsonschema/_keywords.py index f30f9541..78339ff0 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 7d8a4c5c..bc291b08 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.