Skip to content

fix(codegen): String.match no-match returns null, not a boxed null pointer#4860

Merged
proggeramlug merged 1 commit into
mainfrom
fix/4858-string-match-global-no-match-null
Jun 9, 2026
Merged

fix(codegen): String.match no-match returns null, not a boxed null pointer#4860
proggeramlug merged 1 commit into
mainfrom
fix/4858-string-match-global-no-match-null

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #4858.

Root cause

Expr::StringMatch codegen (crates/perry-codegen/src/expr/instance_misc1.rs) NaN-boxed the js_string_match result unconditionally. On no-match the runtime returns null (0), and POINTER_TAG|0 is neither null nor a valid heap pointer:

The sibling Expr::RegExpExec arm already documents and guards this exact bug class; the js_string_match_value path (lower_string_method.rs) also had the guard. The static Expr::StringMatch fast path was the only one missing it.

Note: the non-global no-match case hit the identical codegen path and also segfaulted under JSON.stringify — the issue's "non-global works" observation only held for console.log, which guards small pointers.

Fix

Branchless result == 0 → TAG_NULL select on the call result, mirroring RegExpExec.

Validation

  • Issue repro now byte-matches Node: before / after: null
  • Broad match-semantics sweep (global/non-global × hit/no-match, capture groups, named groups, .index, regex held in a local, the Stripe extractUrlParams pattern) byte-matches node --experimental-strip-types
  • New end-to-end regression test (crates/perry/tests/issue_4858_global_match_no_match.rs) compiles + runs the repro via CARGO_BIN_EXE_perry and asserts exact output
  • cargo test --release -p perry -p perry-codegen green locally

No version bump / changelog per maintainer-at-merge convention.

…inter (#4858)

Expr::StringMatch codegen NaN-boxed js_string_match's result
unconditionally. On no-match the runtime returns null (0), and
POINTER_TAG|0 is neither `null` nor a valid heap pointer:
`"abc".match(/x/g) === null` evaluated to false and consumers that
dereference the result (JSON.stringify's tree walk, .map) segfaulted.
This was the root cause of #4841's Stripe failure — extractUrlParams
runs path.match(/\{\w+\}/g) on every request path, and any path
without {params} crashed the request.

Fix: branchless null -> TAG_NULL select on the call result, exactly
mirroring the Expr::RegExpExec arm directly above (which documents
this same bug class). The js_string_match_value path
(lower_string_method.rs) already had the select; the static
Expr::StringMatch fast path was the only one missing it.

The non-global no-match case hit the identical codegen path and also
crashed under JSON.stringify (the issue's "non-global works" note
only held for console.log, which guards small pointers).

Verified: issue repro + a broad match-semantics sweep (global/
non-global, hit/no-match, capture groups, named groups, .index,
regex-in-local) byte-match node --experimental-strip-types. New
end-to-end regression test compiles and runs the repro via
CARGO_BIN_EXE_perry.
@proggeramlug proggeramlug merged commit ca5fe11 into main Jun 9, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix/4858-string-match-global-no-match-null branch June 9, 2026 21:49
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.

String.prototype.match(/x/g) segfaults (SIGSEGV) on no-match (global flag)

1 participant