Skip to content

fix: raise ValidationError for out-of-range int→float in convert - #1128

Closed
takerukondo wants to merge 2 commits into
msgspec:mainfrom
takerukondo:fix-convert-int-float-overflow
Closed

takerukondo wants to merge 2 commits into
msgspec:mainfrom
takerukondo:fix-convert-int-float-overflow

Conversation

@takerukondo

Copy link
Copy Markdown

Summary

Fixes #1122.

msgspec.convert(10**400, float) was leaking SystemError because PyLong_AsDouble sets OverflowError while convert still returned a float object. json.decode already reports this as ValidationError: Number out of range.

Clear the overflow error and raise ValidationError (same message as the JSON path). Also reject non-finite results as a belt-and-suspenders check.

Test plan

  • pytest tests/unit/test_convert.py::TestFloat::test_float_from_out_of_range_int tests/unit/test_convert.py::TestFloat::test_float
  • CI

Made with Cursor

takerukondo and others added 2 commits July 20, 2026 13:33
PyLong_AsDouble sets OverflowError for ints that cannot fit in a finite
C double. convert was returning a float while that exception stayed set,
which leaked SystemError. Match json.decode and report ValidationError.

Fixes msgspec#1122

Co-authored-by: Cursor <[email protected]>
Comment thread src/msgspec/_core.c
Comment on lines +20941 to +20942
/* PyLong_AsDouble sets OverflowError and may return ±inf for ints that
* cannot be represented as a finite C double. Surface that as a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you getting the

return ±inf for ints that cannot be represented as a finite C double

part from? It's at least not stated in the documentation: https://docs.python.org/3/c-api/long.html#c.PyLong_AsDouble

@Siyet

Siyet commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for this, and sorry for the long silence. We went with #1162, so I am closing this one. You were here first, so you are owed the actual reasons rather than a bare "duplicate".

Two of them are on us, not on you:

  • Nobody approved the workflow runs on this PR, so in 49 days it never got a real CI run beyond the capability policy check. Your tests have never actually been executed by our CI. That is a maintainer miss, and it is why the other PR looks green and this one does not.
  • @provinzkraut's question from 2026-07-20 was left hanging and you were never pinged again.

Answering that question now, since it is the substantive one: he was right, and the code comment was wrong. PyLong_AsDouble cannot return +/-inf. In Objects/longobject.c it either takes the compact single digit fast path, which is always finite, or calls _PyLong_Frexp, which normalises the significand into [0.5, 1.0) and increments the exponent when rounding would push it to 1.0. PyLong_AsDouble then rejects exponent > DBL_MAX_EXP with OverflowError and returns exactly -1.0; otherwise it returns ldexp(x, exponent), at most (1 - 2**-53) * 2**1024, which is DBL_MAX. So the isfinite guard is unreachable and the -1.0 plus PyErr_Occurred() test is sufficient on its own. I checked this rather than taking it on faith: instrumenting that branch on your patch and running 22100 values around the DBL_MAX boundary, it never fired.

The PyErr_Clear() is a no-op too, though a harmless one. ms_error_with_path reports through PyErr_Format, and _PyErr_SetObject clears the pending exception itself before building the new one, while implicit chaining reads the handled exception stack rather than a merely pending error. The three existing sites in _core.c that do this same thing (the integer struct tag check and the Ext.code check among them) also set the new error without clearing first.

On the changelog entry, to be clear: writing one was not wrong, several merged PRs here carry their own. Only the placement broke, and recently. Since you opened this, ## Unreleased became ## Version 0.22.0 (2026-08-11), so your hunk still merges cleanly but drops the entry inside an already released section.

Your diagnosis was right and your test case was a good one. Thank you for the time, and please do send more.

@Siyet Siyet closed this Sep 7, 2026
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.

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

3 participants