From d63fd4a256f15d7e2cf7f6684f6d0c172f97da5f Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Thu, 27 Aug 2026 20:29:53 +0530 Subject: [PATCH] stop truncating out-of-bmp identifier escapes to a char --- .../javascript/jscomp/parsing/parser/Scanner.java | 12 +++++++++++- .../google/javascript/jscomp/parsing/ParserTest.java | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/com/google/javascript/jscomp/parsing/parser/Scanner.java b/src/com/google/javascript/jscomp/parsing/parser/Scanner.java index 8149d681e8c..68b9273889c 100644 --- a/src/com/google/javascript/jscomp/parsing/parser/Scanner.java +++ b/src/com/google/javascript/jscomp/parsing/parser/Scanner.java @@ -882,8 +882,18 @@ private Token scanIdentifierOrKeyword(int beginToken, char ch) { hexDigits = value.substring(escapeStart + 3, escapeEnd); escapeEnd++; } + // Identifiers are carried around the compiler as sequences of char (UTF-16 code units), so a + // code point that does not fit in a single char cannot be represented here. The value used + // to be cast straight to char, which silently kept only the low 16 bits: a braced escape for + // U+10041 was accepted as the identifier "A", and even code points above the U+10FFFF + // maximum (e.g. U+110041) slipped through the same way. Reject anything outside the BMP + // rather than aliasing it to a different character. // TODO(mattloring): Allow code points >= 0xFFFF (greater than the size of a char). - char ch = (char) Integer.parseInt(hexDigits, 0x10); + int codePoint = Integer.parseInt(hexDigits, 0x10); + if (codePoint > 0xFFFF) { + return null; + } + char ch = (char) codePoint; if (!Identifiers.isIdentifierPart(ch)) { return null; } diff --git a/test/com/google/javascript/jscomp/parsing/ParserTest.java b/test/com/google/javascript/jscomp/parsing/ParserTest.java index 4404d2e2b91..fb297aa0e1f 100644 --- a/test/com/google/javascript/jscomp/parsing/ParserTest.java +++ b/test/com/google/javascript/jscomp/parsing/ParserTest.java @@ -5221,6 +5221,11 @@ public void testInvalidUnicodePointEscapeInIdentifiers() { // Legal unicode but invalid in identifier parseError("Js\\u{99}ompiler", "Invalid escape sequence"); parseError("Js\\u{10000}ompiler", "Invalid escape sequence"); + // Code points outside the BMP must not be truncated to their low 16 bits. U+10041 shares its + // low bits with U+0041 ('A') and U+110041 is above the U+10FFFF maximum; both used to be + // silently accepted. + parseError("Js\\u{10041}ompiler", "Invalid escape sequence"); + parseError("Js\\u{110041}ompiler", "Invalid escape sequence"); } @Test