Lift template escape sequence restrictions in tagged templates
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 28 Jan 2017 03:09:12 +0000 (03:09 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 28 Jan 2017 03:09:12 +0000 (03:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=166871

Reviewed by Saam Barati.

JSTests:

Update the error messages and add new tests.

* ChakraCore/test/es6/unicode_6_identifier_Blue524737.baseline-jsc:
* stress/lift-template-literal.js: Added.
(dump):
(testTag.return.tag):
(testTag):
* stress/template-literal-syntax.js:

Source/JavaScriptCore:

This patch implements stage 3 Lifting Template Literal Restriction[1].
Prior to this patch, template literal becomes syntax error if it contains
invalid escape sequences. But it is too restricted; Template literal
can have cooked and raw representations and only cooked representation
can escape sequences. So even if invalid escape sequences are included,
the raw representation can be valid.

Lifting Template Literal Restriction relaxes the above restriction.
When invalid escape sequence is included, if target template literals
are used as tagged templates, we make the result of the template including
the invalid escape sequence `undefined` instead of making it SyntaxError
immediately. It allows us to accept the templates including invalid
escape sequences in the raw representations in tagged templates.

On the other hand, the raw representation is only used in tagged templates.
So if invalid escape sequences are included in the usual template literals,
we just make it SyntaxError as before.

[1]: https://github.com/tc39/proposal-template-literal-revision

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitGetTemplateObject):
* bytecompiler/NodesCodegen.cpp:
(JSC::TemplateStringNode::emitBytecode):
(JSC::TemplateLiteralNode::emitBytecode):
* parser/ASTBuilder.h:
(JSC::ASTBuilder::createTemplateString):
* parser/Lexer.cpp:
(JSC::Lexer<CharacterType>::parseUnicodeEscape):
(JSC::Lexer<T>::parseTemplateLiteral):
(JSC::Lexer<T>::lex):
(JSC::Lexer<T>::scanTemplateString):
(JSC::Lexer<T>::scanTrailingTemplateString): Deleted.
* parser/Lexer.h:
* parser/NodeConstructors.h:
(JSC::TemplateStringNode::TemplateStringNode):
* parser/Nodes.h:
(JSC::TemplateStringNode::cooked):
(JSC::TemplateStringNode::raw):
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseAssignmentElement):
(JSC::Parser<LexerType>::parseTemplateString):
(JSC::Parser<LexerType>::parseTemplateLiteral):
(JSC::Parser<LexerType>::parsePrimaryExpression):
(JSC::Parser<LexerType>::parseMemberExpression):
* parser/ParserTokens.h:
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::createTemplateString):
* runtime/TemplateRegistry.cpp:
(JSC::TemplateRegistry::getTemplateObject):
* runtime/TemplateRegistryKey.h:
(JSC::TemplateRegistryKey::cookedStrings):
(JSC::TemplateRegistryKey::create):
(JSC::TemplateRegistryKey::TemplateRegistryKey):
* runtime/TemplateRegistryKeyTable.cpp:
(JSC::TemplateRegistryKeyTable::createKey):
* runtime/TemplateRegistryKeyTable.h:

LayoutTests:

Update the error messages.

* inspector/runtime/parse-expected.txt:
* js/unicode-escape-sequences-expected.txt:

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

22 files changed:
JSTests/ChakraCore/test/es6/unicode_6_identifier_Blue524737.baseline-jsc
JSTests/ChangeLog
JSTests/stress/lift-template-literal.js [new file with mode: 0644]
JSTests/stress/template-literal-syntax.js
LayoutTests/ChangeLog
LayoutTests/inspector/runtime/parse-expected.txt
LayoutTests/js/unicode-escape-sequences-expected.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/Lexer.cpp
Source/JavaScriptCore/parser/Lexer.h
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/ParserTokens.h
Source/JavaScriptCore/parser/SyntaxChecker.h
Source/JavaScriptCore/runtime/TemplateRegistry.cpp
Source/JavaScriptCore/runtime/TemplateRegistryKey.h
Source/JavaScriptCore/runtime/TemplateRegistryKeyTable.cpp
Source/JavaScriptCore/runtime/TemplateRegistryKeyTable.h

index 87537da..99d6d8b 100644 (file)
@@ -1,2 +1,2 @@
-Exception: SyntaxError: Invalid unicode escape in identifier: '\u{13407'
+Exception: SyntaxError: Invalid unicode escape in identifier: '\u{134071'
 at unicode_6_identifier_Blue524737.js:6
index 3d08386..d10e5cb 100644 (file)
@@ -1,3 +1,19 @@
+2017-01-27  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Lift template escape sequence restrictions in tagged templates
+        https://bugs.webkit.org/show_bug.cgi?id=166871
+
+        Reviewed by Saam Barati.
+
+        Update the error messages and add new tests.
+
+        * ChakraCore/test/es6/unicode_6_identifier_Blue524737.baseline-jsc:
+        * stress/lift-template-literal.js: Added.
+        (dump):
+        (testTag.return.tag):
+        (testTag):
+        * stress/template-literal-syntax.js:
+
 2017-01-26  Mark Lam  <mark.lam@apple.com>
 
         Fix missing exception check in genericTypedArrayViewProtoFuncSet().
