Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions jsonschema/_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading