avm2: Keep large whole JSON numbers as Number, not wrapped i32#24060
avm2: Keep large whole JSON numbers as Number, not wrapped i32#24060ihorprokopiev wants to merge 1 commit into
Conversation
JSON.parse coerced every whole number through f64_to_wrapping_i32, which reduces the value mod 2^32. Integers outside i32 range were silently corrupted; a 13-digit millisecond timestamp such as 1782219299000 wrapped to a near-zero value, leaving anything that parsed a server clock from JSON stuck around 1970. Only narrow to i32 when the value has no fractional part AND actually fits in i32; otherwise keep the f64. This matches Flash Player, where JSON numbers are Numbers and only collapse to int when representable.
|
Welcome! It looks like your change fixed some existing tests as well. You can update the expected output if it's better, and maybe you can look into some cases that still fail and see if that can be trivially fixed as well (if not that's fine too). |
| if number.fract() == 0.0 { | ||
| f64_to_wrapping_i32(number).into() | ||
| // Only use the i32 representation when the value actually fits in i32. | ||
| // Previously this used f64_to_wrapping_i32, which reduced any whole |
There was a problem hiding this comment.
You don't need to mention the history, we're using git for tracking changes.
|
|
||
| public class Test extends Sprite { | ||
| public function Test() { | ||
| // A 13-digit millisecond timestamp. Before the fix, JSON.parse ran every |
There was a problem hiding this comment.
Before the fix [...]
The comment looks like a good commit message, bad comment inside the test. It doesn't really add anything to the test.
| var neg = JSON.parse('{"n": -5000000000}'); | ||
| trace(neg.n, neg.n is int); | ||
|
|
||
| // Fractional and small whole numbers are unaffected. |
There was a problem hiding this comment.
Similarly here, it's not clear what those numbers should be unaffected from.
| // A 13-digit millisecond timestamp. Before the fix, JSON.parse ran every | ||
| // whole number through f64_to_wrapping_i32, reducing it mod 2^32 and | ||
| // corrupting this to a near-zero value. | ||
| var big = JSON.parse('{"t": 1782219299000}'); |
There was a problem hiding this comment.
It looks like you can prepare a list of cases, iterate over it and check for each one, e.g.
cases = [
"{"t": 1782219299000}",
"{"t": 2147483647}",
"{"t": 2147483648}",
"{"t": -5000000000}",
...
]
for case in cases {
var json = JSON.parse(case);
trace("Case: " + case);
trace(json.t);
trace(json.t is int);
trace(json.t is uint);
trace(json.t is Number);
}
This way you can easily add more edge cases.
This can be fairly easily done (this is how I do it) by deleting tests/tests/swfs/from_avmplus/ecma3/JSON/adhoc/output.ruffle.txt, cd-ing to inside Ruffle's top-level tests directory via the Command Prompt or Terminal, and then running |
JSON.parse coerced every whole number through f64_to_wrapping_i32, which reduces the value mod 2^32. Integers outside i32 range were silently corrupted; a 13-digit millisecond timestamp such as 1782219299000 wrapped to a near-zero value, leaving anything that parsed a server clock from JSON stuck around 1970.
Only narrow to i32 when the value has no fractional part AND actually fits in i32; otherwise keep the f64. This matches Flash Player, where JSON numbers are Numbers and only collapse to int when representable.
Description
JSON.parsepreviously converted every whole JSON number withf64_to_wrapping_i32, reducing it mod 2^32. Whole numbers outside i32 range weresilently corrupted — e.g. a 13-digit millisecond timestamp
1782219299000wrapped to a near-zero value, so any content that reads a server clock out of
JSON behaved as if it were ~1970.
The fix narrows to
i32only whennumber.fract() == 0.0 && (i32::MIN..=i32::MAX).contains(&number), and otherwise keeps thef64. Thismatches Flash Player, where JSON numbers are
Numberand only collapse tointwhen representable. The now-unused
f64_to_wrapping_i32import is removed.Testing
Added
tests/tests/swfs/avm2/json_parse_large_number/, which parses a 13-digittimestamp and asserts it round-trips, plus i32 boundary cases (
2147483647narrows to
int;2147483648and a value belowi32::MINstayNumber) and afractional control. The existing
avm2/json_parsetest still passes.Checklist