Skip to content

Fix escape() corrupting the unescaped prefix of its result - #2501

Merged
gbrail merged 1 commit into
mozilla:masterfrom
naaz234:escape-output-prefix
Sep 27, 2026
Merged

gbrail merged 1 commit into
mozilla:masterfrom
naaz234:escape-output-prefix

Conversation

@naaz234

@naaz234 naaz234 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor
    js> escape('ab cd')
    "[o%20cd"      // expected "ab%20cd"

I hit this reading js_escape next to encode(). The output StringBuilder is created lazily on the first character that needs escaping, and it is meant to be seeded with the already-processed prefix str[0..k). It instead does sb.append(s), where s is the VarScope parameter, and then setLength(k), so the leading run of safe characters is overwritten by the first k characters of the scope object's toString() (or NUL padding when that is shorter). Every input that starts with an unescaped run before its first escaped character comes back corrupted.

encode() a few lines down uses the same lazy-init idiom correctly with str, which is what this changes js_escape to do. I added GlobalEscapeTest covering prefixes of different lengths, a leading escaped char, an all-unescaped string, and a %uXXXX escape; the first group fails before the change and passes after.

js_escape seeded its output builder with the VarScope parameter instead of the input string before truncating, so any leading run of unescaped characters was replaced by the scope object's toString().
@rbri

rbri commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

looks like a regression in 75070da, many thanks for finding @naaz234

@naaz234

naaz234 commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Yes, that's the one. It renamed the input String s to str and added the VarScope s parameter in the same change, so the leftover sb.append(s) in the lazy init silently rebound to the scope object and kept compiling. As far as I can see the existing escape coverage (the test262 annexB cases and the legacy 15.1.2.4.js) only feeds single characters or all-unescaped strings, so nothing exercised a safe prefix before the first escaped character, which is why it slipped through.

@aardvark179

Copy link
Copy Markdown
Contributor

And because everything can be converted to a string by the string builder itself we didn't catch the problem. 😕

@gbrail

gbrail commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator

Great -- thanks!

@gbrail
gbrail merged commit 6385eec into mozilla:master Sep 27, 2026
15 of 16 checks passed
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.

4 participants