Commit 18e4c46d authored by bakkot's avatar bakkot Committed by Commit bot

[parser] Lift template literal invalid escape restriction

This implements the proposal at
https://github.com/tc39/proposal-template-literal-revision
staged behind a flag --harmony-template-escapes. The proposal allows
invalid octal, unicode, and hexadecimal escape sequences to appear in
tagged template literals, instead of being a syntax error. These have
a 'cooked' value of 'undefined', but are still accessible through the
'raw' property.

BUG=v8:5546

Review-Url: https://codereview.chromium.org/2665513002
Cr-Commit-Position: refs/heads/master@{#43384}
parent 6a09a416
...@@ -3526,6 +3526,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_tostring) ...@@ -3526,6 +3526,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_tostring)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_fields) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_fields)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_rest_spread) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_rest_spread)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_template_escapes)
void InstallPublicSymbol(Factory* factory, Handle<Context> native_context, void InstallPublicSymbol(Factory* factory, Handle<Context> native_context,
const char* name, Handle<Symbol> value) { const char* name, Handle<Symbol> value) {
...@@ -4242,6 +4243,7 @@ bool Genesis::InstallExperimentalNatives() { ...@@ -4242,6 +4243,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_async_iteration_natives[] = {nullptr}; static const char* harmony_async_iteration_natives[] = {nullptr};
static const char* harmony_dynamic_import_natives[] = {nullptr}; static const char* harmony_dynamic_import_natives[] = {nullptr};
static const char* harmony_promise_finally_natives[] = {nullptr}; static const char* harmony_promise_finally_natives[] = {nullptr};
static const char* harmony_template_escapes_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount(); for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) { i < ExperimentalNatives::GetBuiltinsCount(); i++) {
......
...@@ -209,11 +209,13 @@ DEFINE_IMPLICATION(es_staging, move_object_start) ...@@ -209,11 +209,13 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
V(harmony_promise_finally, "harmony Promise.prototype.finally") V(harmony_promise_finally, "harmony Promise.prototype.finally")
// Features that are complete (but still behind --harmony/es-staging flag). // Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \ #define HARMONY_STAGED(V) \
V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \ V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \
V(harmony_restrictive_generators, \ V(harmony_restrictive_generators, \
"harmony restrictions on generator declarations") \ "harmony restrictions on generator declarations") \
V(harmony_object_rest_spread, "harmony object rest spread properties") V(harmony_object_rest_spread, "harmony object rest spread properties") \
V(harmony_template_escapes, \
"harmony invalid escapes in tagged template literals")
// Features that are shipping (turned on by default, but internal flag remains). // Features that are shipping (turned on by default, but internal flag remains).
#define HARMONY_SHIPPING_BASE(V) \ #define HARMONY_SHIPPING_BASE(V) \
......
...@@ -226,7 +226,8 @@ class ParserBase { ...@@ -226,7 +226,8 @@ class ParserBase {
allow_harmony_class_fields_(false), allow_harmony_class_fields_(false),
allow_harmony_object_rest_spread_(false), allow_harmony_object_rest_spread_(false),
allow_harmony_dynamic_import_(false), allow_harmony_dynamic_import_(false),
allow_harmony_async_iteration_(false) {} allow_harmony_async_iteration_(false),
allow_harmony_template_escapes_(false) {}
#define ALLOW_ACCESSORS(name) \ #define ALLOW_ACCESSORS(name) \
bool allow_##name() const { return allow_##name##_; } \ bool allow_##name() const { return allow_##name##_; } \
...@@ -242,6 +243,7 @@ class ParserBase { ...@@ -242,6 +243,7 @@ class ParserBase {
ALLOW_ACCESSORS(harmony_object_rest_spread); ALLOW_ACCESSORS(harmony_object_rest_spread);
ALLOW_ACCESSORS(harmony_dynamic_import); ALLOW_ACCESSORS(harmony_dynamic_import);
ALLOW_ACCESSORS(harmony_async_iteration); ALLOW_ACCESSORS(harmony_async_iteration);
ALLOW_ACCESSORS(harmony_template_escapes);
#undef ALLOW_ACCESSORS #undef ALLOW_ACCESSORS
...@@ -816,14 +818,12 @@ class ParserBase { ...@@ -816,14 +818,12 @@ class ParserBase {
} }
// Checks whether an octal literal was last seen between beg_pos and end_pos. // Checks whether an octal literal was last seen between beg_pos and end_pos.
// If so, reports an error. Only called for strict mode and template strings. // Only called for strict mode strings.
void CheckOctalLiteral(int beg_pos, int end_pos, bool is_template, bool* ok) { void CheckStrictOctalLiteral(int beg_pos, int end_pos, bool* ok) {
Scanner::Location octal = scanner()->octal_position(); Scanner::Location octal = scanner()->octal_position();
if (octal.IsValid() && beg_pos <= octal.beg_pos && if (octal.IsValid() && beg_pos <= octal.beg_pos &&
octal.end_pos <= end_pos) { octal.end_pos <= end_pos) {
MessageTemplate::Template message = MessageTemplate::Template message = scanner()->octal_message();
is_template ? MessageTemplate::kTemplateOctalLiteral
: scanner()->octal_message();
DCHECK_NE(message, MessageTemplate::kNone); DCHECK_NE(message, MessageTemplate::kNone);
impl()->ReportMessageAt(octal, message); impl()->ReportMessageAt(octal, message);
scanner()->clear_octal_position(); scanner()->clear_octal_position();
...@@ -834,12 +834,23 @@ class ParserBase { ...@@ -834,12 +834,23 @@ class ParserBase {
} }
} }
inline void CheckStrictOctalLiteral(int beg_pos, int end_pos, bool* ok) { // Checks if an octal literal or an invalid hex or unicode escape sequence
CheckOctalLiteral(beg_pos, end_pos, false, ok); // appears in a template literal. In the presence of such, either
} // returns false or reports an error, depending on should_throw. Otherwise
// returns true.
inline bool CheckTemplateEscapes(bool should_throw, bool* ok) {
if (!scanner()->has_invalid_template_escape()) {
return true;
}
inline void CheckTemplateOctalLiteral(int beg_pos, int end_pos, bool* ok) { // Handle error case(s)
CheckOctalLiteral(beg_pos, end_pos, true, ok); if (should_throw) {
impl()->ReportMessageAt(scanner()->invalid_template_escape_location(),
scanner()->invalid_template_escape_message());
*ok = false;
}
scanner()->clear_invalid_template_escape();
return false;
} }
void CheckDestructuringElement(ExpressionT element, int beg_pos, int end_pos); void CheckDestructuringElement(ExpressionT element, int beg_pos, int end_pos);
...@@ -1138,7 +1149,8 @@ class ParserBase { ...@@ -1138,7 +1149,8 @@ class ParserBase {
Scanner::Location class_name_location, Scanner::Location class_name_location,
bool name_is_strict_reserved, bool name_is_strict_reserved,
int class_token_pos, bool* ok); int class_token_pos, bool* ok);
ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool* ok); ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool tagged,
bool* ok);
ExpressionT ParseSuperExpression(bool is_new, bool* ok); ExpressionT ParseSuperExpression(bool is_new, bool* ok);
ExpressionT ParseDynamicImportExpression(bool* ok); ExpressionT ParseDynamicImportExpression(bool* ok);
ExpressionT ParseNewTargetExpression(bool* ok); ExpressionT ParseNewTargetExpression(bool* ok);
...@@ -1449,6 +1461,7 @@ class ParserBase { ...@@ -1449,6 +1461,7 @@ class ParserBase {
bool allow_harmony_object_rest_spread_; bool allow_harmony_object_rest_spread_;
bool allow_harmony_dynamic_import_; bool allow_harmony_dynamic_import_;
bool allow_harmony_async_iteration_; bool allow_harmony_async_iteration_;
bool allow_harmony_template_escapes_;
friend class DiscardableZoneScope; friend class DiscardableZoneScope;
}; };
...@@ -1817,7 +1830,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParsePrimaryExpression( ...@@ -1817,7 +1830,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParsePrimaryExpression(
case Token::TEMPLATE_SPAN: case Token::TEMPLATE_SPAN:
case Token::TEMPLATE_TAIL: case Token::TEMPLATE_TAIL:
BindingPatternUnexpectedToken(); BindingPatternUnexpectedToken();
return ParseTemplateLiteral(impl()->NoTemplateTag(), beg_pos, ok); return ParseTemplateLiteral(impl()->NoTemplateTag(), beg_pos, false, ok);
case Token::MOD: case Token::MOD:
if (allow_natives() || extension_ != NULL) { if (allow_natives() || extension_ != NULL) {
...@@ -3215,7 +3228,7 @@ ParserBase<Impl>::ParseLeftHandSideExpression(bool* ok) { ...@@ -3215,7 +3228,7 @@ ParserBase<Impl>::ParseLeftHandSideExpression(bool* ok) {
impl()->RewriteNonPattern(CHECK_OK); impl()->RewriteNonPattern(CHECK_OK);
BindingPatternUnexpectedToken(); BindingPatternUnexpectedToken();
ArrowFormalParametersUnexpectedToken(); ArrowFormalParametersUnexpectedToken();
result = ParseTemplateLiteral(result, position(), CHECK_OK); result = ParseTemplateLiteral(result, position(), true, CHECK_OK);
break; break;
} }
...@@ -3495,7 +3508,7 @@ ParserBase<Impl>::ParseMemberExpressionContinuation(ExpressionT expression, ...@@ -3495,7 +3508,7 @@ ParserBase<Impl>::ParseMemberExpressionContinuation(ExpressionT expression,
expression->AsFunctionLiteral()->SetShouldEagerCompile(); expression->AsFunctionLiteral()->SetShouldEagerCompile();
} }
} }
expression = ParseTemplateLiteral(expression, pos, CHECK_OK); expression = ParseTemplateLiteral(expression, pos, true, CHECK_OK);
break; break;
} }
case Token::ILLEGAL: { case Token::ILLEGAL: {
...@@ -4376,7 +4389,7 @@ ParserBase<Impl>::ParseAsyncFunctionLiteral(bool* ok) { ...@@ -4376,7 +4389,7 @@ ParserBase<Impl>::ParseAsyncFunctionLiteral(bool* ok) {
template <typename Impl> template <typename Impl>
typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral( typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
ExpressionT tag, int start, bool* ok) { ExpressionT tag, int start, bool tagged, bool* ok) {
// A TemplateLiteral is made up of 0 or more TEMPLATE_SPAN tokens (literal // A TemplateLiteral is made up of 0 or more TEMPLATE_SPAN tokens (literal
// text followed by a substitution expression), finalized by a single // text followed by a substitution expression), finalized by a single
// TEMPLATE_TAIL. // TEMPLATE_TAIL.
...@@ -4389,22 +4402,25 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral( ...@@ -4389,22 +4402,25 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
// TEMPLATE_SPAN, or a TEMPLATE_TAIL. // TEMPLATE_SPAN, or a TEMPLATE_TAIL.
CHECK(peek() == Token::TEMPLATE_SPAN || peek() == Token::TEMPLATE_TAIL); CHECK(peek() == Token::TEMPLATE_SPAN || peek() == Token::TEMPLATE_TAIL);
bool forbid_illegal_escapes = !allow_harmony_template_escapes() || !tagged;
// If we reach a TEMPLATE_TAIL first, we are parsing a NoSubstitutionTemplate. // If we reach a TEMPLATE_TAIL first, we are parsing a NoSubstitutionTemplate.
// In this case we may simply consume the token and build a template with a // In this case we may simply consume the token and build a template with a
// single TEMPLATE_SPAN and no expressions. // single TEMPLATE_SPAN and no expressions.
if (peek() == Token::TEMPLATE_TAIL) { if (peek() == Token::TEMPLATE_TAIL) {
Consume(Token::TEMPLATE_TAIL); Consume(Token::TEMPLATE_TAIL);
int pos = position(); int pos = position();
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
typename Impl::TemplateLiteralState ts = impl()->OpenTemplateLiteral(pos); typename Impl::TemplateLiteralState ts = impl()->OpenTemplateLiteral(pos);
impl()->AddTemplateSpan(&ts, true); bool is_valid = CheckTemplateEscapes(forbid_illegal_escapes, CHECK_OK);
impl()->AddTemplateSpan(&ts, is_valid, true);
return impl()->CloseTemplateLiteral(&ts, start, tag); return impl()->CloseTemplateLiteral(&ts, start, tag);
} }
Consume(Token::TEMPLATE_SPAN); Consume(Token::TEMPLATE_SPAN);
int pos = position(); int pos = position();
typename Impl::TemplateLiteralState ts = impl()->OpenTemplateLiteral(pos); typename Impl::TemplateLiteralState ts = impl()->OpenTemplateLiteral(pos);
impl()->AddTemplateSpan(&ts, false); bool is_valid = CheckTemplateEscapes(forbid_illegal_escapes, CHECK_OK);
impl()->AddTemplateSpan(&ts, is_valid, false);
Token::Value next; Token::Value next;
// If we open with a TEMPLATE_SPAN, we must scan the subsequent expression, // If we open with a TEMPLATE_SPAN, we must scan the subsequent expression,
...@@ -4412,7 +4428,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral( ...@@ -4412,7 +4428,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
// case, representing a TemplateMiddle). // case, representing a TemplateMiddle).
do { do {
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
next = peek(); next = peek();
if (next == Token::EOS) { if (next == Token::EOS) {
impl()->ReportMessageAt(Scanner::Location(start, peek_position()), impl()->ReportMessageAt(Scanner::Location(start, peek_position()),
...@@ -4458,11 +4473,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral( ...@@ -4458,11 +4473,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
return impl()->EmptyExpression(); return impl()->EmptyExpression();
} }
impl()->AddTemplateSpan(&ts, next == Token::TEMPLATE_TAIL); bool is_valid = CheckTemplateEscapes(forbid_illegal_escapes, CHECK_OK);
impl()->AddTemplateSpan(&ts, is_valid, next == Token::TEMPLATE_TAIL);
} while (next == Token::TEMPLATE_SPAN); } while (next == Token::TEMPLATE_SPAN);
DCHECK_EQ(next, Token::TEMPLATE_TAIL); DCHECK_EQ(next, Token::TEMPLATE_TAIL);
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
// Once we've reached a TEMPLATE_TAIL, we can close the TemplateLiteral. // Once we've reached a TEMPLATE_TAIL, we can close the TemplateLiteral.
return impl()->CloseTemplateLiteral(&ts, start, tag); return impl()->CloseTemplateLiteral(&ts, start, tag);
} }
......
...@@ -552,6 +552,7 @@ Parser::Parser(ParseInfo* info) ...@@ -552,6 +552,7 @@ Parser::Parser(ParseInfo* info)
set_allow_harmony_object_rest_spread(FLAG_harmony_object_rest_spread); set_allow_harmony_object_rest_spread(FLAG_harmony_object_rest_spread);
set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import); set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import);
set_allow_harmony_async_iteration(FLAG_harmony_async_iteration); set_allow_harmony_async_iteration(FLAG_harmony_async_iteration);
set_allow_harmony_template_escapes(FLAG_harmony_template_escapes);
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount; for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) { ++feature) {
use_counts_[feature] = 0; use_counts_[feature] = 0;
...@@ -3496,15 +3497,20 @@ Parser::TemplateLiteralState Parser::OpenTemplateLiteral(int pos) { ...@@ -3496,15 +3497,20 @@ Parser::TemplateLiteralState Parser::OpenTemplateLiteral(int pos) {
return new (zone()) TemplateLiteral(zone(), pos); return new (zone()) TemplateLiteral(zone(), pos);
} }
void Parser::AddTemplateSpan(TemplateLiteralState* state, bool should_cook,
void Parser::AddTemplateSpan(TemplateLiteralState* state, bool tail) { bool tail) {
DCHECK(should_cook || allow_harmony_template_escapes());
int pos = scanner()->location().beg_pos; int pos = scanner()->location().beg_pos;
int end = scanner()->location().end_pos - (tail ? 1 : 2); int end = scanner()->location().end_pos - (tail ? 1 : 2);
const AstRawString* tv = scanner()->CurrentSymbol(ast_value_factory());
const AstRawString* trv = scanner()->CurrentRawSymbol(ast_value_factory()); const AstRawString* trv = scanner()->CurrentRawSymbol(ast_value_factory());
Literal* cooked = factory()->NewStringLiteral(tv, pos);
Literal* raw = factory()->NewStringLiteral(trv, pos); Literal* raw = factory()->NewStringLiteral(trv, pos);
(*state)->AddTemplateSpan(cooked, raw, end, zone()); if (should_cook) {
const AstRawString* tv = scanner()->CurrentSymbol(ast_value_factory());
Literal* cooked = factory()->NewStringLiteral(tv, pos);
(*state)->AddTemplateSpan(cooked, raw, end, zone());
} else {
(*state)->AddTemplateSpan(GetLiteralUndefined(pos), raw, end, zone());
}
} }
......
...@@ -598,7 +598,15 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) { ...@@ -598,7 +598,15 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
typedef TemplateLiteral* TemplateLiteralState; typedef TemplateLiteral* TemplateLiteralState;
TemplateLiteralState OpenTemplateLiteral(int pos); TemplateLiteralState OpenTemplateLiteral(int pos);
void AddTemplateSpan(TemplateLiteralState* state, bool tail); // "should_cook" means that the span can be "cooked": in tagged template
// literals, both the raw and "cooked" representations are available to user
// code ("cooked" meaning that escape sequences are converted to their
// interpreted values). With the --harmony-template-escapes flag, invalid
// escape sequences cause the cooked span to be represented by undefined,
// instead of being a syntax error.
// "tail" indicates that this span is the last in the literal.
void AddTemplateSpan(TemplateLiteralState* state, bool should_cook,
bool tail);
void AddTemplateExpression(TemplateLiteralState* state, void AddTemplateExpression(TemplateLiteralState* state,
Expression* expression); Expression* expression);
Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start, Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start,
......
...@@ -1005,7 +1005,8 @@ class PreParser : public ParserBase<PreParser> { ...@@ -1005,7 +1005,8 @@ class PreParser : public ParserBase<PreParser> {
} }
V8_INLINE void AddTemplateExpression(TemplateLiteralState* state, V8_INLINE void AddTemplateExpression(TemplateLiteralState* state,
PreParserExpression expression) {} PreParserExpression expression) {}
V8_INLINE void AddTemplateSpan(TemplateLiteralState* state, bool tail) {} V8_INLINE void AddTemplateSpan(TemplateLiteralState* state, bool should_cook,
bool tail) {}
V8_INLINE PreParserExpression CloseTemplateLiteral( V8_INLINE PreParserExpression CloseTemplateLiteral(
TemplateLiteralState* state, int start, PreParserExpression tag); TemplateLiteralState* state, int start, PreParserExpression tag);
V8_INLINE void CheckConflictingVarDeclarations(Scope* scope, bool* ok) {} V8_INLINE void CheckConflictingVarDeclarations(Scope* scope, bool* ok) {}
......
...@@ -19,6 +19,46 @@ ...@@ -19,6 +19,46 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
// Scoped helper for saving & restoring scanner error state.
// This is used for tagged template literals, in which normally forbidden
// escape sequences are allowed.
class ErrorState {
public:
ErrorState(MessageTemplate::Template* message_stack,
Scanner::Location* location_stack)
: message_stack_(message_stack),
old_message_(*message_stack),
location_stack_(location_stack),
old_location_(*location_stack) {
*message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid();
}
~ErrorState() {
*message_stack_ = old_message_;
*location_stack_ = old_location_;
}
void MoveErrorTo(MessageTemplate::Template* message_dest,
Scanner::Location* location_dest) {
if (*message_stack_ == MessageTemplate::kNone) {
return;
}
if (*message_dest == MessageTemplate::kNone) {
*message_dest = *message_stack_;
*location_dest = *location_stack_;
}
*message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid();
}
private:
MessageTemplate::Template* const message_stack_;
MessageTemplate::Template const old_message_;
Scanner::Location* const location_stack_;
Scanner::Location const old_location_;
};
Handle<String> Scanner::LiteralBuffer::Internalize(Isolate* isolate) const { Handle<String> Scanner::LiteralBuffer::Internalize(Isolate* isolate) const {
if (is_one_byte()) { if (is_one_byte()) {
return isolate->factory()->InternalizeOneByteString(one_byte_literal()); return isolate->factory()->InternalizeOneByteString(one_byte_literal());
...@@ -948,16 +988,12 @@ bool Scanner::ScanEscape() { ...@@ -948,16 +988,12 @@ bool Scanner::ScanEscape() {
break; break;
} }
// According to ECMA-262, section 7.8.4, characters not covered by the // Other escaped characters are interpreted as their non-escaped version.
// above cases should be illegal, but they are commonly handled as
// non-escaped characters by JS VMs.
AddLiteralChar(c); AddLiteralChar(c);
return true; return true;
} }
// Octal escapes of the forms '\0xx' and '\xxx' are not a part of
// ECMA-262. Other JS VMs support them.
template <bool capture_raw> template <bool capture_raw>
uc32 Scanner::ScanOctalEscape(uc32 c, int length) { uc32 Scanner::ScanOctalEscape(uc32 c, int length) {
uc32 x = c - '0'; uc32 x = c - '0';
...@@ -1039,6 +1075,12 @@ Token::Value Scanner::ScanTemplateSpan() { ...@@ -1039,6 +1075,12 @@ Token::Value Scanner::ScanTemplateSpan() {
// TEMPLATE_TAIL terminates a TemplateLiteral and does not need to be // TEMPLATE_TAIL terminates a TemplateLiteral and does not need to be
// followed by an Expression. // followed by an Expression.
// These scoped helpers save and restore the original error state, so that we
// can specially treat invalid escape sequences in templates (which are
// handled by the parser).
ErrorState scanner_error_state(&scanner_error_, &scanner_error_location_);
ErrorState octal_error_state(&octal_message_, &octal_pos_);
Token::Value result = Token::TEMPLATE_SPAN; Token::Value result = Token::TEMPLATE_SPAN;
LiteralScope literal(this); LiteralScope literal(this);
StartRawLiteral(); StartRawLiteral();
...@@ -1069,8 +1111,16 @@ Token::Value Scanner::ScanTemplateSpan() { ...@@ -1069,8 +1111,16 @@ Token::Value Scanner::ScanTemplateSpan() {
AddRawLiteralChar('\n'); AddRawLiteralChar('\n');
} }
} }
} else if (!ScanEscape<capture_raw, in_template_literal>()) { } else {
return Token::ILLEGAL; bool success = ScanEscape<capture_raw, in_template_literal>();
USE(success);
DCHECK_EQ(!success, has_error());
// For templates, invalid escape sequence checking is handled in the
// parser.
scanner_error_state.MoveErrorTo(&invalid_template_escape_message_,
&invalid_template_escape_location_);
octal_error_state.MoveErrorTo(&invalid_template_escape_message_,
&invalid_template_escape_location_);
} }
} else if (c < 0) { } else if (c < 0) {
// Unterminated template literal // Unterminated template literal
...@@ -1095,6 +1145,7 @@ Token::Value Scanner::ScanTemplateSpan() { ...@@ -1095,6 +1145,7 @@ Token::Value Scanner::ScanTemplateSpan() {
literal.Complete(); literal.Complete();
next_.location.end_pos = source_pos(); next_.location.end_pos = source_pos();
next_.token = result; next_.token = result;
return result; return result;
} }
......
...@@ -209,10 +209,27 @@ class Scanner { ...@@ -209,10 +209,27 @@ class Scanner {
// (the token last returned by Next()). // (the token last returned by Next()).
Location location() const { return current_.location; } Location location() const { return current_.location; }
// This error is specifically an invalid hex or unicode escape sequence.
bool has_error() const { return scanner_error_ != MessageTemplate::kNone; } bool has_error() const { return scanner_error_ != MessageTemplate::kNone; }
MessageTemplate::Template error() const { return scanner_error_; } MessageTemplate::Template error() const { return scanner_error_; }
Location error_location() const { return scanner_error_location_; } Location error_location() const { return scanner_error_location_; }
bool has_invalid_template_escape() const {
return invalid_template_escape_message_ != MessageTemplate::kNone;
}
MessageTemplate::Template invalid_template_escape_message() const {
return invalid_template_escape_message_;
}
Location invalid_template_escape_location() const {
return invalid_template_escape_location_;
}
void clear_invalid_template_escape() {
DCHECK(has_invalid_template_escape());
invalid_template_escape_message_ = MessageTemplate::kNone;
invalid_template_escape_location_ = Location::invalid();
}
// Similar functions for the upcoming token. // Similar functions for the upcoming token.
// One token look-ahead (past the token returned by Next()). // One token look-ahead (past the token returned by Next()).
...@@ -466,6 +483,7 @@ class Scanner { ...@@ -466,6 +483,7 @@ class Scanner {
next_next_.raw_literal_chars = NULL; next_next_.raw_literal_chars = NULL;
found_html_comment_ = false; found_html_comment_ = false;
scanner_error_ = MessageTemplate::kNone; scanner_error_ = MessageTemplate::kNone;
invalid_template_escape_message_ = MessageTemplate::kNone;
} }
void ReportScannerError(const Location& location, void ReportScannerError(const Location& location,
...@@ -756,6 +774,9 @@ class Scanner { ...@@ -756,6 +774,9 @@ class Scanner {
MessageTemplate::Template scanner_error_; MessageTemplate::Template scanner_error_;
Location scanner_error_location_; Location scanner_error_location_;
MessageTemplate::Template invalid_template_escape_message_;
Location invalid_template_escape_location_;
}; };
} // namespace internal } // namespace internal
......
...@@ -1263,6 +1263,7 @@ enum ParserFlag { ...@@ -1263,6 +1263,7 @@ enum ParserFlag {
kAllowHarmonyObjectRestSpread, kAllowHarmonyObjectRestSpread,
kAllowHarmonyDynamicImport, kAllowHarmonyDynamicImport,
kAllowHarmonyAsyncIteration, kAllowHarmonyAsyncIteration,
kAllowHarmonyTemplateEscapes,
}; };
enum ParserSyncTestResult { enum ParserSyncTestResult {
...@@ -1282,6 +1283,8 @@ void SetGlobalFlags(i::EnumSet<ParserFlag> flags) { ...@@ -1282,6 +1283,8 @@ void SetGlobalFlags(i::EnumSet<ParserFlag> flags) {
flags.Contains(kAllowHarmonyObjectRestSpread); flags.Contains(kAllowHarmonyObjectRestSpread);
i::FLAG_harmony_dynamic_import = flags.Contains(kAllowHarmonyDynamicImport); i::FLAG_harmony_dynamic_import = flags.Contains(kAllowHarmonyDynamicImport);
i::FLAG_harmony_async_iteration = flags.Contains(kAllowHarmonyAsyncIteration); i::FLAG_harmony_async_iteration = flags.Contains(kAllowHarmonyAsyncIteration);
i::FLAG_harmony_template_escapes =
flags.Contains(kAllowHarmonyTemplateEscapes);
} }
void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) { void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
...@@ -1300,6 +1303,8 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) { ...@@ -1300,6 +1303,8 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
flags.Contains(kAllowHarmonyDynamicImport)); flags.Contains(kAllowHarmonyDynamicImport));
parser->set_allow_harmony_async_iteration( parser->set_allow_harmony_async_iteration(
flags.Contains(kAllowHarmonyAsyncIteration)); flags.Contains(kAllowHarmonyAsyncIteration));
parser->set_allow_harmony_template_escapes(
flags.Contains(kAllowHarmonyTemplateEscapes));
} }
void TestParserSyncWithFlags(i::Handle<i::String> source, void TestParserSyncWithFlags(i::Handle<i::String> source,
...@@ -7067,6 +7072,7 @@ TEST(ObjectSpreadPositiveTests) { ...@@ -7067,6 +7072,7 @@ TEST(ObjectSpreadPositiveTests) {
"{ ...async () => { }}", "{ ...async () => { }}",
"{ ...new Foo()}", "{ ...new Foo()}",
NULL}; NULL};
// clang-format on
static const ParserFlag flags[] = {kAllowHarmonyObjectRestSpread}; static const ParserFlag flags[] = {kAllowHarmonyObjectRestSpread};
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags, RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
...@@ -7090,6 +7096,161 @@ TEST(ObjectSpreadNegativeTests) { ...@@ -7090,6 +7096,161 @@ TEST(ObjectSpreadNegativeTests) {
arraysize(flags)); arraysize(flags));
} }
TEST(TemplateEscapesPositiveTests) {
// clang-format off
const char* context_data[][2] = {
{"", ""},
{"'use strict';", ""},
{NULL, NULL}};
// clang-format off
const char* data[] = {
"tag`\\01`",
"tag`\\01${0}right`",
"tag`left${0}\\01`",
"tag`left${0}\\01${1}right`",
"tag`\\1`",
"tag`\\1${0}right`",
"tag`left${0}\\1`",
"tag`left${0}\\1${1}right`",
"tag`\\xg`",
"tag`\\xg${0}right`",
"tag`left${0}\\xg`",
"tag`left${0}\\xg${1}right`",
"tag`\\xAg`",
"tag`\\xAg${0}right`",
"tag`left${0}\\xAg`",
"tag`left${0}\\xAg${1}right`",
"tag`\\u0`",
"tag`\\u0${0}right`",
"tag`left${0}\\u0`",
"tag`left${0}\\u0${1}right`",
"tag`\\u0g`",
"tag`\\u0g${0}right`",
"tag`left${0}\\u0g`",
"tag`left${0}\\u0g${1}right`",
"tag`\\u00g`",
"tag`\\u00g${0}right`",
"tag`left${0}\\u00g`",
"tag`left${0}\\u00g${1}right`",
"tag`\\u000g`",
"tag`\\u000g${0}right`",
"tag`left${0}\\u000g`",
"tag`left${0}\\u000g${1}right`",
"tag`\\u{}`",
"tag`\\u{}${0}right`",
"tag`left${0}\\u{}`",
"tag`left${0}\\u{}${1}right`",
"tag`\\u{-0}`",
"tag`\\u{-0}${0}right`",
"tag`left${0}\\u{-0}`",
"tag`left${0}\\u{-0}${1}right`",
"tag`\\u{g}`",
"tag`\\u{g}${0}right`",
"tag`left${0}\\u{g}`",
"tag`left${0}\\u{g}${1}right`",
"tag`\\u{0`",
"tag`\\u{0${0}right`",
"tag`left${0}\\u{0`",
"tag`left${0}\\u{0${1}right`",
"tag`\\u{\\u{0}`",
"tag`\\u{\\u{0}${0}right`",
"tag`left${0}\\u{\\u{0}`",
"tag`left${0}\\u{\\u{0}${1}right`",
"tag`\\u{110000}`",
"tag`\\u{110000}${0}right`",
"tag`left${0}\\u{110000}`",
"tag`left${0}\\u{110000}${1}right`",
NULL};
// clang-format on
// No error with flag
static const ParserFlag flags[] = {kAllowHarmonyTemplateEscapes};
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
arraysize(flags));
// Still an error without flag
RunParserSyncTest(context_data, data, kError);
}
TEST(TemplateEscapesNegativeTests) {
// clang-format off
const char* context_data[][2] = {
{"", ""},
{"'use strict';", ""},
{NULL, NULL}};
// clang-format off
const char* data[] = {
"`\\01`",
"`\\01${0}right`",
"`left${0}\\01`",
"`left${0}\\01${1}right`",
"`\\1`",
"`\\1${0}right`",
"`left${0}\\1`",
"`left${0}\\1${1}right`",
"`\\xg`",
"`\\xg${0}right`",
"`left${0}\\xg`",
"`left${0}\\xg${1}right`",
"`\\xAg`",
"`\\xAg${0}right`",
"`left${0}\\xAg`",
"`left${0}\\xAg${1}right`",
"`\\u0`",
"`\\u0${0}right`",
"`left${0}\\u0`",
"`left${0}\\u0${1}right`",
"`\\u0g`",
"`\\u0g${0}right`",
"`left${0}\\u0g`",
"`left${0}\\u0g${1}right`",
"`\\u00g`",
"`\\u00g${0}right`",
"`left${0}\\u00g`",
"`left${0}\\u00g${1}right`",
"`\\u000g`",
"`\\u000g${0}right`",
"`left${0}\\u000g`",
"`left${0}\\u000g${1}right`",
"`\\u{}`",
"`\\u{}${0}right`",
"`left${0}\\u{}`",
"`left${0}\\u{}${1}right`",
"`\\u{-0}`",
"`\\u{-0}${0}right`",
"`left${0}\\u{-0}`",
"`left${0}\\u{-0}${1}right`",
"`\\u{g}`",
"`\\u{g}${0}right`",
"`left${0}\\u{g}`",
"`left${0}\\u{g}${1}right`",
"`\\u{0`",
"`\\u{0${0}right`",
"`left${0}\\u{0`",
"`left${0}\\u{0${1}right`",
"`\\u{\\u{0}`",
"`\\u{\\u{0}${0}right`",
"`left${0}\\u{\\u{0}`",
"`left${0}\\u{\\u{0}${1}right`",
"`\\u{110000}`",
"`\\u{110000}${0}right`",
"`left${0}\\u{110000}`",
"`left${0}\\u{110000}${1}right`",
"`\\1``\\2`",
NULL};
// clang-format on
// Error with flag
static const ParserFlag flags[] = {kAllowHarmonyTemplateEscapes};
RunParserSyncTest(context_data, data, kError, NULL, 0, flags,
arraysize(flags));
// Still an error without flag
RunParserSyncTest(context_data, data, kError);
}
TEST(DestructuringPositiveTests) { TEST(DestructuringPositiveTests) {
const char* context_data[][2] = {{"'use strict'; let ", " = {};"}, const char* context_data[][2] = {{"'use strict'; let ", " = {};"},
{"var ", " = {};"}, {"var ", " = {};"},
......
...@@ -71,6 +71,10 @@ assertThrows('"\\u111G"'); ...@@ -71,6 +71,10 @@ assertThrows('"\\u111G"');
assertEquals("\\x1G", /\x1G/.source); assertEquals("\\x1G", /\x1G/.source);
assertEquals("\\u111G", /\u111G/.source); assertEquals("\\u111G", /\u111G/.source);
// Test that octal literals continue to be forbidden in template even
// when followed by a string containing an octal literal.
assertThrows('`\\1`\n"\\1"');
// Test some materialized array literals. // Test some materialized array literals.
assertEquals([1,2,3,4], eval('[1,2,3,4]')); assertEquals([1,2,3,4], eval('[1,2,3,4]'));
assertEquals([[1,2],3,4], eval('[[1,2],3,4]')); assertEquals([[1,2],3,4], eval('[[1,2],3,4]'));
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-template-escapes
function check({cooked, raw, exprs}) {
return function(strs, ...args) {
assertArrayEquals(cooked, strs);
assertArrayEquals(raw, strs.raw);
assertArrayEquals(exprs, args);
};
}
// clang-format off
check({
'cooked': [
undefined
],
'raw': [
'\\01'
],
'exprs': []
})`\01`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\01',
'right'
],
'exprs': [
0
]
})`\01${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\01'
],
'exprs': [
0
]
})`left${0}\01`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\01',
'right'
],
'exprs': [
0,
1
]
})`left${0}\01${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\1'
],
'exprs': []
})`\1`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\1',
'right'
],
'exprs': [
0
]
})`\1${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\1'
],
'exprs': [
0
]
})`left${0}\1`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\1',
'right'
],
'exprs': [
0,
1
]
})`left${0}\1${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\xg'
],
'exprs': []
})`\xg`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\xg',
'right'
],
'exprs': [
0
]
})`\xg${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\xg'
],
'exprs': [
0
]
})`left${0}\xg`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\xg',
'right'
],
'exprs': [
0,
1
]
})`left${0}\xg${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\xAg'
],
'exprs': []
})`\xAg`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\xAg',
'right'
],
'exprs': [
0
]
})`\xAg${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\xAg'
],
'exprs': [
0
]
})`left${0}\xAg`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\xAg',
'right'
],
'exprs': [
0,
1
]
})`left${0}\xAg${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u0'
],
'exprs': []
})`\u0`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u0',
'right'
],
'exprs': [
0
]
})`\u0${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u0'
],
'exprs': [
0
]
})`left${0}\u0`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u0',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u0${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u0g'
],
'exprs': []
})`\u0g`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u0g',
'right'
],
'exprs': [
0
]
})`\u0g${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u0g'
],
'exprs': [
0
]
})`left${0}\u0g`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u0g',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u0g${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u00g'
],
'exprs': []
})`\u00g`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u00g',
'right'
],
'exprs': [
0
]
})`\u00g${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u00g'
],
'exprs': [
0
]
})`left${0}\u00g`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u00g',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u00g${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u000g'
],
'exprs': []
})`\u000g`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u000g',
'right'
],
'exprs': [
0
]
})`\u000g${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u000g'
],
'exprs': [
0
]
})`left${0}\u000g`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u000g',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u000g${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{}'
],
'exprs': []
})`\u{}`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{}',
'right'
],
'exprs': [
0
]
})`\u{}${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{}'
],
'exprs': [
0
]
})`left${0}\u{}`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{}',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{}${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{-0}'
],
'exprs': []
})`\u{-0}`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{-0}',
'right'
],
'exprs': [
0
]
})`\u{-0}${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{-0}'
],
'exprs': [
0
]
})`left${0}\u{-0}`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{-0}',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{-0}${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{g}'
],
'exprs': []
})`\u{g}`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{g}',
'right'
],
'exprs': [
0
]
})`\u{g}${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{g}'
],
'exprs': [
0
]
})`left${0}\u{g}`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{g}',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{g}${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{0'
],
'exprs': []
})`\u{0`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{0',
'right'
],
'exprs': [
0
]
})`\u{0${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{0'
],
'exprs': [
0
]
})`left${0}\u{0`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{0',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{0${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{\\u{0}'
],
'exprs': []
})`\u{\u{0}`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{\\u{0}',
'right'
],
'exprs': [
0
]
})`\u{\u{0}${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{\\u{0}'
],
'exprs': [
0
]
})`left${0}\u{\u{0}`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{\\u{0}',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{\u{0}${1}right`;
check({
'cooked': [
undefined
],
'raw': [
'\\u{110000}'
],
'exprs': []
})`\u{110000}`;
check({
'cooked': [
undefined,
'right'
],
'raw': [
'\\u{110000}',
'right'
],
'exprs': [
0
]
})`\u{110000}${0}right`;
check({
'cooked': [
'left',
undefined
],
'raw': [
'left',
'\\u{110000}'
],
'exprs': [
0
]
})`left${0}\u{110000}`;
check({
'cooked': [
'left',
undefined,
'right'
],
'raw': [
'left',
'\\u{110000}',
'right'
],
'exprs': [
0,
1
]
})`left${0}\u{110000}${1}right`;
...@@ -396,7 +396,7 @@ ...@@ -396,7 +396,7 @@
'annexB/language/eval-code/direct/func-switch-dflt-eval-func-no-skip-try': [FAIL], 'annexB/language/eval-code/direct/func-switch-dflt-eval-func-no-skip-try': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=5546 # https://bugs.chromium.org/p/v8/issues/detail?id=5546
'language/expressions/tagged-template/invalid-escape-sequences': [FAIL], 'language/expressions/tagged-template/invalid-escape-sequences': ['--harmony-template-escapes'],
# https://bugs.chromium.org/p/v8/issues/detail?id=5537 # https://bugs.chromium.org/p/v8/issues/detail?id=5537
'built-ins/global/*': [SKIP], 'built-ins/global/*': [SKIP],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment