summaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
authorGaming4JC <g4jc@hyperbola.info>2019-06-08 16:51:29 -0400
committerGaming4JC <g4jc@hyperbola.info>2019-07-18 22:38:19 -0400
commitafb28a43d481075a244b0e18faa8447dfadacf8f (patch)
tree5a820425685c2e9986ae34bf9e6325314e3b72a0 /js
parentadc81d634d12d58a64e0d2a1e95998b766cf6e96 (diff)
downloadUXP-afb28a43d481075a244b0e18faa8447dfadacf8f.tar
UXP-afb28a43d481075a244b0e18faa8447dfadacf8f.tar.gz
UXP-afb28a43d481075a244b0e18faa8447dfadacf8f.tar.lz
UXP-afb28a43d481075a244b0e18faa8447dfadacf8f.tar.xz
UXP-afb28a43d481075a244b0e18faa8447dfadacf8f.zip
1317375 - Implement "Template Literals Revision / Lifting Template Literal Restriction" ECMAScript proposal
Diffstat (limited to 'js')
-rw-r--r--js/src/builtin/ReflectParse.cpp12
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp5
-rw-r--r--js/src/frontend/FoldConstants.cpp4
-rw-r--r--js/src/frontend/FullParseHandler.h4
-rw-r--r--js/src/frontend/NameFunctions.cpp6
-rw-r--r--js/src/frontend/ParseNode.cpp2
-rw-r--r--js/src/frontend/ParseNode.h18
-rw-r--r--js/src/frontend/Parser.cpp25
-rw-r--r--js/src/frontend/Parser.h3
-rw-r--r--js/src/frontend/SyntaxParseHandler.h1
-rw-r--r--js/src/frontend/TokenStream.cpp140
-rw-r--r--js/src/frontend/TokenStream.h62
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/tagTempl.js171
-rw-r--r--js/src/tests/js1_8_5/reflect-parse/templateStrings.js2
14 files changed, 386 insertions, 69 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index 2a0a08a5d..3495ede2f 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -3044,7 +3044,12 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
RootedValue expr(cx);
- expr.setString(next->pn_atom);
+ if (next->isKind(PNK_RAW_UNDEFINED)) {
+ expr.setUndefined();
+ } else {
+ MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
+ expr.setString(next->pn_atom);
+ }
cooked.infallibleAppend(expr);
}
@@ -3136,6 +3141,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
return literal(pn, dst);
case PNK_YIELD_STAR:
@@ -3276,6 +3282,10 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
val.setNull();
break;
+ case PNK_RAW_UNDEFINED:
+ val.setUndefined();
+ break;
+
case PNK_TRUE:
val.setBoolean(true);
break;
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index 7f9fa8a5d..44787dc52 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3069,6 +3069,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:
@@ -6242,6 +6243,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje
case PNK_NULL:
vp.setNull();
return true;
+ case PNK_RAW_UNDEFINED:
+ vp.setUndefined();
+ return true;
case PNK_CALLSITEOBJ:
case PNK_ARRAY: {
unsigned count;
@@ -10636,6 +10640,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
if (!emit1(pn->getOp()))
return false;
break;
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index 6f62ffac6..441a72a45 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -378,6 +378,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_THIS:
case PNK_ELISION:
case PNK_NUMBER:
@@ -468,6 +469,7 @@ IsEffectless(ParseNode* node)
node->isKind(PNK_TEMPLATE_STRING) ||
node->isKind(PNK_NUMBER) ||
node->isKind(PNK_NULL) ||
+ node->isKind(PNK_RAW_UNDEFINED) ||
node->isKind(PNK_FUNCTION) ||
node->isKind(PNK_GENEXP);
}
@@ -492,6 +494,7 @@ Boolish(ParseNode* pn)
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
return Falsy;
case PNK_VOID: {
@@ -1643,6 +1646,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_NUMBER:
case PNK_DEBUGGER:
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
index 3938d9743..f0fc7700d 100644
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -183,6 +183,10 @@ class FullParseHandler
return new_<NullLiteral>(pos);
}
+ ParseNode* newRawUndefinedLiteral(const TokenPos& pos) {
+ return new_<RawUndefinedLiteral>(pos);
+ }
+
// The Boxer object here is any object that can allocate ObjectBoxes.
// Specifically, a Boxer has a .newObjectBox(T) method that accepts a
// Rooted<RegExpObject*> argument and returns an ObjectBox*.
diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp
index ce1318f0b..dc54d0a88 100644
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -316,7 +316,8 @@ class NameResolver
return false;
// Next is the callsite object node. This node only contains
- // internal strings and an array -- no user-controlled expressions.
+ // internal strings or undefined and an array -- no user-controlled
+ // expressions.
element = element->pn_next;
#ifdef DEBUG
{
@@ -326,7 +327,7 @@ class NameResolver
for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next)
MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING));
for (ParseNode* next = array->pn_next; next; next = next->pn_next)
- MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
+ MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING) || next->isKind(PNK_RAW_UNDEFINED));
}
#endif
@@ -382,6 +383,7 @@ class NameResolver
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index 91f17625c..5fe64e3d3 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -190,6 +190,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:
@@ -685,6 +686,7 @@ NullaryNode::dump()
case PNK_TRUE: fprintf(stderr, "#true"); break;
case PNK_FALSE: fprintf(stderr, "#false"); break;
case PNK_NULL: fprintf(stderr, "#null"); break;
+ case PNK_RAW_UNDEFINED: fprintf(stderr, "#undefined"); break;
case PNK_NUMBER: {
ToCStringBuf cbuf;
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index c3523afe4..c0669d85e 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -54,6 +54,7 @@ class ObjectBox;
F(TRUE) \
F(FALSE) \
F(NULL) \
+ F(RAW_UNDEFINED) \
F(THIS) \
F(FUNCTION) \
F(MODULE) \
@@ -406,7 +407,8 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_NUMBER dval pn_dval: double value of numeric literal
* PNK_TRUE, nullary pn_op: JSOp bytecode
* PNK_FALSE,
- * PNK_NULL
+ * PNK_NULL,
+ * PNK_RAW_UNDEFINED
*
* PNK_THIS, unary pn_kid: '.this' Name if function `this`, else nullptr
* PNK_SUPERBASE unary pn_kid: '.this' Name
@@ -686,7 +688,8 @@ class ParseNode
isKind(PNK_STRING) ||
isKind(PNK_TRUE) ||
isKind(PNK_FALSE) ||
- isKind(PNK_NULL);
+ isKind(PNK_NULL) ||
+ isKind(PNK_RAW_UNDEFINED);
}
/* Return true if this node appears in a Directive Prologue. */
@@ -1141,6 +1144,16 @@ class NullLiteral : public ParseNode
explicit NullLiteral(const TokenPos& pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { }
};
+// This is only used internally, currently just for tagged templates.
+// It represents the value 'undefined' (aka `void 0`), like NullLiteral
+// represents the value 'null'.
+class RawUndefinedLiteral : public ParseNode
+{
+ public:
+ explicit RawUndefinedLiteral(const TokenPos& pos)
+ : ParseNode(PNK_RAW_UNDEFINED, JSOP_UNDEFINED, PN_NULLARY, pos) { }
+};
+
class BooleanLiteral : public ParseNode
{
public:
@@ -1361,6 +1374,7 @@ ParseNode::isConstant()
case PNK_STRING:
case PNK_TEMPLATE_STRING:
case PNK_NULL:
+ case PNK_RAW_UNDEFINED:
case PNK_FALSE:
case PNK_TRUE:
return true;
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 66ada79f2..0ff10f73f 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3123,7 +3123,7 @@ template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
{
- Node pn = noSubstitutionTemplate();
+ Node pn = noSubstitutionUntaggedTemplate();
if (!pn)
return null();
@@ -3136,7 +3136,7 @@ Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
return null();
- pn = noSubstitutionTemplate();
+ pn = noSubstitutionUntaggedTemplate();
if (!pn)
return null();
@@ -3365,7 +3365,7 @@ template <typename ParseHandler>
bool
Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
{
- Node cookedNode = noSubstitutionTemplate();
+ Node cookedNode = noSubstitutionTaggedTemplate();
if (!cookedNode)
return false;
@@ -8753,8 +8753,23 @@ Parser<ParseHandler>::stringLiteral()
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::noSubstitutionTemplate()
+Parser<ParseHandler>::noSubstitutionTaggedTemplate()
{
+ if (tokenStream.hasInvalidTemplateEscape()) {
+ tokenStream.clearInvalidTemplateEscape();
+ return handler.newRawUndefinedLiteral(pos());
+ }
+
+ return handler.newTemplateStringLiteral(stopStringCompression(), pos());
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::noSubstitutionUntaggedTemplate()
+{
+ if (!tokenStream.checkForInvalidTemplateEscapeError())
+ return null();
+
return handler.newTemplateStringLiteral(stopStringCompression(), pos());
}
@@ -9485,7 +9500,7 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling
return templateLiteral(yieldHandling);
case TOK_NO_SUBS_TEMPLATE:
- return noSubstitutionTemplate();
+ return noSubstitutionUntaggedTemplate();
case TOK_STRING:
return stringLiteral();
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index e36a6e8b2..b850dc00b 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1052,7 +1052,8 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
JSAtom* stopStringCompression();
Node stringLiteral();
- Node noSubstitutionTemplate();
+ Node noSubstitutionTaggedTemplate();
+ Node noSubstitutionUntaggedTemplate();
Node templateLiteral(YieldHandling yieldHandling);
bool taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt);
bool appendToCallSiteObj(Node callSiteObj);
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
index 9dc3c1072..aa85a631e 100644
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -224,6 +224,7 @@ class SyntaxParseHandler
Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; }
Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; }
+ Node newRawUndefinedLiteral(const TokenPos& pos) { return NodeGeneric; }
template <class Boxer>
Node newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { return NodeGeneric; }
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index 186b7a8ab..6a7934e8f 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1918,55 +1918,6 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
}
bool
-TokenStream::matchBracedUnicode(bool* matched, uint32_t* cp)
-{
- int32_t c;
- if (!peekChar(&c))
- return false;
- if (c != '{') {
- *matched = false;
- return true;
- }
-
- consumeKnownChar('{');
-
- uint32_t start = userbuf.offset();
-
- bool first = true;
- uint32_t code = 0;
- do {
- int32_t c = getCharIgnoreEOL();
- if (c == EOF) {
- error(JSMSG_MALFORMED_ESCAPE, "Unicode");
- return false;
- }
- if (c == '}') {
- if (first) {
- error(JSMSG_MALFORMED_ESCAPE, "Unicode");
- return false;
- }
- break;
- }
-
- if (!JS7_ISHEX(c)) {
- error(JSMSG_MALFORMED_ESCAPE, "Unicode");
- return false;
- }
-
- code = (code << 4) | JS7_UNHEX(c);
- if (code > unicode::NonBMPMax) {
- errorAt(start, JSMSG_UNICODE_OVERFLOW, "escape sequence");
- return false;
- }
- first = false;
- } while (true);
-
- *matched = true;
- *cp = code;
- return true;
-}
-
-bool
TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
{
int c;
@@ -1988,6 +1939,10 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
}
if (c == '\\') {
+ // When parsing templates, we don't immediately report errors for
+ // invalid escapes; these are handled by the parser.
+ // In those cases we don't append to tokenbuf, since it won't be
+ // read.
switch (c = getChar()) {
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
@@ -2003,11 +1958,73 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
// Unicode character specification.
case 'u': {
- bool matched;
- uint32_t code;
- if (!matchBracedUnicode(&matched, &code))
+ uint32_t code = 0;
+
+ int32_t c2;
+ if (!peekChar(&c2))
return false;
- if (matched) {
+
+ uint32_t start = userbuf.offset() - 2;
+
+ if (c2 == '{') {
+ consumeKnownChar('{');
+
+ bool first = true;
+ bool valid = true;
+ do {
+ int32_t c = getCharIgnoreEOL();
+ if (c == EOF) {
+ if (parsingTemplate) {
+ setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
+ valid = false;
+ break;
+ }
+ reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+ return false;
+ }
+ if (c == '}') {
+ if (first) {
+ if (parsingTemplate) {
+ setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
+ valid = false;
+ break;
+ }
+ reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+ return false;
+ }
+ break;
+ }
+
+ if (!JS7_ISHEX(c)) {
+ if (parsingTemplate) {
+ // We put the character back so that we read
+ // it on the next pass, which matters if it
+ // was '`' or '\'.
+ ungetCharIgnoreEOL(c);
+ setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
+ valid = false;
+ break;
+ }
+ reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+ return false;
+ }
+
+ code = (code << 4) | JS7_UNHEX(c);
+ if (code > unicode::NonBMPMax) {
+ if (parsingTemplate) {
+ setInvalidTemplateEscape(start + 3, InvalidEscapeType::UnicodeOverflow);
+ valid = false;
+ break;
+ }
+ reportInvalidEscapeError(start + 3, InvalidEscapeType::UnicodeOverflow);
+ return false;
+ }
+
+ first = false;
+ } while (true);
+
+ if (!valid)
+ continue;
MOZ_ASSERT(code <= unicode::NonBMPMax);
if (code < unicode::NonBMPMin) {
@@ -2030,7 +2047,11 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
c = (c << 4) + JS7_UNHEX(cp[3]);
skipChars(4);
} else {
- error(JSMSG_MALFORMED_ESCAPE, "Unicode");
+ if (parsingTemplate) {
+ setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
+ continue;
+ }
+ reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
return false;
}
break;
@@ -2043,7 +2064,12 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
skipChars(2);
} else {
- error(JSMSG_MALFORMED_ESCAPE, "hexadecimal");
+ uint32_t start = userbuf.offset() - 2;
+ if (parsingTemplate) {
+ setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal);
+ continue;
+ }
+ reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal);
return false;
}
break;
@@ -2060,8 +2086,8 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
// Strict mode code allows only \0, then a non-digit.
if (val != 0 || JS7_ISDEC(c)) {
if (parsingTemplate) {
- error(JSMSG_DEPRECATED_OCTAL);
- return false;
+ setInvalidTemplateEscape(userbuf.offset() - 2, InvalidEscapeType::Octal);
+ continue;
}
if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
return false;
diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h
index 4efc31446..fbfefbfe1 100644
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -80,6 +80,20 @@ struct TokenPos {
enum DecimalPoint { NoDecimal = false, HasDecimal = true };
+enum class InvalidEscapeType {
+ // No invalid character escapes.
+ None,
+ // A malformed \x escape.
+ Hexadecimal,
+ // A malformed \u escape.
+ Unicode,
+ // An otherwise well-formed \u escape which represents a
+ // codepoint > 10FFFF.
+ UnicodeOverflow,
+ // An octal escape in a template token.
+ Octal
+};
+
class TokenStream;
struct Token
@@ -361,6 +375,23 @@ class MOZ_STACK_CLASS TokenStream
bool hadError() const { return flags.hadError; }
void clearSawOctalEscape() { flags.sawOctalEscape = false; }
+ bool hasInvalidTemplateEscape() const {
+ return invalidTemplateEscapeType != InvalidEscapeType::None;
+ }
+ void clearInvalidTemplateEscape() {
+ invalidTemplateEscapeType = InvalidEscapeType::None;
+ }
+
+ // If there is an invalid escape in a template, report it and return false,
+ // otherwise return true.
+ bool checkForInvalidTemplateEscapeError() {
+ if (invalidTemplateEscapeType == InvalidEscapeType::None)
+ return true;
+
+ reportInvalidEscapeError(invalidTemplateEscapeOffset, invalidTemplateEscapeType);
+ return false;
+ }
+
// TokenStream-specific error reporters.
bool reportError(unsigned errorNumber, ...);
bool reportErrorNoOffset(unsigned errorNumber, ...);
@@ -422,6 +453,33 @@ class MOZ_STACK_CLASS TokenStream
bool reportStrictModeError(unsigned errorNumber, ...);
bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); }
+ void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) {
+ MOZ_ASSERT(type != InvalidEscapeType::None);
+ if (invalidTemplateEscapeType != InvalidEscapeType::None)
+ return;
+ invalidTemplateEscapeOffset = offset;
+ invalidTemplateEscapeType = type;
+ }
+ void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) {
+ switch (type) {
+ case InvalidEscapeType::None:
+ MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType");
+ return;
+ case InvalidEscapeType::Hexadecimal:
+ errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal");
+ return;
+ case InvalidEscapeType::Unicode:
+ errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode");
+ return;
+ case InvalidEscapeType::UnicodeOverflow:
+ errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence");
+ return;
+ case InvalidEscapeType::Octal:
+ errorAt(offset, JSMSG_DEPRECATED_OCTAL);
+ return;
+ }
+ }
+
static JSAtom* atomize(ExclusiveContext* cx, CharBuffer& cb);
MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart);
@@ -442,6 +500,9 @@ class MOZ_STACK_CLASS TokenStream
bool awaitIsKeyword = false;
friend class AutoAwaitIsKeyword;
+ uint32_t invalidTemplateEscapeOffset = 0;
+ InvalidEscapeType invalidTemplateEscapeType = InvalidEscapeType::None;
+
public:
typedef Token::Modifier Modifier;
static constexpr Modifier None = Token::None;
@@ -955,7 +1016,6 @@ class MOZ_STACK_CLASS TokenStream
MOZ_MUST_USE bool getTokenInternal(TokenKind* ttp, Modifier modifier);
- MOZ_MUST_USE bool matchBracedUnicode(bool* matched, uint32_t* code);
MOZ_MUST_USE bool getStringOrTemplateToken(int untilChar, Token** tp);
int32_t getChar();
diff --git a/js/src/tests/ecma_6/TemplateStrings/tagTempl.js b/js/src/tests/ecma_6/TemplateStrings/tagTempl.js
index 1e3f52bfb..99c2098dc 100644
--- a/js/src/tests/ecma_6/TemplateStrings/tagTempl.js
+++ b/js/src/tests/ecma_6/TemplateStrings/tagTempl.js
@@ -287,5 +287,176 @@ assertEq(String.raw`h\r\ney${4}there\n`, "h\\r\\ney4there\\n");
assertEq(String.raw`hey`, "hey");
assertEq(String.raw``, "");
+// Invalid escape sequences
+check(raw`\01`, ["\\01"]);
+check(raw`\01${0}right`, ["\\01","right"]);
+check(raw`left${0}\01`, ["left","\\01"]);
+check(raw`left${0}\01${1}right`, ["left","\\01","right"]);
+check(raw`\1`, ["\\1"]);
+check(raw`\1${0}right`, ["\\1","right"]);
+check(raw`left${0}\1`, ["left","\\1"]);
+check(raw`left${0}\1${1}right`, ["left","\\1","right"]);
+check(raw`\xg`, ["\\xg"]);
+check(raw`\xg${0}right`, ["\\xg","right"]);
+check(raw`left${0}\xg`, ["left","\\xg"]);
+check(raw`left${0}\xg${1}right`, ["left","\\xg","right"]);
+check(raw`\xAg`, ["\\xAg"]);
+check(raw`\xAg${0}right`, ["\\xAg","right"]);
+check(raw`left${0}\xAg`, ["left","\\xAg"]);
+check(raw`left${0}\xAg${1}right`, ["left","\\xAg","right"]);
+check(raw`\u0`, ["\\u0"]);
+check(raw`\u0${0}right`, ["\\u0","right"]);
+check(raw`left${0}\u0`, ["left","\\u0"]);
+check(raw`left${0}\u0${1}right`, ["left","\\u0","right"]);
+check(raw`\u0g`, ["\\u0g"]);
+check(raw`\u0g${0}right`, ["\\u0g","right"]);
+check(raw`left${0}\u0g`, ["left","\\u0g"]);
+check(raw`left${0}\u0g${1}right`, ["left","\\u0g","right"]);
+check(raw`\u00g`, ["\\u00g"]);
+check(raw`\u00g${0}right`, ["\\u00g","right"]);
+check(raw`left${0}\u00g`, ["left","\\u00g"]);
+check(raw`left${0}\u00g${1}right`, ["left","\\u00g","right"]);
+check(raw`\u000g`, ["\\u000g"]);
+check(raw`\u000g${0}right`, ["\\u000g","right"]);
+check(raw`left${0}\u000g`, ["left","\\u000g"]);
+check(raw`left${0}\u000g${1}right`, ["left","\\u000g","right"]);
+check(raw`\u{}`, ["\\u{}"]);
+check(raw`\u{}${0}right`, ["\\u{}","right"]);
+check(raw`left${0}\u{}`, ["left","\\u{}"]);
+check(raw`left${0}\u{}${1}right`, ["left","\\u{}","right"]);
+check(raw`\u{-0}`, ["\\u{-0}"]);
+check(raw`\u{-0}${0}right`, ["\\u{-0}","right"]);
+check(raw`left${0}\u{-0}`, ["left","\\u{-0}"]);
+check(raw`left${0}\u{-0}${1}right`, ["left","\\u{-0}","right"]);
+check(raw`\u{g}`, ["\\u{g}"]);
+check(raw`\u{g}${0}right`, ["\\u{g}","right"]);
+check(raw`left${0}\u{g}`, ["left","\\u{g}"]);
+check(raw`left${0}\u{g}${1}right`, ["left","\\u{g}","right"]);
+check(raw`\u{0`, ["\\u{0"]);
+check(raw`\u{0${0}right`, ["\\u{0","right"]);
+check(raw`left${0}\u{0`, ["left","\\u{0"]);
+check(raw`left${0}\u{0${1}right`, ["left","\\u{0","right"]);
+check(raw`\u{\u{0}`, ["\\u{\\u{0}"]);
+check(raw`\u{\u{0}${0}right`, ["\\u{\\u{0}","right"]);
+check(raw`left${0}\u{\u{0}`, ["left","\\u{\\u{0}"]);
+check(raw`left${0}\u{\u{0}${1}right`, ["left","\\u{\\u{0}","right"]);
+check(raw`\u{110000}`, ["\\u{110000}"]);
+check(raw`\u{110000}${0}right`, ["\\u{110000}","right"]);
+check(raw`left${0}\u{110000}`, ["left","\\u{110000}"]);
+check(raw`left${0}\u{110000}${1}right`, ["left","\\u{110000}","right"]);
+
+check(cooked`\01`, [void 0]);
+check(cooked`\01${0}right`, [void 0,"right"]);
+check(cooked`left${0}\01`, ["left",void 0]);
+check(cooked`left${0}\01${1}right`, ["left",void 0,"right"]);
+check(cooked`\1`, [void 0]);
+check(cooked`\1${0}right`, [void 0,"right"]);
+check(cooked`left${0}\1`, ["left",void 0]);
+check(cooked`left${0}\1${1}right`, ["left",void 0,"right"]);
+check(cooked`\xg`, [void 0]);
+check(cooked`\xg${0}right`, [void 0,"right"]);
+check(cooked`left${0}\xg`, ["left",void 0]);
+check(cooked`left${0}\xg${1}right`, ["left",void 0,"right"]);
+check(cooked`\xAg`, [void 0]);
+check(cooked`\xAg${0}right`, [void 0,"right"]);
+check(cooked`left${0}\xAg`, ["left",void 0]);
+check(cooked`left${0}\xAg${1}right`, ["left",void 0,"right"]);
+check(cooked`\u0`, [void 0]);
+check(cooked`\u0${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u0`, ["left",void 0]);
+check(cooked`left${0}\u0${1}right`, ["left",void 0,"right"]);
+check(cooked`\u0g`, [void 0]);
+check(cooked`\u0g${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u0g`, ["left",void 0]);
+check(cooked`left${0}\u0g${1}right`, ["left",void 0,"right"]);
+check(cooked`\u00g`, [void 0]);
+check(cooked`\u00g${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u00g`, ["left",void 0]);
+check(cooked`left${0}\u00g${1}right`, ["left",void 0,"right"]);
+check(cooked`\u000g`, [void 0]);
+check(cooked`\u000g${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u000g`, ["left",void 0]);
+check(cooked`left${0}\u000g${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{}`, [void 0]);
+check(cooked`\u{}${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{}`, ["left",void 0]);
+check(cooked`left${0}\u{}${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{-0}`, [void 0]);
+check(cooked`\u{-0}${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{-0}`, ["left",void 0]);
+check(cooked`left${0}\u{-0}${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{g}`, [void 0]);
+check(cooked`\u{g}${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{g}`, ["left",void 0]);
+check(cooked`left${0}\u{g}${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{0`, [void 0]);
+check(cooked`\u{0${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{0`, ["left",void 0]);
+check(cooked`left${0}\u{0${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{\u{0}`, [void 0]);
+check(cooked`\u{\u{0}${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{\u{0}`, ["left",void 0]);
+check(cooked`left${0}\u{\u{0}${1}right`, ["left",void 0,"right"]);
+check(cooked`\u{110000}`, [void 0]);
+check(cooked`\u{110000}${0}right`, [void 0,"right"]);
+check(cooked`left${0}\u{110000}`, ["left",void 0]);
+check(cooked`left${0}\u{110000}${1}right`, ["left",void 0,"right"]);
+
+syntaxError("`\\01`");
+syntaxError("`\\01${0}right`");
+syntaxError("`left${0}\\01`");
+syntaxError("`left${0}\\01${1}right`");
+syntaxError("`\\1`");
+syntaxError("`\\1${0}right`");
+syntaxError("`left${0}\\1`");
+syntaxError("`left${0}\\1${1}right`");
+syntaxError("`\\xg`");
+syntaxError("`\\xg${0}right`");
+syntaxError("`left${0}\\xg`");
+syntaxError("`left${0}\\xg${1}right`");
+syntaxError("`\\xAg`");
+syntaxError("`\\xAg${0}right`");
+syntaxError("`left${0}\\xAg`");
+syntaxError("`left${0}\\xAg${1}right`");
+syntaxError("`\\u0`");
+syntaxError("`\\u0${0}right`");
+syntaxError("`left${0}\\u0`");
+syntaxError("`left${0}\\u0${1}right`");
+syntaxError("`\\u0g`");
+syntaxError("`\\u0g${0}right`");
+syntaxError("`left${0}\\u0g`");
+syntaxError("`left${0}\\u0g${1}right`");
+syntaxError("`\\u00g`");
+syntaxError("`\\u00g${0}right`");
+syntaxError("`left${0}\\u00g`");
+syntaxError("`left${0}\\u00g${1}right`");
+syntaxError("`\\u000g`");
+syntaxError("`\\u000g${0}right`");
+syntaxError("`left${0}\\u000g`");
+syntaxError("`left${0}\\u000g${1}right`");
+syntaxError("`\\u{}`");
+syntaxError("`\\u{}${0}right`");
+syntaxError("`left${0}\\u{}`");
+syntaxError("`left${0}\\u{}${1}right`");
+syntaxError("`\\u{-0}`");
+syntaxError("`\\u{-0}${0}right`");
+syntaxError("`left${0}\\u{-0}`");
+syntaxError("`left${0}\\u{-0}${1}right`");
+syntaxError("`\\u{g}`");
+syntaxError("`\\u{g}${0}right`");
+syntaxError("`left${0}\\u{g}`");
+syntaxError("`left${0}\\u{g}${1}right`");
+syntaxError("`\\u{0`");
+syntaxError("`\\u{0${0}right`");
+syntaxError("`left${0}\\u{0`");
+syntaxError("`left${0}\\u{0${1}right`");
+syntaxError("`\\u{\\u{0}`");
+syntaxError("`\\u{\\u{0}${0}right`");
+syntaxError("`left${0}\\u{\\u{0}`");
+syntaxError("`left${0}\\u{\\u{0}${1}right`");
+syntaxError("`\\u{110000}`");
+syntaxError("`\\u{110000}${0}right`");
+syntaxError("`left${0}\\u{110000}`");
+syntaxError("`left${0}\\u{110000}${1}right`");
reportCompare(0, 0, "ok");
diff --git a/js/src/tests/js1_8_5/reflect-parse/templateStrings.js b/js/src/tests/js1_8_5/reflect-parse/templateStrings.js
index c87ba96b8..fb12afd00 100644
--- a/js/src/tests/js1_8_5/reflect-parse/templateStrings.js
+++ b/js/src/tests/js1_8_5/reflect-parse/templateStrings.js
@@ -7,6 +7,8 @@ assertStringExpr("`hey\nthere`", literal("hey\nthere"));
assertExpr("`hey${\"there\"}`", templateLit([lit("hey"), lit("there"), lit("")]));
assertExpr("`hey${\"there\"}mine`", templateLit([lit("hey"), lit("there"), lit("mine")]));
assertExpr("`hey${a == 5}mine`", templateLit([lit("hey"), binExpr("==", ident("a"), lit(5)), lit("mine")]));
+assertExpr("func`hey\\x`", taggedTemplate(ident("func"), template(["hey\\x"], [void 0])));
+assertExpr("func`hey${4}\\x`", taggedTemplate(ident("func"), template(["hey","\\x"], ["hey",void 0], lit(4))));
assertExpr("`hey${`there${\"how\"}`}mine`", templateLit([lit("hey"),
templateLit([lit("there"), lit("how"), lit("")]), lit("mine")]));
assertExpr("func`hey`", taggedTemplate(ident("func"), template(["hey"], ["hey"])));