diff --git a/JSTests/stress/lift-template-literal.js b/JSTests/stress/lift-template-literal.js
new file mode 100644 (file)
index 0000000..12f8f5e
--- /dev/null
@@ -0,0 +1,69 @@
+function dump(callSite)
+{
+    return JSON.stringify({ cooked: callSite, raw: callSite.raw });
+}
+
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+shouldBe(dump`\newcommand{\fun}{\textbf{Fun!}}`, `{"cooked":["\\newcommand{\\fun}{\\textbf{Fun!}}"],"raw":["\\\\newcommand{\\\\fun}{\\\\textbf{Fun!}}"]}`);
+shouldBe(dump`\newcommand{\unicode}{\textbf{Unicode!}}`, `{"cooked":[null],"raw":["\\\\newcommand{\\\\unicode}{\\\\textbf{Unicode!}}"]}`);
+shouldBe(dump`\newcommand{\xerxes}{\textbf{King!}}`, `{"cooked":[null],"raw":["\\\\newcommand{\\\\xerxes}{\\\\textbf{King!}}"]}`);
+shouldBe(dump`Breve over the h goes \u{h}ere`, `{"cooked":[null],"raw":["Breve over the h goes \\\\u{h}ere"]}`);
+
+function testTag(expected) {
+    return function tag(callSite) {
+        shouldBe(callSite.length, expected.cooked.length);
+        shouldBe(callSite.raw.length, expected.raw.length);
+        expected.cooked.forEach((value, index) => shouldBe(callSite[index], value));
+        expected.raw.forEach((value, index) => shouldBe(callSite.raw[index], value));
+    }
+}
+
+testTag({
+    cooked: [ undefined ],
+    raw: [ "\\unicode and \\u{55}" ],
+})`\unicode and \u{55}`;
+
+testTag({
+    cooked: [ undefined, "test" ],
+    raw: [ "\\unicode and \\u{55}", "test" ],
+})`\unicode and \u{55}${42}test`;
+
+testTag({
+    cooked: [ undefined, undefined, "Cocoa" ],
+    raw: [ "\\unicode and \\u{55}", "\\uhello", "Cocoa" ],
+})`\unicode and \u{55}${42}\uhello${42}Cocoa`;
+
+testTag({
+    cooked: [ "Cocoa", undefined, undefined, "Cocoa" ],
+    raw: [ "Cocoa", "\\unicode and \\u{55}", "\\uhello", "Cocoa" ],
+})`Cocoa${42}\unicode and \u{55}${42}\uhello${42}Cocoa`;
+
+testTag({
+    cooked: [ "Cocoa", undefined, undefined, "Cocoa" ],
+    raw: [ "Cocoa", "\\unicode and \\u{55}", "\\uhello", "Cocoa" ],
+})`Cocoa${42}\unicode and \u{55}${42}\uhello${42}Cocoa`;
+
+testTag({
+    cooked: [ undefined, undefined, undefined ],
+    raw: [ "\\00", "\\01", "\\1" ]
+})`\00${42}\01${42}\1`;
+
+testTag({
+    cooked: [ undefined, undefined ],
+    raw: [ "\\xo", "\\x0o" ]
+})`\xo${42}\x0o`;
+
+testTag({
+    cooked: [ undefined, undefined, undefined, undefined ],
+    raw: [ "\\uo", "\\u0o", "\\u00o", "\\u000o" ]
+})`\uo${42}\u0o${42}\u00o${42}\u000o`;
+
+testTag({
+    cooked: [ undefined, undefined, undefined ],
+    raw: [ "\\u{o", "\\u{0o", "\\u{110000o" ]
+})`\u{o${42}\u{0o${42}\u{110000o`;
index b444280..346c8dd 100644 (file)
@@ -46,6 +46,19 @@ testSyntax("(`Hello`) + 42");
 testSyntax("`\\\n`");
 testSyntax("`\\\r\n`");
 testSyntax("`\\\r`");
+testSyntax("Hello`bad escape sequence: \\unicode`");
+testSyntax("Hello`\\00`");
+testSyntax("Hello`\\01`");
+testSyntax("Hello`\\1`");
+testSyntax("Hello`\\xo`");
+testSyntax("Hello`\\x0o`");
+testSyntax("Hello`\\uo`");
+testSyntax("Hello`\\u0o`");
+testSyntax("Hello`\\u00o`");
+testSyntax("Hello`\\u000o`");
+testSyntax("Hello`\\u{o`");
+testSyntax("Hello`\\u{0o`");
+testSyntax("Hello`\\u{110000o`");
 
 testSyntaxError("`Hello", "SyntaxError: Unexpected EOF");
 testSyntaxError("`Hello${expr}", "SyntaxError: Unexpected EOF");
