Describe the bug
store_json_encode's number path in src/ext_store.c narrows a double to int before checking its magnitude:
if (n == (int)n && fabs(n) < 1e15)
&& evaluates left to right, so (int)n runs first. Converting a double whose truncated value is outside int's range is undefined behaviour (C11 6.3.1.4p1), and the magnitude guard that would have prevented it sits on the right-hand side where it cannot help.
This is the same pattern as #695 (value_to_string: double→long long cast evaluated before the range check), which was fixed — so this looks like a second instance of it rather than a new class.
Any script value large enough reaches it: store_put encodes whatever number it is given, and 1e300 is an ordinary EigenScript number.
To reproduce
db is store_open of "/tmp/ub.eigstore"
ignored is store_put of [db, {"n": 1e300}]
Built with -fsanitize=float-cast-overflow, that reports the cast. Note this does not show up under the repo's existing sanitizer job: GCC's -fsanitize=undefined does not include float-cast-overflow, so make asan is silent on it. I verified that separately rather than assuming it:
$ gcc -fsanitize=address,undefined -g -O1 -o probe probe.c && ./probe
(int)1e300 = -2147483648 <- silent
$ gcc -fsanitize=float-cast-overflow -g -O1 -o probe probe.c && ./probe
probe.c:3:1: runtime error: 1e+300 is outside the range of representable values of type 'int'
Expected behavior
The magnitude is checked before the narrowing cast, so no out-of-range double is ever converted.
Actual behavior
The cast happens first and is undefined. In practice on x86-64 it is harmless — cvttsd2si returns INT_MIN, which then fails the n == (int)n comparison and takes the intended %.15g branch — so the observable output is currently correct. It is correct by hardware accident rather than by the language, and nothing obliges a different target or a more aggressive optimisation level to preserve it.
Environment
- OS: Debian 13
- GCC version: 14.2.0
- EigenScript version: 0.34.0 (
main at 14c3a07)
Found while fixing #805; the decoder had the same defect one line from the CodeQL alert that led me there, and that half is fixed in #814. This encoder half is outside that PR's scope, so filing rather than folding it in.
Describe the bug
store_json_encode's number path insrc/ext_store.cnarrows a double tointbefore checking its magnitude:&&evaluates left to right, so(int)nruns first. Converting a double whose truncated value is outsideint's range is undefined behaviour (C11 6.3.1.4p1), and the magnitude guard that would have prevented it sits on the right-hand side where it cannot help.This is the same pattern as #695 (
value_to_string: double→long long cast evaluated before the range check), which was fixed — so this looks like a second instance of it rather than a new class.Any script value large enough reaches it:
store_putencodes whatever number it is given, and1e300is an ordinary EigenScript number.To reproduce
Built with
-fsanitize=float-cast-overflow, that reports the cast. Note this does not show up under the repo's existing sanitizer job: GCC's-fsanitize=undefineddoes not includefloat-cast-overflow, somake asanis silent on it. I verified that separately rather than assuming it:Expected behavior
The magnitude is checked before the narrowing cast, so no out-of-range double is ever converted.
Actual behavior
The cast happens first and is undefined. In practice on x86-64 it is harmless —
cvttsd2sireturnsINT_MIN, which then fails then == (int)ncomparison and takes the intended%.15gbranch — so the observable output is currently correct. It is correct by hardware accident rather than by the language, and nothing obliges a different target or a more aggressive optimisation level to preserve it.Environment
mainat14c3a07)Found while fixing #805; the decoder had the same defect one line from the CodeQL alert that led me there, and that half is fixed in #814. This encoder half is outside that PR's scope, so filing rather than folding it in.