[ES6] Implement Unicode code point escapes
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Apr 2015 16:33:12 +0000 (16:33 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 29 Apr 2015 16:33:12 +0000 (16:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=144377

Reviewed by Antti Koivisto.

Source/JavaScriptCore:

* parser/Lexer.cpp: Moved the UnicodeHexValue class in here from
the header. Made it a non-member class so it doesn't need to be part
of a template. Made it use UChar32 instead of int for the value to
make it clearer what goes into this class.
(JSC::ParsedUnicodeEscapeValue::isIncomplete): Added. Replaces the
old type() function.
(JSC::Lexer<CharacterType>::parseUnicodeEscape): Renamed from
parseFourDigitUnicodeHex and added support for code point escapes.
(JSC::isLatin1): Added an overload for UChar32.
(JSC::isIdentStart): Changed this to take UChar32; no caller tries
to call it with a UChar, so no need to overload for that type for now.
(JSC::isNonLatin1IdentPart): Changed argument type to UChar32 for clarity.
Also added FIXME about a subtle ES6 change that we might want to make later.
(JSC::isIdentPart): Changed this to take UChar32; no caller tries
to call it with a UChar, so no need to overload for that type for now.
(JSC::isIdentPartIncludingEscapeTemplate): Made this a template so that we
don't need to repeat the code twice. Added code to handle code point escapes.
(JSC::isIdentPartIncludingEscape): Call the template instead of having the
code in line.
(JSC::Lexer<CharacterType>::recordUnicodeCodePoint): Added.
(JSC::Lexer<CharacterType>::parseIdentifierSlowCase): Made small tweaks and
updated to call parseUnicodeEscape instead of parseFourDigitUnicodeHex.
(JSC::Lexer<CharacterType>::parseComplexEscape): Call parseUnicodeEscape
instead of parseFourDigitUnicodeHex. Move the code to handle "\u" before
the code that handles the escapes, since the code point escape code now
consumes characters while parsing rather than peeking ahead. Test case
covers this: Symptom would be that "\u{" would evaluate to "u" instead of
giving a syntax error.

* parser/Lexer.h: Updated for above changes.

* runtime/StringConstructor.cpp:
(JSC::stringFromCodePoint): Use ICU's UCHAR_MAX_VALUE instead of writing
out 0x10FFFF; clearer this way.

Source/WebCore:

Test: js/unicode-escape-sequences.html

* css/CSSParser.cpp:
(WebCore::CSSParser::parseEscape): Use ICU's UCHAR_MAX_VALUE instead of writing
out 0x10FFFF; clearer this way. Also use our replacementCharacter instead of
writing out 0xFFFD.

* html/parser/HTMLEntityParser.cpp:
(WebCore::isAlphaNumeric): Deleted.
(WebCore::HTMLEntityParser::legalEntityFor): Use ICU's UCHAR_MAX_VALUE and
U_IS_SURROGATE instead of writing the code out. Didn't use U_IS_UNICODE_CHAR
because that also includes U_IS_UNICODE_NONCHAR and thus would change behavior,
but maye it's something we want to do in the future.
(WebCore::HTMLEntityParser::consumeNamedEntity): Use isASCIIAlphanumeric instead
of a the function in this file that does the same thing less efficiently.

* html/parser/InputStreamPreprocessor.h:
(WebCore::InputStreamPreprocessor::processNextInputCharacter): Use
replacementCharacter from CharacterNames.h instead of writing out 0xFFFd.

* xml/parser/CharacterReferenceParserInlines.h:
(WebCore::consumeCharacterReference): Use ICU's UCHAR_MAX_VALUE instead of
defining our own local highestValidCharacter constant.

LayoutTests:

* js/script-tests/unicode-escape-sequences.js: Added.
* js/unicode-escape-sequences-expected.txt: Added.
* js/unicode-escape-sequences.html: Added. Generated with make-script-test-wrappers.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@183552 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/js/script-tests/unicode-escape-sequences.js [new file with mode: 0644]
LayoutTests/js/unicode-escape-sequences-expected.txt [new file with mode: 0644]
LayoutTests/js/unicode-escape-sequences.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/parser/Lexer.cpp
Source/JavaScriptCore/parser/Lexer.h
Source/JavaScriptCore/runtime/StringConstructor.cpp
Source/WebCore/ChangeLog
Source/WebCore/css/CSSParser.cpp
Source/WebCore/html/parser/HTMLEntityParser.cpp
Source/WebCore/html/parser/InputStreamPreprocessor.h
Source/WebCore/xml/parser/CharacterReferenceParserInlines.h

index 5765e9b..0e6736f 100644 (file)
@@ -1,3 +1,14 @@
+2015-04-29  Darin Adler  <darin@apple.com>
+
+        [ES6] Implement Unicode code point escapes
+        https://bugs.webkit.org/show_bug.cgi?id=144377
+
+        Reviewed by Antti Koivisto.
+
+        * js/script-tests/unicode-escape-sequences.js: Added.
+        * js/unicode-escape-sequences-expected.txt: Added.
+        * js/unicode-escape-sequences.html: Added. Generated with make-script-test-wrappers.
+
 2015-04-29  Hyungwook Lee  <hyungwook.lee@navercorp.com>
 
         Fix crash in WebCore::LogicalSelectionOffsetCaches::ContainingBlockInfo::setBlock().
diff --git a/LayoutTests/js/script-tests/unicode-escape-sequences.js b/LayoutTests/js/script-tests/unicode-escape-sequences.js
new file mode 100644 (file)
index 0000000..18e6e2d
--- /dev/null
@@ -0,0 +1,138 @@
+description("Test of Unicode escape sequences in string literals and identifiers, especially code point escape sequences.");
+
+function codeUnits(string)
+{
+    var result = [];
+    for (var i = 0; i < string.length; ++i) {
+        var hex = "000" + string.charCodeAt(i).toString(16).toUpperCase();
+        result.push(hex.substring(hex.length - 4));
+    }
+    return result.join(",");
+}
+
+function testStringUnicodeEscapeSequence(sequence, expectedResult)
+{
+    shouldBeEqualToString('codeUnits("\\u' + sequence + '")', expectedResult);
+}
+
+function testInvalidStringUnicodeEscapeSequence(sequence)
+{
+    shouldThrow('codeUnits("\\u' + sequence + '")');
+}
+
+function testIdentifierStartUnicodeEscapeSequence(sequence, expectedResult)
+{
+    shouldBeEqualToString('codeUnits(function \\u' + sequence + '(){}.name)', expectedResult);
+}
+
+function testInvalidIdentifierStartUnicodeEscapeSequence(sequence)
+{
+    shouldThrow('codeUnits(function \\u' + sequence + '(){}.name)');
+}
+
+function testIdentifierPartUnicodeEscapeSequence(sequence, expectedResult)
+{
+    shouldBeEqualToString('codeUnits(function x\\u' + sequence + '(){}.name.substring(1))', expectedResult);
+}
+
+function testInvalidIdentifierPartUnicodeEscapeSequence(sequence)
+{
+    shouldThrow('codeUnits(function x\\u' + sequence + '(){}.name.substring(1))');
+}
+
+testStringUnicodeEscapeSequence("", "0075");
+testStringUnicodeEscapeSequence("{0}", "0000");
+testStringUnicodeEscapeSequence("{41}", "0041");
+testStringUnicodeEscapeSequence("{D800}", "D800");
+testStringUnicodeEscapeSequence("{d800}", "D800");
+testStringUnicodeEscapeSequence("{DC00}", "DC00");
+testStringUnicodeEscapeSequence("{dc00}", "DC00");
+testStringUnicodeEscapeSequence("{FFFF}", "FFFF");
+testStringUnicodeEscapeSequence("{ffff}", "FFFF");
+testStringUnicodeEscapeSequence("{10000}", "D800,DC00");
+testStringUnicodeEscapeSequence("{10001}", "D800,DC01");
+testStringUnicodeEscapeSequence("{102C0}", "D800,DEC0");
+testStringUnicodeEscapeSequence("{102c0}", "D800,DEC0");
+testStringUnicodeEscapeSequence("{1D306}", "D834,DF06");
+testStringUnicodeEscapeSequence("{1d306}", "D834,DF06");
+testStringUnicodeEscapeSequence("{10FFFE}", "DBFF,DFFE");
+testStringUnicodeEscapeSequence("{10fffe}", "DBFF,DFFE");
+testStringUnicodeEscapeSequence("{10FFFF}", "DBFF,DFFF");
+testStringUnicodeEscapeSequence("{10ffff}", "DBFF,DFFF");
+testStringUnicodeEscapeSequence("{00000000000000000000000010FFFF}", "DBFF,DFFF");
+testStringUnicodeEscapeSequence("{00000000000000000000000010ffff}", "DBFF,DFFF");
+
+testInvalidStringUnicodeEscapeSequence("x");
+testInvalidStringUnicodeEscapeSequence("{");
+testInvalidStringUnicodeEscapeSequence("{}");
+testInvalidStringUnicodeEscapeSequence("{G}");
+testInvalidStringUnicodeEscapeSequence("{1G}");
+testInvalidStringUnicodeEscapeSequence("{110000}");
+testInvalidStringUnicodeEscapeSequence("{1000000}");
+testInvalidStringUnicodeEscapeSequence("{100000000000000000000000}");
+
+testIdentifierStartUnicodeEscapeSequence("{41}", "0041");
+testIdentifierStartUnicodeEscapeSequence("{102C0}", "D800,DEC0");
+testIdentifierStartUnicodeEscapeSequence("{102c0}", "D800,DEC0");
+testIdentifierStartUnicodeEscapeSequence("{1D306}", "D834,DF06");
+testIdentifierStartUnicodeEscapeSequence("{1d306}", "D834,DF06");
+
+testInvalidIdentifierStartUnicodeEscapeSequence("");
+testInvalidIdentifierStartUnicodeEscapeSequence("{0}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{D800}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{d800}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{DC00}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{dc00}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{FFFF}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{ffff}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10000}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10001}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10FFFE}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10fffe}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10FFFF}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{10ffff}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{00000000000000000000000010FFFF}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{00000000000000000000000010ffff}");
+
+testInvalidIdentifierStartUnicodeEscapeSequence("x");
+testInvalidIdentifierStartUnicodeEscapeSequence("{");
+testInvalidIdentifierStartUnicodeEscapeSequence("{}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{G}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{1G}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{110000}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{1000000}");
+testInvalidIdentifierStartUnicodeEscapeSequence("{100000000000000000000000}");
+
+testIdentifierPartUnicodeEscapeSequence("{41}", "0041");
+testIdentifierPartUnicodeEscapeSequence("{10000}", "D800,DC00");
+testIdentifierPartUnicodeEscapeSequence("{10001}", "D800,DC01");
+testIdentifierPartUnicodeEscapeSequence("{102C0}", "D800,DEC0");
+testIdentifierPartUnicodeEscapeSequence("{102c0}", "D800,DEC0");
+
+testInvalidIdentifierPartUnicodeEscapeSequence("");
+testInvalidIdentifierPartUnicodeEscapeSequence("{0}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{D800}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{d800}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{DC00}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{dc00}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{FFFF}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{ffff}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{1D306}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{1d306}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{10FFFE}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{10fffe}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{10FFFF}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{10ffff}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{00000000000000000000000010FFFF}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{00000000000000000000000010ffff}");
+
+testInvalidIdentifierPartUnicodeEscapeSequence("x");
+testInvalidIdentifierPartUnicodeEscapeSequence("{");
+testInvalidIdentifierPartUnicodeEscapeSequence("{}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{G}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{1G}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{110000}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{1000000}");
+testInvalidIdentifierPartUnicodeEscapeSequence("{100000000000000000000000}");
+
+var successfullyParsed = true;
diff --git a/LayoutTests/js/unicode-escape-sequences-expected.txt b/LayoutTests/js/unicode-escape-sequences-expected.txt
new file mode 100644 (file)
index 0000000..828f1a5
--- /dev/null
@@ -0,0 +1,96 @@
+Test of Unicode escape sequences in string literals and identifiers, especially code point escape sequences.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS codeUnits("\u") is "0075"
+PASS codeUnits("\u{0}") is "0000"
+PASS codeUnits("\u{41}") is "0041"
+PASS codeUnits("\u{D800}") is "D800"
+PASS codeUnits("\u{d800}") is "D800"
+PASS codeUnits("\u{DC00}") is "DC00"
+PASS codeUnits("\u{dc00}") is "DC00"
+PASS codeUnits("\u{FFFF}") is "FFFF"
+PASS codeUnits("\u{ffff}") is "FFFF"
+PASS codeUnits("\u{10000}") is "D800,DC00"
+PASS codeUnits("\u{10001}") is "D800,DC01"
+PASS codeUnits("\u{102C0}") is "D800,DEC0"
+PASS codeUnits("\u{102c0}") is "D800,DEC0"
+PASS codeUnits("\u{1D306}") is "D834,DF06"
+PASS codeUnits("\u{1d306}") is "D834,DF06"
+PASS codeUnits("\u{10FFFE}") is "DBFF,DFFE"
+PASS codeUnits("\u{10fffe}") is "DBFF,DFFE"
+PASS codeUnits("\u{10FFFF}") is "DBFF,DFFF"
+PASS codeUnits("\u{10ffff}") is "DBFF,DFFF"
+PASS codeUnits("\u{00000000000000000000000010FFFF}") is "DBFF,DFFF"
+PASS codeUnits("\u{00000000000000000000000010ffff}") is "DBFF,DFFF"
+PASS codeUnits("\ux") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{G}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{1G}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{110000}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{1000000}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits("\u{100000000000000000000000}") threw exception SyntaxError: \u can only be followed by a Unicode character sequence.
+PASS codeUnits(function \u{41}(){}.name) is "0041"
+PASS codeUnits(function \u{102C0}(){}.name) is "D800,DEC0"
+PASS codeUnits(function \u{102c0}(){}.name) is "D800,DEC0"
+PASS codeUnits(function \u{1D306}(){}.name) is "D834,DF06"
+PASS codeUnits(function \u{1d306}(){}.name) is "D834,DF06"
+PASS codeUnits(function \u(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u'.
+PASS codeUnits(function \u{0}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{0}'.
+PASS codeUnits(function \u{D800}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{D800}'.
+PASS codeUnits(function \u{d800}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{d800}'.
+PASS codeUnits(function \u{DC00}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{DC00}'.
+PASS codeUnits(function \u{dc00}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{dc00}'.
+PASS codeUnits(function \u{FFFF}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{FFFF}'.
+PASS codeUnits(function \u{ffff}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{ffff}'.
+PASS codeUnits(function \u{10000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10000}'.
+PASS codeUnits(function \u{10001}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10001}'.
+PASS codeUnits(function \u{10FFFE}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10FFFE}'.
+PASS codeUnits(function \u{10fffe}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10fffe}'.
+PASS codeUnits(function \u{10FFFF}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10FFFF}'.
+PASS codeUnits(function \u{10ffff}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{10ffff}'.
+PASS codeUnits(function \u{00000000000000000000000010FFFF}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{00000000000000000000000010FFFF}'.
+PASS codeUnits(function \u{00000000000000000000000010ffff}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{00000000000000000000000010ffff}'.
+PASS codeUnits(function \ux(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u'.
+PASS codeUnits(function \u{(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{'.
+PASS codeUnits(function \u{}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{'.
+PASS codeUnits(function \u{G}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{'.
+PASS codeUnits(function \u{1G}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{1'.
+PASS codeUnits(function \u{110000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{11000'.
+PASS codeUnits(function \u{1000000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{100000'.
+PASS codeUnits(function \u{100000000000000000000000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{100000'.
+PASS codeUnits(function x\u{41}(){}.name.substring(1)) is "0041"
+PASS codeUnits(function x\u{10000}(){}.name.substring(1)) is "D800,DC00"
+PASS codeUnits(function x\u{10001}(){}.name.substring(1)) is "D800,DC01"
+PASS codeUnits(function x\u{102C0}(){}.name.substring(1)) is "D800,DEC0"
+PASS codeUnits(function x\u{102c0}(){}.name.substring(1)) is "D800,DEC0"
+PASS codeUnits(function x\u(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u'.
+PASS codeUnits(function x\u{0}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{0}'.
+PASS codeUnits(function x\u{D800}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{D800}'.
+PASS codeUnits(function x\u{d800}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{d800}'.
+PASS codeUnits(function x\u{DC00}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{DC00}'.
+PASS codeUnits(function x\u{dc00}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{dc00}'.
+PASS codeUnits(function x\u{FFFF}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{FFFF}'.
+PASS codeUnits(function x\u{ffff}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{ffff}'.
+PASS codeUnits(function x\u{1D306}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{1D306}'.
+PASS codeUnits(function x\u{1d306}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{1d306}'.
+PASS codeUnits(function x\u{10FFFE}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{10FFFE}'.
+PASS codeUnits(function x\u{10fffe}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{10fffe}'.
+PASS codeUnits(function x\u{10FFFF}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{10FFFF}'.
+PASS codeUnits(function x\u{10ffff}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{10ffff}'.
+PASS codeUnits(function x\u{00000000000000000000000010FFFF}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{00000000000000000000000010FFFF}'.
+PASS codeUnits(function x\u{00000000000000000000000010ffff}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{00000000000000000000000010ffff}'.
+PASS codeUnits(function x\ux(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u'.
+PASS codeUnits(function x\u{(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{'.
+PASS codeUnits(function x\u{}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{'.
+PASS codeUnits(function x\u{G}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{'.
+PASS codeUnits(function x\u{1G}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{1'.
+PASS codeUnits(function x\u{110000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{11000'.
+PASS codeUnits(function x\u{1000000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{100000'.
+PASS codeUnits(function x\u{100000000000000000000000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{100000'.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/unicode-escape-sequences.html b/LayoutTests/js/unicode-escape-sequences.html
new file mode 100644 (file)
index 0000000..3a23002
--- /dev/null
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../resources/js-test-pre.js"></script>
+<script src="script-tests/unicode-escape-sequences.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
index e47b872..5c70855 100644 (file)
@@ -1,3 +1,45 @@
+2015-04-29  Darin Adler  <darin@apple.com>
+
+        [ES6] Implement Unicode code point escapes
+        https://bugs.webkit.org/show_bug.cgi?id=144377
+
+        Reviewed by Antti Koivisto.
+
+        * parser/Lexer.cpp: Moved the UnicodeHexValue class in here from
+        the header. Made it a non-member class so it doesn't need to be part
+        of a template. Made it use UChar32 instead of int for the value to
+        make it clearer what goes into this class.
+        (JSC::ParsedUnicodeEscapeValue::isIncomplete): Added. Replaces the
+        old type() function.
+        (JSC::Lexer<CharacterType>::parseUnicodeEscape): Renamed from
+        parseFourDigitUnicodeHex and added support for code point escapes.
+        (JSC::isLatin1): Added an overload for UChar32.
+        (JSC::isIdentStart): Changed this to take UChar32; no caller tries
+        to call it with a UChar, so no need to overload for that type for now.
+        (JSC::isNonLatin1IdentPart): Changed argument type to UChar32 for clarity.
+        Also added FIXME about a subtle ES6 change that we might want to make later.
+        (JSC::isIdentPart): Changed this to take UChar32; no caller tries
+        to call it with a UChar, so no need to overload for that type for now.
+        (JSC::isIdentPartIncludingEscapeTemplate): Made this a template so that we
+        don't need to repeat the code twice. Added code to handle code point escapes.
+        (JSC::isIdentPartIncludingEscape): Call the template instead of having the
+        code in line.
+        (JSC::Lexer<CharacterType>::recordUnicodeCodePoint): Added.
+        (JSC::Lexer<CharacterType>::parseIdentifierSlowCase): Made small tweaks and
+        updated to call parseUnicodeEscape instead of parseFourDigitUnicodeHex.
+        (JSC::Lexer<CharacterType>::parseComplexEscape): Call parseUnicodeEscape
+        instead of parseFourDigitUnicodeHex. Move the code to handle "\u" before
+        the code that handles the escapes, since the code point escape code now
+        consumes characters while parsing rather than peeking ahead. Test case
+        covers this: Symptom would be that "\u{" would evaluate to "u" instead of
+        giving a syntax error.
+
+        * parser/Lexer.h: Updated for above changes.
+
+        * runtime/StringConstructor.cpp:
+        (JSC::stringFromCodePoint): Use ICU's UCHAR_MAX_VALUE instead of writing
+        out 0x10FFFF; clearer this way.
+
 2015-04-29  Martin Robinson  <mrobinson@igalia.com>
 
         [CMake] [GTK] Organize and clean up unused CMake variables
index 5a0c308..96eb7aa 100644 (file)
@@ -610,22 +610,60 @@ ALWAYS_INLINE T Lexer<T>::peek(int offset) const
     return (code < m_codeEnd) ? *code : 0;
 }
 
-template <typename T>
-typename Lexer<T>::UnicodeHexValue Lexer<T>::parseFourDigitUnicodeHex()
-{
-    T char1 = peek(1);
-    T char2 = peek(2);
-    T char3 = peek(3);
+struct ParsedUnicodeEscapeValue {
+    ParsedUnicodeEscapeValue(UChar32 value)
+        : m_value(value)
+    {
+        ASSERT(isValid());
+    }
 
-    if (UNLIKELY(!isASCIIHexDigit(m_current) || !isASCIIHexDigit(char1) || !isASCIIHexDigit(char2) || !isASCIIHexDigit(char3)))
-        return UnicodeHexValue((m_code + 4) >= m_codeEnd ? UnicodeHexValue::IncompleteHex : UnicodeHexValue::InvalidHex);
+    enum SpecialValueType { Incomplete = -2, Invalid = -1 };
+    ParsedUnicodeEscapeValue(SpecialValueType type)
+        : m_value(type)
+    {
+    }
 
-    int result = convertUnicode(m_current, char1, char2, char3);
+    bool isValid() const { return m_value >= 0; }
+    bool isIncomplete() const { return m_value == Incomplete; }
+
+    UChar32 value() const
+    {
+        ASSERT(isValid());
+        return m_value;
+    }
+
+private:
+    UChar32 m_value;
+};
+
+template<typename CharacterType> ParsedUnicodeEscapeValue Lexer<CharacterType>::parseUnicodeEscape()
+{
+    if (m_current == '{') {
+        shift();
+        UChar32 codePoint = 0;
+        do {
+            if (!isASCIIHexDigit(m_current))
+                return m_current ? ParsedUnicodeEscapeValue::Invalid : ParsedUnicodeEscapeValue::Incomplete;
+            codePoint = (codePoint << 4) | toASCIIHexValue(m_current);
+            if (codePoint > UCHAR_MAX_VALUE)
+                return ParsedUnicodeEscapeValue::Invalid;
+            shift();
+        } while (m_current != '}');
+        shift();
+        return codePoint;
+    }
+
+    auto character2 = peek(1);
+    auto character3 = peek(2);
+    auto character4 = peek(3);
+    if (UNLIKELY(!isASCIIHexDigit(m_current) || !isASCIIHexDigit(character2) || !isASCIIHexDigit(character3) || !isASCIIHexDigit(character4)))
+        return (m_code + 4) >= m_codeEnd ? ParsedUnicodeEscapeValue::Incomplete : ParsedUnicodeEscapeValue::Invalid;
+    auto result = convertUnicode(m_current, character2, character3, character4);
     shift();
     shift();
     shift();
     shift();
-    return UnicodeHexValue(result);
+    return result;
 }
 
 template <typename T>
@@ -665,18 +703,24 @@ static ALWAYS_INLINE bool isLatin1(UChar c)
     return c < 256;
 }
 
+static ALWAYS_INLINE bool isLatin1(UChar32 c)
+{
+    return !(c & ~0xFF);
+}
+
 static inline bool isIdentStart(LChar c)
 {
     return typesOfLatin1Characters[c] == CharacterIdentifierStart;
 }
 
-static inline bool isIdentStart(UChar c)
+static inline bool isIdentStart(UChar32 c)
 {
     return isLatin1(c) ? isIdentStart(static_cast<LChar>(c)) : isNonLatin1IdentStart(c);
 }
 
-static NEVER_INLINE bool isNonLatin1IdentPart(int c)
+static NEVER_INLINE bool isNonLatin1IdentPart(UChar32 c)
 {
+    // FIXME: ES6 says this should be based on the Unicode property ID_Continue now instead.
     return (U_GET_GC_MASK(c) & (U_GC_L_MASK | U_GC_MN_MASK | U_GC_MC_MASK | U_GC_ND_MASK | U_GC_PC_MASK)) || c == 0x200C || c == 0x200D;
 }
 
@@ -688,39 +732,59 @@ static ALWAYS_INLINE bool isIdentPart(LChar c)
     return typesOfLatin1Characters[c] <= CharacterNumber;
 }
 
-static ALWAYS_INLINE bool isIdentPart(UChar c)
+static ALWAYS_INLINE bool isIdentPart(UChar32 c)
 {
     return isLatin1(c) ? isIdentPart(static_cast<LChar>(c)) : isNonLatin1IdentPart(c);
 }
 
-template <typename T>
-bool isUnicodeEscapeIdentPart(const T* code)
+static ALWAYS_INLINE bool isIdentPart(UChar c)
 {
-    T char1 = code[0];
-    T char2 = code[1];
-    T char3 = code[2];
-    T char4 = code[3];
-    
-    if (!isASCIIHexDigit(char1) || !isASCIIHexDigit(char2) || !isASCIIHexDigit(char3) || !isASCIIHexDigit(char4))
-        return false;
-    
-    return isIdentPart(Lexer<T>::convertUnicode(char1, char2, char3, char4));
+    return isIdentPart(static_cast<UChar32>(c));
 }
 
-static ALWAYS_INLINE bool isIdentPartIncludingEscape(const LChar* code, const LChar* codeEnd)
+template<typename CharacterType> ALWAYS_INLINE bool isIdentPartIncludingEscapeTemplate(const CharacterType* code, const CharacterType* codeEnd)
 {
-    if (isIdentPart(*code))
+    if (isIdentPart(code[0]))
         return true;
 
-    return (*code == '\\' && ((codeEnd - code) >= 6) && code[1] == 'u' && isUnicodeEscapeIdentPart(code+2));
+    // Shortest sequence handled below is \u{0}, which is 5 characters.
+    if (!(code[0] == '\\' && codeEnd - code >= 5 && code[1] == 'u'))
+        return false;
+
+    if (code[2] == '{') {
+        UChar32 codePoint = 0;
+        const CharacterType* pointer;
+        for (pointer = &code[3]; pointer < codeEnd; ++pointer) {
+            auto digit = *pointer;
+            if (!isASCIIHexDigit(digit))
+                break;
+            codePoint = (codePoint << 4) | toASCIIHexValue(digit);
+            if (codePoint > UCHAR_MAX_VALUE)
+                return false;
+        }
+        return isIdentPart(codePoint) && pointer < codeEnd && *pointer == '}';
+    }
+
+    // Shortest sequence handled below is \uXXXX, which is 6 characters.
+    if (codeEnd - code < 6)
+        return false;
+
+    auto character1 = code[2];
+    auto character2 = code[3];
+    auto character3 = code[4];
+    auto character4 = code[5];
+    return isASCIIHexDigit(character1) && isASCIIHexDigit(character2) && isASCIIHexDigit(character3) && isASCIIHexDigit(character4)
+        && isIdentPart(Lexer<LChar>::convertUnicode(character1, character2, character3, character4));
+}
+
+static ALWAYS_INLINE bool isIdentPartIncludingEscape(const LChar* code, const LChar* codeEnd)
+{
+    return isIdentPartIncludingEscapeTemplate(code, codeEnd);
 }
 
 static ALWAYS_INLINE bool isIdentPartIncludingEscape(const UChar* code, const UChar* codeEnd)
 {
-    if (isIdentPart(*code))
-        return true;
-    
-    return (*code == '\\' && ((codeEnd - code) >= 6) && code[1] == 'u' && isUnicodeEscapeIdentPart(code+2));
+    return isIdentPartIncludingEscapeTemplate(code, codeEnd);
 }
 
 static inline LChar singleEscape(int c)
@@ -799,6 +863,18 @@ inline void Lexer<T>::record16(int c)
     m_buffer16.append(static_cast<UChar>(c));
 }
     
+template<typename CharacterType> inline void Lexer<CharacterType>::recordUnicodeCodePoint(UChar32 codePoint)
+{
+    ASSERT(codePoint >= 0);
+    ASSERT(codePoint <= UCHAR_MAX_VALUE);
+    if (U_IS_BMP(codePoint))
+        record16(codePoint);
+    else {
+        UChar codeUnits[2] = { U16_LEAD(codePoint), U16_TRAIL(codePoint) };
+        append16(codeUnits, 2);
+    }
+}
+
 #if !ASSERT_DISABLED
 bool isSafeBuiltinIdentifier(VM& vm, const Identifier* ident)
 {
@@ -807,6 +883,7 @@ bool isSafeBuiltinIdentifier(VM& vm, const Identifier* ident)
     /* Just block any use of suspicious identifiers.  This is intended to
      * be used as a safety net while implementing builtins.
      */
+    // FIXME: How can a debug-only assertion be a safety net?
     if (*ident == vm.propertyNames->builtinNames().callPublicName())
         return false;
     if (*ident == vm.propertyNames->builtinNames().applyPublicName())
@@ -960,11 +1037,10 @@ template <bool shouldCreateIdentifier> ALWAYS_INLINE JSTokenType Lexer<UChar>::p
     return IDENT;
 }
 
-template <typename T>
-template <bool shouldCreateIdentifier> JSTokenType Lexer<T>::parseIdentifierSlowCase(JSTokenData* tokenData, unsigned lexerFlags, bool strictMode)
+template<typename CharacterType> template<bool shouldCreateIdentifier> JSTokenType Lexer<CharacterType>::parseIdentifierSlowCase(JSTokenData* tokenData, unsigned lexerFlags, bool strictMode)
 {
     const ptrdiff_t remaining = m_codeEnd - m_code;
-    const T* identifierStart = currentSourcePtr();
+    auto identifierStart = currentSourcePtr();
     bool bufferRequired = false;
 
     while (true) {
@@ -983,19 +1059,18 @@ template <bool shouldCreateIdentifier> JSTokenType Lexer<T>::parseIdentifierSlow
         if (UNLIKELY(m_current != 'u'))
             return atEnd() ? UNTERMINATED_IDENTIFIER_ESCAPE_ERRORTOK : INVALID_IDENTIFIER_ESCAPE_ERRORTOK;
         shift();
-        UnicodeHexValue character = parseFourDigitUnicodeHex();
+        auto character = parseUnicodeEscape();
         if (UNLIKELY(!character.isValid()))
-            return character.valueType() == UnicodeHexValue::IncompleteHex ? UNTERMINATED_IDENTIFIER_UNICODE_ESCAPE_ERRORTOK : INVALID_IDENTIFIER_UNICODE_ESCAPE_ERRORTOK;
-        UChar ucharacter = static_cast<UChar>(character.value());
-        if (UNLIKELY(m_buffer16.size() ? !isIdentPart(ucharacter) : !isIdentStart(ucharacter)))
+            return character.isIncomplete() ? UNTERMINATED_IDENTIFIER_UNICODE_ESCAPE_ERRORTOK : INVALID_IDENTIFIER_UNICODE_ESCAPE_ERRORTOK;
+        if (UNLIKELY(m_buffer16.size() ? !isIdentPart(character.value()) : !isIdentStart(character.value())))
             return INVALID_IDENTIFIER_UNICODE_ESCAPE_ERRORTOK;
         if (shouldCreateIdentifier)
-            record16(ucharacter);
+            recordUnicodeCodePoint(character.value());
         identifierStart = currentSourcePtr();
     }
 
     int identifierLength;
-    const Identifier* ident = 0;
+    const Identifier* ident = nullptr;
     if (shouldCreateIdentifier) {
         if (!bufferRequired) {
             identifierLength = currentSourcePtr() - identifierStart;
@@ -1008,7 +1083,7 @@ template <bool shouldCreateIdentifier> JSTokenType Lexer<T>::parseIdentifierSlow
 
         tokenData->ident = ident;
     } else
-        tokenData->ident = 0;
+        tokenData->ident = nullptr;
 
     if (LIKELY(!bufferRequired && !(lexerFlags & LexerFlagsIgnoreReservedWords))) {
         ASSERT(shouldCreateIdentifier);
@@ -1125,21 +1200,22 @@ template <bool shouldBuildStrings> ALWAYS_INLINE auto Lexer<T>::parseComplexEsca
 
     if (m_current == 'u') {
         shift();
-        UnicodeHexValue character = parseFourDigitUnicodeHex();
-        if (character.isValid()) {
+
+        if (escapeParseMode == EscapeParseMode::String && m_current == stringQuoteCharacter) {
             if (shouldBuildStrings)
-                record16(character.value());
+                record16('u');
             return StringParsedSuccessfully;
         }
 
-        if (escapeParseMode == EscapeParseMode::String && m_current == stringQuoteCharacter) {
+        auto character = parseUnicodeEscape();
+        if (character.isValid()) {
             if (shouldBuildStrings)
-                record16('u');
+                recordUnicodeCodePoint(character.value());
             return StringParsedSuccessfully;
         }
 
         m_lexErrorMessage = ASCIILiteral("\\u can only be followed by a Unicode character sequence");
-        return character.valueType() == UnicodeHexValue::IncompleteHex ? StringUnterminated : StringCannotBeParsed;
+        return character.isIncomplete() ? StringUnterminated : StringCannotBeParsed;
     }
 
     if (strictMode) {
index 39010a1..8afef26 100644 (file)
@@ -65,6 +65,8 @@ enum LexerFlags {
     LexexFlagsDontBuildKeywords = 4
 };
 
+struct ParsedUnicodeEscapeValue;
+
 template <typename T>
 class Lexer {
     WTF_MAKE_NONCOPYABLE(Lexer);
@@ -138,42 +140,15 @@ private:
     void append8(const T*, size_t);
     void record16(int);
     void record16(T);
+    void recordUnicodeCodePoint(UChar32);
     void append16(const LChar*, size_t);
     void append16(const UChar* characters, size_t length) { m_buffer16.append(characters, length); }
 
     ALWAYS_INLINE void shift();
     ALWAYS_INLINE bool atEnd() const;
     ALWAYS_INLINE T peek(int offset) const;
-    struct UnicodeHexValue {
-        
-        enum ValueType { ValidHex, IncompleteHex, InvalidHex };
-        
-        explicit UnicodeHexValue(int value)
-            : m_value(value)
-        {
-        }
-        explicit UnicodeHexValue(ValueType type)
-            : m_value(type == IncompleteHex ? -2 : -1)
-        {
-        }
-
-        ValueType valueType() const
-        {
-            if (m_value >= 0)
-                return ValidHex;
-            return m_value == -2 ? IncompleteHex : InvalidHex;
-        }
-        bool isValid() const { return m_value >= 0; }
-        int value() const
-        {
-            ASSERT(m_value >= 0);
-            return m_value;
-        }
-        
-    private:
-        int m_value;
-    };
-    UnicodeHexValue parseFourDigitUnicodeHex();
+
+    ParsedUnicodeEscapeValue parseUnicodeEscape();
     void shiftLineTerminator();
 
     ALWAYS_INLINE int offsetFromSourcePtr(const T* ptr) const { return ptr - m_codeStart; }
index 9610f68..309c6f7 100644 (file)
@@ -105,7 +105,7 @@ static EncodedJSValue JSC_HOST_CALL stringFromCodePoint(ExecState* exec)
 
         uint32_t codePoint = static_cast<uint32_t>(codePointAsDouble);
 
-        if (codePoint != codePointAsDouble || codePoint > 0x10FFFF)
+        if (codePoint != codePointAsDouble || codePoint > UCHAR_MAX_VALUE)
             return throwVMError(exec, createRangeError(exec, ASCIILiteral("Arguments contain a value that is out of range of code points")));
 
         if (U_IS_BMP(codePoint))
index 15e6efd..7213fc8 100644 (file)
@@ -1,3 +1,34 @@
+2015-04-29  Darin Adler  <darin@apple.com>
+
+        [ES6] Implement Unicode code point escapes
+        https://bugs.webkit.org/show_bug.cgi?id=144377
+
+        Reviewed by Antti Koivisto.
+
+        Test: js/unicode-escape-sequences.html
+
+        * css/CSSParser.cpp:
+        (WebCore::CSSParser::parseEscape): Use ICU's UCHAR_MAX_VALUE instead of writing
+        out 0x10FFFF; clearer this way. Also use our replacementCharacter instead of
+        writing out 0xFFFD.
+
+        * html/parser/HTMLEntityParser.cpp:
+        (WebCore::isAlphaNumeric): Deleted.
+        (WebCore::HTMLEntityParser::legalEntityFor): Use ICU's UCHAR_MAX_VALUE and
+        U_IS_SURROGATE instead of writing the code out. Didn't use U_IS_UNICODE_CHAR
+        because that also includes U_IS_UNICODE_NONCHAR and thus would change behavior,
+        but maye it's something we want to do in the future.
+        (WebCore::HTMLEntityParser::consumeNamedEntity): Use isASCIIAlphanumeric instead
+        of a the function in this file that does the same thing less efficiently.
+
+        * html/parser/InputStreamPreprocessor.h:
+        (WebCore::InputStreamPreprocessor::processNextInputCharacter): Use
+        replacementCharacter from CharacterNames.h instead of writing out 0xFFFd.
+
+        * xml/parser/CharacterReferenceParserInlines.h:
+        (WebCore::consumeCharacterReference): Use ICU's UCHAR_MAX_VALUE instead of
+        defining our own local highestValidCharacter constant.
+
 2015-04-29  Martin Robinson  <mrobinson@igalia.com>
 
         [CMake] [GTK] Organize and clean up unused CMake variables
index a8c332d..0aa5f2d 100644 (file)
@@ -10762,9 +10762,8 @@ unsigned CSSParser::parseEscape(CharacterType*& src)
             unicode = (unicode << 4) + toASCIIHexValue(*src++);
         } while (--length && isASCIIHexDigit(*src));
 
-        // Characters above 0x10ffff are not handled.
-        if (unicode > 0x10ffff)
-            unicode = 0xfffd;
+        if (unicode > UCHAR_MAX_VALUE)
+            unicode = replacementCharacter;
 
         // Optional space after the escape sequence.
         if (isHTMLSpace(*src))
index dfdfd6c..dbffadf 100644 (file)
@@ -32,8 +32,7 @@
 #include "HTMLEntitySearch.h"
 #include "HTMLEntityTable.h"
 #include <wtf/text/StringBuilder.h>
-
-using namespace WTF;
+#include <wtf/unicode/CharacterNames.h>
 
 namespace WebCore {
 
@@ -44,17 +43,12 @@ static const UChar windowsLatin1ExtensionArray[32] = {
     0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, // 98-9F
 };
 
-static inline bool isAlphaNumeric(UChar cc)
-{
-    return (cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'z') || (cc >= 'A' && cc <= 'Z');
-}
-
 class HTMLEntityParser {
 public:
     static UChar32 legalEntityFor(UChar32 value)
     {
-        if (value <= 0 || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF))
-            return 0xFFFD;
+        if (value <= 0 || value > UCHAR_MAX_VALUE || U_IS_SURROGATE(value))
+            return replacementCharacter;
         if ((value & ~0x1F) != 0x80)
             return value;
         return windowsLatin1ExtensionArray[value - 0x80];
@@ -104,7 +98,7 @@ public:
         }
         if (entitySearch.mostRecentMatch()->lastCharacter() == ';'
             || !additionalAllowedCharacter
-            || !(isAlphaNumeric(cc) || cc == '=')) {
+            || !(isASCIIAlphanumeric(cc) || cc == '=')) {
             decodedEntity.append(entitySearch.mostRecentMatch()->firstValue);
             if (entitySearch.mostRecentMatch()->secondValue)
                 decodedEntity.append(entitySearch.mostRecentMatch()->secondValue);
index 6290c15..8e10070 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "SegmentedString.h"
 #include <wtf/Noncopyable.h>
+#include <wtf/unicode/CharacterNames.h>
 
 namespace WebCore {
 
@@ -115,7 +116,7 @@ private:
                     m_nextInputCharacter = source.currentChar();
                     goto ProcessAgain;
                 }
-                m_nextInputCharacter = 0xFFFD;
+                m_nextInputCharacter = replacementCharacter;
             }
         }
         return true;
index 23e0c02..f0f4ff7 100644 (file)
@@ -54,7 +54,6 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
     } state = Initial;
     UChar32 result = 0;
     bool overflow = false;
-    const UChar32 highestValidCharacter = 0x10FFFF;
     StringBuilder consumedCharacters;
     
     while (!source.isEmpty()) {
@@ -107,7 +106,7 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
         Hex:
             if (isASCIIHexDigit(character)) {
                 result = result * 16 + toASCIIHexValue(character);
-                if (result > highestValidCharacter)
+                if (result > UCHAR_MAX_VALUE)
                     overflow = true;
                 break;
             }
@@ -126,7 +125,7 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
         Decimal:
             if (isASCIIDigit(character)) {
                 result = result * 10 + character - '0';
-                if (result > highestValidCharacter)
+                if (result > UCHAR_MAX_VALUE)
                     overflow = true;
                 break;
             }