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)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_fields)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_rest_spread)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_template_escapes)
void InstallPublicSymbol(Factory* factory, Handle<Context> native_context,
const char* name, Handle<Symbol> value) {
......@@ -4242,6 +4243,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_async_iteration_natives[] = {nullptr};
static const char* harmony_dynamic_import_natives[] = {nullptr};
static const char* harmony_promise_finally_natives[] = {nullptr};
static const char* harmony_template_escapes_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) {
......
......@@ -209,11 +209,13 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
V(harmony_promise_finally, "harmony Promise.prototype.finally")
// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \
V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \
V(harmony_restrictive_generators, \
"harmony restrictions on generator declarations") \
V(harmony_object_rest_spread, "harmony object rest spread properties")
#define HARMONY_STAGED(V) \
V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \
V(harmony_restrictive_generators, \
"harmony restrictions on generator declarations") \
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).
#define HARMONY_SHIPPING_BASE(V) \
......
......@@ -226,7 +226,8 @@ class ParserBase {
allow_harmony_class_fields_(false),
allow_harmony_object_rest_spread_(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) \
bool allow_##name() const { return allow_##name##_; } \
......@@ -242,6 +243,7 @@ class ParserBase {
ALLOW_ACCESSORS(harmony_object_rest_spread);
ALLOW_ACCESSORS(harmony_dynamic_import);
ALLOW_ACCESSORS(harmony_async_iteration);
ALLOW_ACCESSORS(harmony_template_escapes);
#undef ALLOW_ACCESSORS
......@@ -816,14 +818,12 @@ class ParserBase {
}
// 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.
void CheckOctalLiteral(int beg_pos, int end_pos, bool is_template, bool* ok) {
// Only called for strict mode strings.
void CheckStrictOctalLiteral(int beg_pos, int end_pos, bool* ok) {
Scanner::Location octal = scanner()->octal_position();
if (octal.IsValid() && beg_pos <= octal.beg_pos &&
octal.end_pos <= end_pos) {
MessageTemplate::Template message =
is_template ? MessageTemplate::kTemplateOctalLiteral
: scanner()->octal_message();
MessageTemplate::Template message = scanner()->octal_message();
DCHECK_NE(message, MessageTemplate::kNone);
impl()->ReportMessageAt(octal, message);
scanner()->clear_octal_position();
......@@ -834,12 +834,23 @@ class ParserBase {
}
}
inline void CheckStrictOctalLiteral(int beg_pos, int end_pos, bool* ok) {
CheckOctalLiteral(beg_pos, end_pos, false, ok);
}
// Checks if an octal literal or an invalid hex or unicode escape sequence
// 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) {
CheckOctalLiteral(beg_pos, end_pos, true, ok);
// Handle error case(s)
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);
......@@ -1138,7 +1149,8 @@ class ParserBase {
Scanner::Location class_name_location,
bool name_is_strict_reserved,
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 ParseDynamicImportExpression(bool* ok);
ExpressionT ParseNewTargetExpression(bool* ok);
......@@ -1449,6 +1461,7 @@ class ParserBase {
bool allow_harmony_object_rest_spread_;
bool allow_harmony_dynamic_import_;
bool allow_harmony_async_iteration_;
bool allow_harmony_template_escapes_;
friend class DiscardableZoneScope;
};
......@@ -1817,7 +1830,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParsePrimaryExpression(
case Token::TEMPLATE_SPAN:
case Token::TEMPLATE_TAIL:
BindingPatternUnexpectedToken();
return ParseTemplateLiteral(impl()->NoTemplateTag(), beg_pos, ok);
return ParseTemplateLiteral(impl()->NoTemplateTag(), beg_pos, false, ok);
case Token::MOD:
if (allow_natives() || extension_ != NULL) {
......@@ -3215,7 +3228,7 @@ ParserBase<Impl>::ParseLeftHandSideExpression(bool* ok) {
impl()->RewriteNonPattern(CHECK_OK);
BindingPatternUnexpectedToken();
ArrowFormalParametersUnexpectedToken();
result = ParseTemplateLiteral(result, position(), CHECK_OK);
result = ParseTemplateLiteral(result, position(), true, CHECK_OK);
break;
}
......@@ -3495,7 +3508,7 @@ ParserBase<Impl>::ParseMemberExpressionContinuation(ExpressionT expression,
expression->AsFunctionLiteral()->SetShouldEagerCompile();
}
}
expression = ParseTemplateLiteral(expression, pos, CHECK_OK);
expression = ParseTemplateLiteral(expression, pos, true, CHECK_OK);
break;
}
case Token::ILLEGAL: {
......@@ -4376,7 +4389,7 @@ ParserBase<Impl>::ParseAsyncFunctionLiteral(bool* ok) {
template <typename Impl>
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
// text followed by a substitution expression), finalized by a single
// TEMPLATE_TAIL.
......@@ -4389,22 +4402,25 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
// TEMPLATE_SPAN, or a 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.
// In this case we may simply consume the token and build a template with a
// single TEMPLATE_SPAN and no expressions.
if (peek() == Token::TEMPLATE_TAIL) {
Consume(Token::TEMPLATE_TAIL);
int pos = position();
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
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);
}
Consume(Token::TEMPLATE_SPAN);
int pos = position();
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;
// If we open with a TEMPLATE_SPAN, we must scan the subsequent expression,
......@@ -4412,7 +4428,6 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
// case, representing a TemplateMiddle).
do {
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
next = peek();
if (next == Token::EOS) {
impl()->ReportMessageAt(Scanner::Location(start, peek_position()),
......@@ -4458,11 +4473,11 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseTemplateLiteral(
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);
DCHECK_EQ(next, Token::TEMPLATE_TAIL);
CheckTemplateOctalLiteral(pos, peek_position(), CHECK_OK);
// Once we've reached a TEMPLATE_TAIL, we can close the TemplateLiteral.
return impl()->CloseTemplateLiteral(&ts, start, tag);
}
......
......@@ -552,6 +552,7 @@ Parser::Parser(ParseInfo* info)
set_allow_harmony_object_rest_spread(FLAG_harmony_object_rest_spread);
set_allow_harmony_dynamic_import(FLAG_harmony_dynamic_import);
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;
++feature) {
use_counts_[feature] = 0;
......@@ -3496,15 +3497,20 @@ Parser::TemplateLiteralState Parser::OpenTemplateLiteral(int pos) {
return new (zone()) TemplateLiteral(zone(), pos);
}
void Parser::AddTemplateSpan(TemplateLiteralState* state, bool tail) {
void Parser::AddTemplateSpan(TemplateLiteralState* state, bool should_cook,
bool tail) {
DCHECK(should_cook || allow_harmony_template_escapes());
int pos = scanner()->location().beg_pos;
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());
Literal* cooked = factory()->NewStringLiteral(tv, 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>) {
typedef TemplateLiteral* TemplateLiteralState;
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,
Expression* expression);
Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start,
......
......@@ -1005,7 +1005,8 @@ class PreParser : public ParserBase<PreParser> {
}
V8_INLINE void AddTemplateExpression(TemplateLiteralState* state,
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(
TemplateLiteralState* state, int start, PreParserExpression tag);
V8_INLINE void CheckConflictingVarDeclarations(Scope* scope, bool* ok) {}
......
......@@ -19,6 +19,46 @@
namespace v8 {
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 {
if (is_one_byte()) {
return isolate->factory()->InternalizeOneByteString(one_byte_literal());
......@@ -948,16 +988,12 @@ bool Scanner::ScanEscape() {
break;
}
// According to ECMA-262, section 7.8.4, characters not covered by the
// above cases should be illegal, but they are commonly handled as
// non-escaped characters by JS VMs.
// Other escaped characters are interpreted as their non-escaped version.
AddLiteralChar(c);
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>
uc32 Scanner::ScanOctalEscape(uc32 c, int length) {
uc32 x = c - '0';
......@@ -1039,6 +1075,12 @@ Token::Value Scanner::ScanTemplateSpan() {
// TEMPLATE_TAIL terminates a TemplateLiteral and does not need to be
// 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;
LiteralScope literal(this);
StartRawLiteral();
......@@ -1069,8 +1111,16 @@ Token::Value Scanner::ScanTemplateSpan() {
AddRawLiteralChar('\n');
}
}
} else if (!ScanEscape<capture_raw, in_template_literal>()) {
return Token::ILLEGAL;
} else {
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) {
// Unterminated template literal
......@@ -1095,6 +1145,7 @@ Token::Value Scanner::ScanTemplateSpan() {
literal.Complete();
next_.location.end_pos = source_pos();
next_.token = result;
return result;
}
......
......@@ -209,10 +209,27 @@ class Scanner {
// (the token last returned by Next()).
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; }
MessageTemplate::Template error() const { return scanner_error_; }
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.
// One token look-ahead (past the token returned by Next()).
......@@ -466,6 +483,7 @@ class Scanner {
next_next_.raw_literal_chars = NULL;
found_html_comment_ = false;
scanner_error_ = MessageTemplate::kNone;
invalid_template_escape_message_ = MessageTemplate::kNone;
}
void ReportScannerError(const Location& location,
......@@ -756,6 +774,9 @@ class Scanner {
MessageTemplate::Template scanner_error_;
Location scanner_error_location_;
MessageTemplate::Template invalid_template_escape_message_;
Location invalid_template_escape_location_;
};
} // namespace internal
......
......@@ -1263,6 +1263,7 @@ enum ParserFlag {
kAllowHarmonyObjectRestSpread,
kAllowHarmonyDynamicImport,
kAllowHarmonyAsyncIteration,
kAllowHarmonyTemplateEscapes,
};
enum ParserSyncTestResult {
......@@ -1282,6 +1283,8 @@ void SetGlobalFlags(i::EnumSet<ParserFlag> flags) {
flags.Contains(kAllowHarmonyObjectRestSpread);
i::FLAG_harmony_dynamic_import = flags.Contains(kAllowHarmonyDynamicImport);
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) {
......@@ -1300,6 +1303,8 @@ void SetParserFlags(i::PreParser* parser, i::EnumSet<ParserFlag> flags) {
flags.Contains(kAllowHarmonyDynamicImport));
parser->set_allow_harmony_async_iteration(
flags.Contains(kAllowHarmonyAsyncIteration));
parser->set_allow_harmony_template_escapes(
flags.Contains(kAllowHarmonyTemplateEscapes));
}
void TestParserSyncWithFlags(i::Handle<i::String> source,
......@@ -7067,6 +7072,7 @@ TEST(ObjectSpreadPositiveTests) {
"{ ...async () => { }}",
"{ ...new Foo()}",
NULL};
// clang-format on
static const ParserFlag flags[] = {kAllowHarmonyObjectRestSpread};
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, flags,
......@@ -7090,6 +7096,161 @@ TEST(ObjectSpreadNegativeTests) {
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) {
const char* context_data[][2] = {{"'use strict'; let ", " = {};"},
{"var ", " = {};"},
......
......@@ -71,6 +71,10 @@ assertThrows('"\\u111G"');
assertEquals("\\x1G", /\x1G/.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.
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 @@
'annexB/language/eval-code/direct/func-switch-dflt-eval-func-no-skip-try': [FAIL],
# 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
'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