@@ -81,3 +94,18 @@ testSyntaxError("`\\u20`", "SyntaxError: \\u can only be followed by a Unicode c
 testSyntaxError("`${expr}\\u20`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
 testSyntaxError("`\\u202`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
 testSyntaxError("`${expr}\\u202`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+
+testSyntaxError("`bad escape sequence: \\unicode`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+
+testSyntaxError("`\\00`", "SyntaxError: The only valid numeric escape in strict mode is '\\0'");
+testSyntaxError("`\\01`", "SyntaxError: The only valid numeric escape in strict mode is '\\0'");
+testSyntaxError("`\\1`", "SyntaxError: The only valid numeric escape in strict mode is '\\0'");
+testSyntaxError("`\\xo`", "SyntaxError: \\x can only be followed by a hex character sequence");
+testSyntaxError("`\\x0o`", "SyntaxError: \\x can only be followed by a hex character sequence");
+testSyntaxError("`\\uo`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u0o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u00o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u000o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u{o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u{0o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
+testSyntaxError("`\\u{110000o`", "SyntaxError: \\u can only be followed by a Unicode character sequence");
index aabaf79..5913164 100644 (file)
@@ -1,5 +1,17 @@
 2017-01-27  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        Lift template escape sequence restrictions in tagged templates
+        https://bugs.webkit.org/show_bug.cgi?id=166871
+
+        Reviewed by Saam Barati.
+
+        Update the error messages.
+
+        * inspector/runtime/parse-expected.txt:
+        * js/unicode-escape-sequences-expected.txt:
+
+2017-01-27  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         setTimeout / setInterval's string execution should inherit SourceOrigin correctly
         https://bugs.webkit.org/show_bug.cgi?id=167097
 
index fa6c83f..0c57d81 100644 (file)
@@ -67,9 +67,9 @@ Range: {"startOffset":0,"endOffset":1}
 
 PASS: Should be SyntaxErrorType UnterminatedLiteral.
 Source: var \u007
-            ^~
-Error Message: Incomplete unicode escape in identifier: '\u'
-Range: {"startOffset":4,"endOffset":6}
+            ^~~~~
+Error Message: Incomplete unicode escape in identifier: '\u007'
+Range: {"startOffset":4,"endOffset":9}
 
 
 -- Running test case: SyntaxErrorType.Recoverable
index 828f1a5..5cb9aaf 100644 (file)
@@ -58,9 +58,9 @@ PASS codeUnits(function \u{(){}.name) threw exception SyntaxError: Invalid unico
 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 \u{110000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{110000'.
+PASS codeUnits(function \u{1000000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{1000000'.
+PASS codeUnits(function \u{100000000000000000000000}(){}.name) threw exception SyntaxError: Invalid unicode escape in identifier: '\u{100000000000000000000000'.
 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"
@@ -87,9 +87,9 @@ PASS codeUnits(function x\u{(){}.name.substring(1)) threw exception SyntaxError:
 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 codeUnits(function x\u{110000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{110000'.
+PASS codeUnits(function x\u{1000000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{1000000'.
+PASS codeUnits(function x\u{100000000000000000000000}(){}.name.substring(1)) threw exception SyntaxError: Invalid unicode escape in identifier: 'x\u{100000000000000000000000'.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index a1249d5..381b013 100644 (file)
@@ -1,3 +1,68 @@
+2017-01-27  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        Lift template escape sequence restrictions in tagged templates
+        https://bugs.webkit.org/show_bug.cgi?id=166871
+
+        Reviewed by Saam Barati.
+
+        This patch implements stage 3 Lifting Template Literal Restriction[1].
+        Prior to this patch, template literal becomes syntax error if it contains
+        invalid escape sequences. But it is too restricted; Template literal
+        can have cooked and raw representations and only cooked representation
+        can escape sequences. So even if invalid escape sequences are included,
+        the raw representation can be valid.
+
+        Lifting Template Literal Restriction relaxes the above restriction.
+        When invalid escape sequence is included, if target template literals
+        are used as tagged templates, we make the result of the template including
+        the invalid escape sequence `undefined` instead of making it SyntaxError
+        immediately. It allows us to accept the templates including invalid
+        escape sequences in the raw representations in tagged templates.
+
+        On the other hand, the raw representation is only used in tagged templates.
+        So if invalid escape sequences are included in the usual template literals,
+        we just make it SyntaxError as before.
+
+        [1]: https://github.com/tc39/proposal-template-literal-revision
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitGetTemplateObject):
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::TemplateStringNode::emitBytecode):
+        (JSC::TemplateLiteralNode::emitBytecode):
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::createTemplateString):
+        * parser/Lexer.cpp:
+        (JSC::Lexer<CharacterType>::parseUnicodeEscape):
+        (JSC::Lexer<T>::parseTemplateLiteral):
+        (JSC::Lexer<T>::lex):
+        (JSC::Lexer<T>::scanTemplateString):
+        (JSC::Lexer<T>::scanTrailingTemplateString): Deleted.
+        * parser/Lexer.h:
+        * parser/NodeConstructors.h:
+        (JSC::TemplateStringNode::TemplateStringNode):
+        * parser/Nodes.h:
+        (JSC::TemplateStringNode::cooked):
+        (JSC::TemplateStringNode::raw):
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseAssignmentElement):
+        (JSC::Parser<LexerType>::parseTemplateString):
+        (JSC::Parser<LexerType>::parseTemplateLiteral):
+        (JSC::Parser<LexerType>::parsePrimaryExpression):
+        (JSC::Parser<LexerType>::parseMemberExpression):
+        * parser/ParserTokens.h:
+        * parser/SyntaxChecker.h:
+        (JSC::SyntaxChecker::createTemplateString):
+        * runtime/TemplateRegistry.cpp:
+        (JSC::TemplateRegistry::getTemplateObject):
+        * runtime/TemplateRegistryKey.h:
+        (JSC::TemplateRegistryKey::cookedStrings):
+        (JSC::TemplateRegistryKey::create):
+        (JSC::TemplateRegistryKey::TemplateRegistryKey):
+        * runtime/TemplateRegistryKeyTable.cpp:
+        (JSC::TemplateRegistryKeyTable::createKey):
+        * runtime/TemplateRegistryKeyTable.h:
+
 2017-01-27  Saam Barati  <sbarati@apple.com>
 
         Make the CLI for the sampling profiler better for inlined call site indices
index 30a8b6d..945fce9 100644 (file)
@@ -4253,17 +4253,22 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
 RegisterID* BytecodeGenerator::emitGetTemplateObject(RegisterID* dst, TaggedTemplateNode* taggedTemplate)
 {
     TemplateRegistryKey::StringVector rawStrings;
-    TemplateRegistryKey::StringVector cookedStrings;
+    TemplateRegistryKey::OptionalStringVector cookedStrings;
 
     TemplateStringListNode* templateString = taggedTemplate->templateLiteral()->templateStrings();
     for (; templateString; templateString = templateString->next()) {
-        rawStrings.append(templateString->value()->raw().impl());
-        cookedStrings.append(templateString->value()->cooked().impl());
+        auto* string = templateString->value();
+        ASSERT(string->raw());
+        rawStrings.append(string->raw()->impl());
+        if (!string->cooked())
+            cookedStrings.append(std::nullopt);
+        else
+            cookedStrings.append(string->cooked()->impl());
     }
 
     RefPtr<RegisterID> getTemplateObject = emitGetGlobalPrivate(newTemporary(), propertyNames().builtinNames().getTemplateObjectPrivateName());
     CallArguments arguments(*this, nullptr);
-    emitLoad(arguments.thisRegister(), JSValue(addTemplateRegistryKeyConstant(m_vm->templateRegistryKeyTable().createKey(rawStrings, cookedStrings))));
+    emitLoad(arguments.thisRegister(), JSValue(addTemplateRegistryKeyConstant(m_vm->templateRegistryKeyTable().createKey(WTFMove(rawStrings), WTFMove(cookedStrings)))));
     return emitCall(dst, getTemplateObject.get(), NoExpectedFunction, arguments, taggedTemplate->divot(), taggedTemplate->divotStart(), taggedTemplate->divotEnd(), DebuggableCall::No);
 }
 
index 521551c..1b17a8b 100644 (file)
@@ -246,7 +246,8 @@ RegisterID* TemplateStringNode::emitBytecode(BytecodeGenerator& generator, Regis
 {
     if (dst == generator.ignoredResult())
         return nullptr;
-    return generator.emitLoad(dst, JSValue(generator.addStringConstant(cooked())));
+    ASSERT(cooked());
+    return generator.emitLoad(dst, JSValue(generator.addStringConstant(*cooked())));
 }
 
 // ------------------------------ TemplateLiteralNode -----------------------------------
@@ -265,7 +266,8 @@ RegisterID* TemplateLiteralNode::emitBytecode(BytecodeGenerator& generator, Regi
     TemplateExpressionListNode* templateExpression = m_templateExpressions;
     for (; templateExpression; templateExpression = templateExpression->next(), templateString = templateString->next()) {
         // Evaluate TemplateString.
-        if (!templateString->value()->cooked().isEmpty()) {
+        ASSERT(templateString->value()->cooked());
+        if (!templateString->value()->cooked()->isEmpty()) {
             temporaryRegisters.append(generator.newTemporary());
             generator.emitNode(temporaryRegisters.last().get(), templateString->value());
         }
@@ -277,7 +279,8 @@ RegisterID* TemplateLiteralNode::emitBytecode(BytecodeGenerator& generator, Regi
     }
 
     // Evaluate tail TemplateString.
-    if (!templateString->value()->cooked().isEmpty()) {
+    ASSERT(templateString->value()->cooked());
+    if (!templateString->value()->cooked()->isEmpty()) {
         temporaryRegisters.append(generator.newTemporary());
         generator.emitNode(temporaryRegisters.last().get(), templateString->value());
     }
index 4e74b5b..801a364 100644 (file)
@@ -275,7 +275,7 @@ public:
         return node;
     }
 
-    TemplateStringNode* createTemplateString(const JSTokenLocation& location, const Identifier& cooked, const Identifier& raw)
+    TemplateStringNode* createTemplateString(const JSTokenLocation& location, const Identifier* cooked, const Identifier* raw)
     {
         return new (m_parserArena) TemplateStringNode(location, cooked, raw);
     }
index d990f7b..f1e1e0f 100644 (file)
@@ -633,7 +633,8 @@ private:
     UChar32 m_value;
 };
 
-template<typename CharacterType> ParsedUnicodeEscapeValue Lexer<CharacterType>::parseUnicodeEscape()
+template<typename CharacterType>
+ParsedUnicodeEscapeValue Lexer<CharacterType>::parseUnicodeEscape()
 {
     if (m_current == '{') {
         shift();
@@ -642,8 +643,26 @@ template<typename CharacterType> ParsedUnicodeEscapeValue Lexer<CharacterType>::
             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;
+            if (codePoint > UCHAR_MAX_VALUE) {
+                // For raw template literal syntax, we consume `NotEscapeSequence`.
+                // Here, we consume NotCodePoint's HexDigits.
+                //
+                // NotEscapeSequence ::
+                //     u { [lookahread not one of HexDigit]
+                //     u { NotCodePoint
+                //     u { CodePoint [lookahead != }]
+                //
+                // NotCodePoint ::
+                //     HexDigits but not if MV of HexDigits <= 0x10FFFF
+                //
+                // CodePoint ::
+                //     HexDigits but not if MV of HexDigits > 0x10FFFF
+                shift();
+                while (isASCIIHexDigit(m_current))
+                    shift();
+
+                return atEnd() ? ParsedUnicodeEscapeValue::Incomplete : ParsedUnicodeEscapeValue::Invalid;
+            }
             shift();
         } while (m_current != '}');
         shift();
@@ -653,8 +672,22 @@ template<typename CharacterType> ParsedUnicodeEscapeValue Lexer<CharacterType>::
     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;
+    if (UNLIKELY(!isASCIIHexDigit(m_current) || !isASCIIHexDigit(character2) || !isASCIIHexDigit(character3) || !isASCIIHexDigit(character4))) {
+        auto result = (m_code + 4) >= m_codeEnd ? ParsedUnicodeEscapeValue::Incomplete : ParsedUnicodeEscapeValue::Invalid;
+
+        // For raw template literal syntax, we consume `NotEscapeSequence`.
+        //
+        // NotEscapeSequence ::
+        //     u [lookahead not one of HexDigit][lookahead != {]
+        //     u HexDigit [lookahead not one of HexDigit]
+        //     u HexDigit HexDigit [lookahead not one of HexDigit]
+        //     u HexDigit HexDigit HexDigit [lookahead not one of HexDigit]
+        while (isASCIIHexDigit(m_current))
+            shift();
+
+        return result;
+    }
+
     auto result = convertUnicode(m_current, character2, character3, character4);
     shift();
     shift();
@@ -1181,26 +1214,37 @@ template <bool shouldBuildStrings> ALWAYS_INLINE typename Lexer<T>::StringParseR
 }
 
 template <typename T>
-template <bool shouldBuildStrings> ALWAYS_INLINE auto Lexer<T>::parseComplexEscape(EscapeParseMode escapeParseMode, bool strictMode, T stringQuoteCharacter) -> StringParseResult
+template <bool shouldBuildStrings, LexerEscapeParseMode escapeParseMode> ALWAYS_INLINE auto Lexer<T>::parseComplexEscape(bool strictMode, T stringQuoteCharacter) -> StringParseResult
 {
     if (m_current == 'x') {
         shift();
         if (!isASCIIHexDigit(m_current) || !isASCIIHexDigit(peek(1))) {
+            // For raw template literal syntax, we consume `NotEscapeSequence`.
+            //
+            // NotEscapeSequence ::
+            //     x [lookahread not one of HexDigit]
+            //     x HexDigit [lookahread not one of HexDigit]
+            if (isASCIIHexDigit(m_current))
+                shift();
+            ASSERT(!isASCIIHexDigit(m_current));
+
             m_lexErrorMessage = ASCIILiteral("\\x can only be followed by a hex character sequence");
-            return StringCannotBeParsed;
+            return atEnd() ? StringUnterminated : StringCannotBeParsed;
         }
+
         T prev = m_current;
         shift();
         if (shouldBuildStrings)
             record16(convertHex(prev, m_current));
         shift();
+
         return StringParsedSuccessfully;
     }
 
     if (m_current == 'u') {
         shift();
 
-        if (escapeParseMode == EscapeParseMode::String && m_current == stringQuoteCharacter) {
+        if (escapeParseMode == LexerEscapeParseMode::String && m_current == stringQuoteCharacter) {
             if (shouldBuildStrings)
                 record16('u');
             return StringParsedSuccessfully;
@@ -1214,7 +1258,7 @@ template <bool shouldBuildStrings> ALWAYS_INLINE auto Lexer<T>::parseComplexEsca
         }
 
         m_lexErrorMessage = ASCIILiteral("\\u can only be followed by a Unicode character sequence");
-        return character.isIncomplete() ? StringUnterminated : StringCannotBeParsed;
+        return atEnd() ? StringUnterminated : StringCannotBeParsed;
     }
 
     if (strictMode) {
@@ -1223,8 +1267,16 @@ template <bool shouldBuildStrings> ALWAYS_INLINE auto Lexer<T>::parseComplexEsca
             int character1 = m_current;
             shift();
             if (character1 != '0' || isASCIIDigit(m_current)) {
+                // For raw template literal syntax, we consume `NotEscapeSequence`.
+                //
+                // NotEscapeSequence ::
+                //     0 DecimalDigit
+                //     DecimalDigit but not 0
+                if (character1 == '0')
+                    shift();
+
                 m_lexErrorMessage = ASCIILiteral("The only valid numeric escape in strict mode is '\\0'");
-                return StringCannotBeParsed;
+                return atEnd() ? StringUnterminated : StringCannotBeParsed;
             }
             if (shouldBuildStrings)
                 record16(0);
@@ -1290,7 +1342,7 @@ template <bool shouldBuildStrings> auto Lexer<T>::parseStringSlowCase(JSTokenDat
             } else if (UNLIKELY(isLineTerminator(m_current)))
                 shiftLineTerminator();
             else {
-                StringParseResult result = parseComplexEscape<shouldBuildStrings>(EscapeParseMode::String, strictMode, stringQuoteCharacter);
+                StringParseResult result = parseComplexEscape<shouldBuildStrings, LexerEscapeParseMode::String>(strictMode, stringQuoteCharacter);
                 if (result != StringParsedSuccessfully)
                     return result;
             }
@@ -1372,8 +1424,9 @@ private:
 };
 
 template <typename T>
-template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>::parseTemplateLiteral(JSTokenData* tokenData, RawStringsBuildMode rawStringsBuildMode)
+typename Lexer<T>::StringParseResult Lexer<T>::parseTemplateLiteral(JSTokenData* tokenData, RawStringsBuildMode rawStringsBuildMode)
 {
+    bool parseCookedFailed = false;
     const T* stringStart = currentSourcePtr();
     const T* rawStringStart = currentSourcePtr();
 
@@ -1382,7 +1435,7 @@ template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>
     while (m_current != '`') {
         if (UNLIKELY(m_current == '\\')) {
             lineNumberAdder.clear();
-            if (stringStart != currentSourcePtr() && shouldBuildStrings)
+            if (stringStart != currentSourcePtr())
                 append16(stringStart, currentSourcePtr() - stringStart);
             shift();
 
@@ -1390,19 +1443,16 @@ template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>
 
             // Most common escape sequences first.
             if (escape) {
-                if (shouldBuildStrings)
-                    record16(escape);
+                record16(escape);
                 shift();
             } else if (UNLIKELY(isLineTerminator(m_current))) {
                 // Normalize <CR>, <CR><LF> to <LF>.
                 if (m_current == '\r') {
-                    if (shouldBuildStrings) {
-                        ASSERT_WITH_MESSAGE(rawStringStart != currentSourcePtr(), "We should have at least shifted the escape.");
+                    ASSERT_WITH_MESSAGE(rawStringStart != currentSourcePtr(), "We should have at least shifted the escape.");
 
-                        if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings) {
-                            m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
-                            m_bufferForRawTemplateString16.append('\n');
-                        }
+                    if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings) {
+                        m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
+                        m_bufferForRawTemplateString16.append('\n');
                     }
 
                     lineNumberAdder.add(m_current);
@@ -1419,9 +1469,13 @@ template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>
                 }
             } else {
                 bool strictMode = true;
-                StringParseResult result = parseComplexEscape<shouldBuildStrings>(EscapeParseMode::Template, strictMode, '`');
-                if (result != StringParsedSuccessfully)
-                    return result;
+                StringParseResult result = parseComplexEscape<true, LexerEscapeParseMode::Template>(strictMode, '`');
+                if (result != StringParsedSuccessfully) {
+                    if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings && result == StringCannotBeParsed)
+                        parseCookedFailed = true;
+                    else
+                        return result;
+                }
             }
 
             stringStart = currentSourcePtr();
@@ -1445,16 +1499,14 @@ template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>
             if (isLineTerminator(m_current)) {
                 if (m_current == '\r') {
                     // Normalize <CR>, <CR><LF> to <LF>.
-                    if (shouldBuildStrings) {
-                        if (stringStart != currentSourcePtr())
-                            append16(stringStart, currentSourcePtr() - stringStart);
-                        if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
-                            m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
-
-                        record16('\n');
-                        if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
-                            m_bufferForRawTemplateString16.append('\n');
-                    }
+                    if (stringStart != currentSourcePtr())
+                        append16(stringStart, currentSourcePtr() - stringStart);
+                    if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
+                        m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
+
+                    record16('\n');
+                    if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
+                        m_bufferForRawTemplateString16.append('\n');
                     lineNumberAdder.add(m_current);
                     shift();
                     if (m_current == '\n') {
@@ -1478,24 +1530,22 @@ template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>
 
     bool isTail = m_current == '`';
 
-    if (shouldBuildStrings) {
-        if (currentSourcePtr() != stringStart)
-            append16(stringStart, currentSourcePtr() - stringStart);
-        if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
-            m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
-    }
+    if (currentSourcePtr() != stringStart)
+        append16(stringStart, currentSourcePtr() - stringStart);
+    if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
+        m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
 
-    if (shouldBuildStrings) {
+    if (!parseCookedFailed)
         tokenData->cooked = makeIdentifier(m_buffer16.data(), m_buffer16.size());
-        // Line terminator normalization (e.g. <CR> => <LF>) should be applied to both the raw and cooked representations.
-        if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
-            tokenData->raw = makeIdentifier(m_bufferForRawTemplateString16.data(), m_bufferForRawTemplateString16.size());
-        else
-            tokenData->raw = makeEmptyIdentifier();
-    } else {
-        tokenData->cooked = makeEmptyIdentifier();
-        tokenData->raw = makeEmptyIdentifier();
-    }
+    else
+        tokenData->cooked = nullptr;
+
+    // Line terminator normalization (e.g. <CR> => <LF>) should be applied to both the raw and cooked representations.
+    if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
+        tokenData->raw = makeIdentifier(m_bufferForRawTemplateString16.data(), m_bufferForRawTemplateString16.size());
+    else
+        tokenData->raw = nullptr;
+
     tokenData->isTail = isTail;
 
     m_buffer16.shrink(0);
@@ -2082,6 +2132,10 @@ start:
         shift();
         token = SEMICOLON;
         break;
+    case CharacterBackQuote:
+        shift();
+        token = BACKQUOTE;
+        break;
     case CharacterOpenBrace:
         tokenData->line = lineNumber();
         tokenData->offset = currentOffset();
@@ -2234,22 +2288,6 @@ inNumberAfterDecimalPoint:
         token = STRING;
         break;
         }
-    case CharacterBackQuote: {
-        // Skip backquote.
-        shift();
-        StringParseResult result = StringCannotBeParsed;
-        if (lexerFlags & LexerFlagsDontBuildStrings)
-            result = parseTemplateLiteral<false>(tokenData, RawStringsBuildMode::BuildRawStrings);
-        else
-            result = parseTemplateLiteral<true>(tokenData, RawStringsBuildMode::BuildRawStrings);
-
-        if (UNLIKELY(result != StringParsedSuccessfully)) {
-            token = result == StringUnterminated ? UNTERMINATED_TEMPLATE_LITERAL_ERRORTOK : INVALID_TEMPLATE_LITERAL_ERRORTOK;
-            goto returnError;
-        }
-        token = TEMPLATE;
-        break;
-        }
     case CharacterIdentifierStart:
         ASSERT(isIdentStart(m_current));
         FALLTHROUGH;
@@ -2420,15 +2458,15 @@ JSTokenType Lexer<T>::scanRegExp(JSToken* tokenRecord, UChar patternPrefix)
 }
 
 template <typename T>
-JSTokenType Lexer<T>::scanTrailingTemplateString(JSToken* tokenRecord, RawStringsBuildMode rawStringsBuildMode)
+JSTokenType Lexer<T>::scanTemplateString(JSToken* tokenRecord, RawStringsBuildMode rawStringsBuildMode)
 {
     JSTokenData* tokenData = &tokenRecord->m_data;
     ASSERT(!m_error);
     ASSERT(m_buffer16.isEmpty());
 
-    // Leading closing brace } is already shifted in the previous token scan.
+    // Leading backquote ` (for template head) or closing brace } (for template trailing) are already shifted in the previous token scan.
     // So in this re-scan phase, shift() is not needed here.
-    StringParseResult result = parseTemplateLiteral<true>(tokenData, rawStringsBuildMode);
+    StringParseResult result = parseTemplateLiteral(tokenData, rawStringsBuildMode);
     JSTokenType token = ERRORTOK;
     if (UNLIKELY(result != StringParsedSuccessfully)) {
         token = result == StringUnterminated ? UNTERMINATED_TEMPLATE_LITERAL_ERRORTOK : INVALID_TEMPLATE_LITERAL_ERRORTOK;
index e408cc6..7283fe7 100644 (file)
@@ -37,6 +37,8 @@ enum LexerFlags {
     LexexFlagsDontBuildKeywords = 4
 };
 
+enum class LexerEscapeParseMode { Template, String };
+
 struct ParsedUnicodeEscapeValue;
 
 bool isLexerKeyword(const Identifier&);
@@ -77,7 +79,7 @@ public:
     bool prevTerminator() const { return m_terminator; }
     JSTokenType scanRegExp(JSToken*, UChar patternPrefix = 0);
     enum class RawStringsBuildMode { BuildRawStrings, DontBuildRawStrings };
-    JSTokenType scanTrailingTemplateString(JSToken*, RawStringsBuildMode);
+    JSTokenType scanTemplateString(JSToken*, RawStringsBuildMode);
 
     // Functions for use after parsing.
     bool sawError() const { return m_error; }
@@ -170,9 +172,8 @@ private:
     template <bool shouldBuildStrings> ALWAYS_INLINE StringParseResult parseString(JSTokenData*, bool strictMode);
     template <bool shouldBuildStrings> NEVER_INLINE StringParseResult parseStringSlowCase(JSTokenData*, bool strictMode);
 
-    enum class EscapeParseMode { Template, String };
-    template <bool shouldBuildStrings> ALWAYS_INLINE StringParseResult parseComplexEscape(EscapeParseMode, bool strictMode, T stringQuoteCharacter);
-    template <bool shouldBuildStrings> ALWAYS_INLINE StringParseResult parseTemplateLiteral(JSTokenData*, RawStringsBuildMode);
+    template <bool shouldBuildStrings, LexerEscapeParseMode escapeParseMode> ALWAYS_INLINE StringParseResult parseComplexEscape(bool strictMode, T stringQuoteCharacter);
+    ALWAYS_INLINE StringParseResult parseTemplateLiteral(JSTokenData*, RawStringsBuildMode);
     ALWAYS_INLINE void parseHex(double& returnValue);
     ALWAYS_INLINE bool parseBinary(double& returnValue);
     ALWAYS_INLINE bool parseOctal(double& returnValue);
index 7d2e2e9..da71e8a 100644 (file)
@@ -111,7 +111,7 @@ namespace JSC {
         previous->m_next = this;
     }
 
-    inline TemplateStringNode::TemplateStringNode(const JSTokenLocation& location, const Identifier& cooked, const Identifier& raw)
+    inline TemplateStringNode::TemplateStringNode(const JSTokenLocation& location, const Identifier* cooked, const Identifier* raw)
         : ExpressionNode(location)
         , m_cooked(cooked)
         , m_raw(raw)
index 7324cd4..c8b5922 100644 (file)
@@ -490,16 +490,16 @@ namespace JSC {
 
     class TemplateStringNode : public ExpressionNode {
     public:
-        TemplateStringNode(const JSTokenLocation&, const Identifier& cooked, const Identifier& raw);
+        TemplateStringNode(const JSTokenLocation&, const Identifier* cooked, const Identifier* raw);
 
-        const Identifier& cooked() { return m_cooked; }
-        const Identifier& raw() { return m_raw; }
+        const Identifier* cooked() { return m_cooked; }
+        const Identifier* raw() { return m_raw; }
 
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
-        const Identifier& m_cooked;
-        const Identifier& m_raw;
+        const Identifier* m_cooked;
+        const Identifier* m_raw;
     };
 
     class TemplateStringListNode : public ParserArenaFreeable {
index f4e307b..1b75858 100644 (file)
@@ -904,7 +904,7 @@ template <class TreeBuilder> TreeDestructuringPattern Parser<LexerType>::parseAs
     if (match(OPENBRACE) || match(OPENBRACKET)) {
         SavePoint savePoint = createSavePoint();
         assignmentTarget = parseDestructuringPattern(context, kind, exportType, duplicateIdentifier, hasDestructuringPattern, bindingContext, depth);
-        if (assignmentTarget && !match(DOT) && !match(OPENBRACKET) && !match(OPENPAREN) && !match(TEMPLATE))
+        if (assignmentTarget && !match(DOT) && !match(OPENBRACKET) && !match(OPENPAREN) && !match(BACKQUOTE))
             return assignmentTarget;
         restoreSavePoint(savePoint);
     }
@@ -4089,23 +4089,26 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseAsyncFunctio
 template <typename LexerType>
 template <class TreeBuilder> typename TreeBuilder::TemplateString Parser<LexerType>::parseTemplateString(TreeBuilder& context, bool isTemplateHead, typename LexerType::RawStringsBuildMode rawStringsBuildMode, bool& elementIsTail)
 {
-    if (!isTemplateHead) {
+    if (isTemplateHead)
+        ASSERT(match(BACKQUOTE));
+    else
         matchOrFail(CLOSEBRACE, "Expected a closing '}' following an expression in template literal");
-        // Re-scan the token to recognize it as Template Element.
-        m_token.m_type = m_lexer->scanTrailingTemplateString(&m_token, rawStringsBuildMode);
-    }
+
+    // Re-scan the token to recognize it as Template Element.
+    m_token.m_type = m_lexer->scanTemplateString(&m_token, rawStringsBuildMode);
     matchOrFail(TEMPLATE, "Expected an template element");
     const Identifier* cooked = m_token.m_data.cooked;
     const Identifier* raw = m_token.m_data.raw;
     elementIsTail = m_token.m_data.isTail;
     JSTokenLocation location(tokenLocation());
     next();
-    return context.createTemplateString(location, *cooked, *raw);
+    return context.createTemplateString(location, cooked, raw);
 }
 
 template <typename LexerType>
 template <class TreeBuilder> typename TreeBuilder::TemplateLiteral Parser<LexerType>::parseTemplateLiteral(TreeBuilder& context, typename LexerType::RawStringsBuildMode rawStringsBuildMode)
 {
+    ASSERT(match(BACKQUOTE));
     JSTokenLocation location(tokenLocation());
     bool elementIsTail = false;
 
@@ -4268,7 +4271,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parsePrimaryExpre
         }
         return re;
     }
-    case TEMPLATE:
+    case BACKQUOTE:
         return parseTemplateLiteral(context, LexerType::RawStringsBuildMode::DontBuildRawStrings);
     case YIELD:
         if (!strictMode() && !currentScope()->isGenerator())
@@ -4505,7 +4508,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpres
             next();
             break;
         }
-        case TEMPLATE: {
+        case BACKQUOTE: {
             semanticFailIfTrue(baseIsSuper, "Cannot use super as tag for tagged templates");
             JSTextPosition expressionEnd = lastTokenEndPosition();
             int nonLHSCount = m_parserState.nonLHSCount;
index f70fc3a..a0554ff 100644 (file)
@@ -109,6 +109,7 @@ enum JSTokenType {
     CLOSEBRACKET,
     COMMA,
     QUESTION,
+    BACKQUOTE,
     INTEGER,
     DOUBLE,
     IDENT,
index 52d947a..324cccf 100644 (file)
@@ -194,7 +194,7 @@ public:
     int createArguments() { return ArgumentsResult; }
     int createArguments(int) { return ArgumentsResult; }
     ExpressionType createSpreadExpression(const JSTokenLocation&, ExpressionType, int, int, int) { return SpreadExpr; }
-    TemplateString createTemplateString(const JSTokenLocation&, const Identifier&, const Identifier&) { return TemplateStringResult; }
+    TemplateString createTemplateString(const JSTokenLocation&, const Identifier*, const Identifier*) { return TemplateStringResult; }
     TemplateStringList createTemplateStringList(TemplateString) { return TemplateStringListResult; }
     TemplateStringList createTemplateStringList(TemplateStringList, TemplateString) { return TemplateStringListResult; }
     TemplateExpressionList createTemplateExpressionList(Expression) { return TemplateExpressionListResult; }
index 54ae07d..9b85160 100644 (file)
@@ -58,8 +58,13 @@ JSArray* TemplateRegistry::getTemplateObject(ExecState* exec, JSTemplateRegistry
     RETURN_IF_EXCEPTION(scope, nullptr);
 
     for (unsigned index = 0; index < count; ++index) {
-        templateObject->putDirectIndex(exec, index, jsString(exec, templateKey.cookedStrings()[index]), ReadOnly | DontDelete, PutDirectIndexLikePutDirect);
+        auto cooked = templateKey.cookedStrings()[index];
+        if (cooked)
+            templateObject->putDirectIndex(exec, index, jsString(exec, cooked.value()), ReadOnly | DontDelete, PutDirectIndexLikePutDirect);
+        else
+            templateObject->putDirectIndex(exec, index, jsUndefined(), ReadOnly | DontDelete, PutDirectIndexLikePutDirect);
         RETURN_IF_EXCEPTION(scope, nullptr);
+
         rawObject->putDirectIndex(exec, index, jsString(exec, templateKey.rawStrings()[index]), ReadOnly | DontDelete, PutDirectIndexLikePutDirect);
         RETURN_IF_EXCEPTION(scope, nullptr);
     }
index 126ac11..880369d 100644 (file)
@@ -38,6 +38,7 @@ class TemplateRegistryKey : public RefCounted<TemplateRegistryKey> {
 public:
     friend class TemplateRegistryKeyTable;
     typedef Vector<String, 4> StringVector;
+    typedef Vector<std::optional<String>, 4> OptionalStringVector;
 
     enum DeletedValueTag { DeletedValue };
     TemplateRegistryKey(DeletedValueTag);
@@ -51,7 +52,7 @@ public:
     unsigned hash() const { return m_hash; }
 
     const StringVector& rawStrings() const { return m_rawStrings; }
-    const StringVector& cookedStrings() const { return m_cookedStrings; }
+    const OptionalStringVector& cookedStrings() const { return m_cookedStrings; }
 
     bool operator==(const TemplateRegistryKey& other) const { return m_hash == other.m_hash && m_rawStrings == other.m_rawStrings; }
     bool operator!=(const TemplateRegistryKey& other) const { return m_hash != other.m_hash || m_rawStrings != other.m_rawStrings; }
@@ -66,22 +67,22 @@ public:
     ~TemplateRegistryKey();
 
 private:
-    static Ref<TemplateRegistryKey> create(const StringVector& rawStrings, const StringVector& cookedStrings)
+    static Ref<TemplateRegistryKey> create(StringVector&& rawStrings, OptionalStringVector&& cookedStrings)
     {
-        return adoptRef(*new TemplateRegistryKey(rawStrings, cookedStrings));
+        return adoptRef(*new TemplateRegistryKey(WTFMove(rawStrings), WTFMove(cookedStrings)));
     }
 
-    TemplateRegistryKey(const StringVector& rawStrings, const StringVector& cookedStrings);
+    TemplateRegistryKey(StringVector&& rawStrings, OptionalStringVector&& cookedStrings);
 
     TemplateRegistryKeyTable* m_table { nullptr };
     StringVector m_rawStrings;
-    StringVector m_cookedStrings;
+    OptionalStringVector m_cookedStrings;
     unsigned m_hash { 0 };
 };
 
-inline TemplateRegistryKey::TemplateRegistryKey(const StringVector& rawStrings, const StringVector& cookedStrings)
-    : m_rawStrings(rawStrings)
-    , m_cookedStrings(cookedStrings)
+inline TemplateRegistryKey::TemplateRegistryKey(StringVector&& rawStrings, OptionalStringVector&& cookedStrings)
+    : m_rawStrings(WTFMove(rawStrings))
+    , m_cookedStrings(WTFMove(cookedStrings))
     , m_hash(calculateHash(rawStrings))
 {
 }
index 725a52a..1e3e358 100644 (file)
@@ -42,9 +42,9 @@ TemplateRegistryKeyTable::~TemplateRegistryKeyTable()
         key->m_table = nullptr;
 }
 
-Ref<TemplateRegistryKey> TemplateRegistryKeyTable::createKey(const TemplateRegistryKey::StringVector& rawStrings, const TemplateRegistryKey::StringVector& cookedStrings)
+Ref<TemplateRegistryKey> TemplateRegistryKeyTable::createKey(TemplateRegistryKey::StringVector&& rawStrings, TemplateRegistryKey::OptionalStringVector&& cookedStrings)
 {
-    auto key = TemplateRegistryKey::create(rawStrings, cookedStrings);
+    auto key = TemplateRegistryKey::create(WTFMove(rawStrings), WTFMove(cookedStrings));
     auto addResult = m_atomicTable.add<TemplateRegistryKeyTranslator>(key.ptr());
     if (addResult.isNewEntry)
         (*addResult.iterator)->m_table = this;
index 9b10402..c3252a8 100644 (file)
@@ -36,10 +36,11 @@ class TemplateRegistryKeyTable {
     WTF_MAKE_NONCOPYABLE(TemplateRegistryKeyTable);
 public:
     using StringVector = Vector<String, 4>;
+    using OptionalStringVector = Vector<std::optional<String>, 4>;
 
     TemplateRegistryKeyTable() = default;
 
-    Ref<TemplateRegistryKey> createKey(const StringVector& rawStrings, const StringVector& cookedStrings);
+    Ref<TemplateRegistryKey> createKey(StringVector&& rawStrings, OptionalStringVector&& cookedStrings);
 
     void unregister(TemplateRegistryKey&);