Skip to content

convert leaks SystemError on an out-of-range int → float, where json.decode cleanly raises ValidationError #1122

Closed
chuckwondo/covjson-msgspec
#276

Description

@chuckwondo

Description

Summary

Converting a Python int that is too large to represent as a float via msgspec.convert leaks a SystemError ("<built-in function convert> returned a result with an exception set") instead of raising a msgspec.ValidationError. The equivalent json.decode handles the same out-of-range value correctly, raising ValidationError: Number out of range, so the two paths disagree on the same logical conversion.

Reproduction

import msgspec

big = 10**400  # far beyond the largest finite float (~1.8e308)

# convert: leaks SystemError (both strict=True and strict=False)
msgspec.convert(big, float)
# SystemError: <built-in function convert> returned a result with an exception set

# nested, same result:
msgspec.convert({"v": big}, dict[str, float])
# SystemError: ...

# json.decode: handled correctly
msgspec.json.decode(str(big).encode(), type=float)
# msgspec.ValidationError: Number out of range

Expected behavior

convert should raise msgspec.ValidationError (e.g. "Number out of range"), matching json.decode. An out-of-range integer is a value error the caller can catch, not an internal interpreter fault.

Actual behavior

convert raises SystemError: <built-in function convert> returned a result with an exception set. This looks like a C-level conversion (int → C double) that overflows and sets a Python exception, but whose result is still returned to the interpreter, which then raises SystemError. A caller who wraps the call in except msgspec.ValidationError (the documented contract for a bad value) does not catch it.

  • Affects both strict=True and strict=False.
  • Affects nested targets (dict[str, float], tuple[float, ...], etc.), not just a bare float.
  • A normal in-range int converts fine (convert(5, float) -> 5.0), so only the overflow path is affected.

Environment

  • msgspec 0.21.1
  • Python 3.11.10
  • macOS 12.7.6 (x86_64)

Notes

The json.decode path already reports this as ValidationError: Number out of range, so the fix is presumably to make convert's int → float path apply the same range check / error rather than leaking the C exception as a SystemError.

Activity

  1. Brohammad commented on Aug 31, 2026

    @Brohammad

    Hi has this issue been resolved? If not I would like to take it up.

  2. provinzkraut commented on Aug 31, 2026

    @provinzkraut
    Member

    @Brohammad we do not need any more PRs for this. There's already 2 closed and 2 open ones.

  3. Brohammad commented on Aug 31, 2026

    @Brohammad

    oh ok closing mine

  4. Siyet commented on Sep 7, 2026

    @Siyet
    Contributor

    @provinzkraut two things, since you have been the one steering this issue.

    On the choice between the two open PRs. You pointed contributors at #1128 back in July when you closed #1129, and I have gone the other way and merged #1162. The reasons, briefly:

    If you would rather I had fixed #1128 up in place and kept the queue order, say so and I will take that as the rule next time.

    The same root cause is still live in the public API, and neither PR touches it. PyLong_AsDouble appears twice in _core.c. The convert_int site is what got fixed. The other is ensure_is_finite_numeric, reached straight from Meta:

    >>> msgspec.Meta(gt=10**400)
    SystemError: <class 'msgspec.Meta'> returned a result with an exception set

    Same for ge, lt and le, positive or negative. multiple_of is arguably worse, because it does not crash, it answers wrongly:

    >>> msgspec.Meta(multiple_of=10**400)
    ValueError: `multiple_of` must be > 0

    The failed conversion leaves the -1.0 sentinel, which trips the positive && x <= 0 check, so a plainly positive value is reported as not positive. ensure_is_nonnegative_integer has the same shape with PyLong_AsSsize_t, so Meta(min_length=10**400) says must be >= 0 about a positive number.

    None of this is a regression. It reproduces on 0.21.1 and on 3.11, and git log -S puts it in the commit that first added Meta in 2022. It is a small fix in two functions and it needs its own change rather than being folded into the convert fix.

    Worth knowing for #1006, which rewrites ensure_is_finite_numeric for Decimal bounds but leaves the int branch unguarded: after it, Meta(ge=Decimal("1E+400")) is accepted while the numerically equal Meta(ge=10**400) still raises SystemError.

  5. added a commit that references this issue on Sep 7, 2026
    3f67b34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions