-
-
Notifications
You must be signed in to change notification settings - Fork 159
fix(string): stop mask-casting SSO values to StringHeader* (#6887) #6888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| **Fix SIGSEGV indexing or iterating a short concatenated string (#6887):** `("ab" + "c")[0]`, `for (const ch of a + b)`, `Array.from(a + b)`, `[...s]` and `s.split("")` all segfaulted when the concatenation was short enough to be small-string-optimized. A short string is an inline `SHORT_STRING_TAG` JSValue whose payload *is* the characters, but codegen's `s[i]` fast path mask-unboxed the receiver to a `StringHeader*`, and `js_array_from_value` performed the same mask itself behind a `(bits >> 48) >= 0x7FF8` test that an SSO value passes — both then dereferenced the packed characters as an address. String literals, `join()` results and long (heap-backed) concatenations were unaffected, which is why this survived: a 3-char concat crashed while a 64-char one did not, and `typeof`, `.length` and printing the value all worked first. Codegen now passes the receiver still boxed to a new `js_string_index_get_boxed`, which decides by tag, and `js_array_from_value` materializes an SSO receiver before extracting pointers. This was the blocker behind #6872 and the last defect stopping the Milo compiler from building and running Milo programs under Perry. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // A short string built by concatenation is an inline SHORT_STRING_TAG (SSO) | ||
| // JSValue whose payload IS the characters, not a heap address. Codegen's `s[i]` | ||
| // fast path mask-unboxed the receiver to a `StringHeader*`, and | ||
| // `js_array_from_value` did the same mask itself, so both produced a bogus | ||
| // pointer and segfaulted. `"ab" + "c"` is the ordinary way to make one, which | ||
| // made `(a + b)[0]`, `for (const ch of a + b)` and `Array.from(a + b)` crash | ||
| // while the identical operations on a literal or a `join()` result were fine. | ||
| // | ||
| // Long concatenations exceed the SSO threshold and were always heap-backed — | ||
| // they are covered here so the fix cannot regress the heap path. | ||
|
|
||
| const a = "ab"; | ||
| const b = "c"; | ||
| const short = a + b; | ||
|
|
||
| console.log(typeof short, short.length, short); | ||
|
|
||
| // --- indexed reads on a short concatenation ------------------------------- | ||
| console.log(short[0], short[1], short[2]); | ||
| console.log(String(short[3])); // undefined, out of range | ||
| console.log(String(short[-1])); // undefined, negative | ||
| console.log(short.charAt(1), short.charCodeAt(1), short.codePointAt(1)); | ||
| console.log(short.at(0), short.at(-1)); | ||
|
|
||
| // index-loop accumulation (the shape milo's codegen uses) | ||
| let viaIndex = ""; | ||
| for (let i = 0; i < short.length; i++) viaIndex += short[i]; | ||
| console.log(viaIndex); | ||
|
|
||
| // --- iteration protocols -------------------------------------------------- | ||
| let viaForOf = 0; | ||
| for (const ch of short) viaForOf++; | ||
| console.log(viaForOf); | ||
| console.log([...short].join("-")); | ||
| console.log(Array.from(short).length, Array.from(short).join("|")); | ||
| console.log(short.split("").join("+")); | ||
|
|
||
| // --- concatenation of a join result, exactly milo's shape ----------------- | ||
| const parts: string[] = []; | ||
| parts.push("%.*s"); | ||
| const fmt = parts.join("") + "\n"; | ||
| console.log(fmt.length); | ||
| let fmtChars = 0; | ||
| for (const ch of fmt) fmtChars++; | ||
| console.log(fmtChars); | ||
| console.log(fmt[0], fmt[1], JSON.stringify(fmt[4])); | ||
|
|
||
| // --- empty and single-char concatenations --------------------------------- | ||
| const empty = "" + ""; | ||
| console.log(empty.length, String(empty[0]), Array.from(empty).length); | ||
| const one = "" + "x"; | ||
| console.log(one.length, one[0], Array.from(one).length); | ||
|
|
||
| // --- non-ASCII, where UTF-16 indexing differs from bytes ------------------ | ||
| const uni = "é" + "ü"; | ||
| console.log(uni.length, uni[0], uni[1], Array.from(uni).length); | ||
|
|
||
| // --- long (heap-backed) concatenation must still work --------------------- | ||
| const long = "a".repeat(40) + "b".repeat(40); | ||
| console.log(long.length, long[0], long[79], Array.from(long).length); | ||
|
|
||
| // --- concatenation built in a loop ---------------------------------------- | ||
| let acc = ""; | ||
| for (const p of ["x", "y", "z"]) acc += p; | ||
| console.log(acc.length, acc[0], acc[2], Array.from(acc).join("")); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Do not imply that
#6872is resolved.The PR objective says
#6872remains open pending validation of its specific JSON-replacer reproduction. Reword this as related investigation rather than calling this its blocker.🤖 Prompt for AI Agents