Skip to content

avm2: Keep large whole JSON numbers as Number, not wrapped i32#24060

Open
ihorprokopiev wants to merge 1 commit into
ruffle-rs:masterfrom
ihorprokopiev:avm2-json-large-int
Open

avm2: Keep large whole JSON numbers as Number, not wrapped i32#24060
ihorprokopiev wants to merge 1 commit into
ruffle-rs:masterfrom
ihorprokopiev:avm2-json-large-int

Conversation

@ihorprokopiev

@ihorprokopiev ihorprokopiev commented Jun 24, 2026

Copy link
Copy Markdown

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.parse previously converted every whole JSON number with
f64_to_wrapping_i32, reducing it mod 2^32. Whole numbers outside i32 range were
silently corrupted — e.g. a 13-digit millisecond timestamp 1782219299000
wrapped 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 i32 only when number.fract() == 0.0 && (i32::MIN..=i32::MAX).contains(&number), and otherwise keeps the f64. This
matches Flash Player, where JSON numbers are Number and only collapse to int
when representable. The now-unused f64_to_wrapping_i32 import is removed.

Testing

Added tests/tests/swfs/avm2/json_parse_large_number/, which parses a 13-digit
timestamp and asserts it round-trips, plus i32 boundary cases (2147483647
narrows to int; 2147483648 and a value below i32::MIN stay Number) and a
fractional control. The existing avm2/json_parse test still passes.

Checklist

  • I, a human, have self-reviewed this PR and fully understand the changes within.
  • I have made or updated tests where possible.
  • All of my commits are properly scoped, compile successfully, and pass all tests.
  • This PR does not make sense to split up into smaller PRs.
  • An LLM was involved in the authoring of this code.

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.
@kjarosh

kjarosh commented Jun 24, 2026

Copy link
Copy Markdown
Member

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

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.

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

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.

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.

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.

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}');

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.

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.

@danielhjacobs

danielhjacobs commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

It looks like your change fixed some existing tests as well. You can update the expected output if it's better

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 cargo test from_avmplus/ecma3/JSON/adhoc. If the test is still failing, but differently, this creates a new output.ruffle.txt with the current Ruffle output. If it's no longer failing it tells you to remove known_failure = true from the test.toml.

@kjarosh kjarosh added waiting-on-author Waiting on the PR author to make the requested changes A-avm2 Area: AVM2 (ActionScript 3) T-fix Type: Bug fix (in something that's supposed to work already) labels Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-avm2 Area: AVM2 (ActionScript 3) T-fix Type: Bug fix (in something that's supposed to work already) waiting-on-author Waiting on the PR author to make the requested changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants