Commit baa74e89 authored by bakkot's avatar bakkot Committed by Commit bot

[parser] Correctly handle invalid escapes in adjacent template tokens.

A previous patch lifting the restriction on invalid escape sequences in
tagged templates had a bug when two template tokens appeared immediately
adject to each other. This moves invalid escape information from the
tokenizer state proper into the TokenDesc, preventing the overwriting
which caused this issue.

Previous CL is at
https://codereview.chromium.org/2665513002

BUG=v8:6029,v8:5546

Review-Url: https://codereview.chromium.org/2724003006
Cr-Commit-Position: refs/heads/master@{#43596}
parent 3a20c322
...@@ -797,10 +797,12 @@ class ParserBase { ...@@ -797,10 +797,12 @@ class ParserBase {
} }
// Checks if an octal literal or an invalid hex or unicode escape sequence // Checks if an octal literal or an invalid hex or unicode escape sequence
// appears in a template literal. In the presence of such, either // appears in the current template literal token. In the presence of such,
// returns false or reports an error, depending on should_throw. Otherwise // either returns false or reports an error, depending on should_throw.
// returns true. // Otherwise returns true.
inline bool CheckTemplateEscapes(bool should_throw, bool* ok) { inline bool CheckTemplateEscapes(bool should_throw, bool* ok) {
DCHECK(scanner()->current_token() == Token::TEMPLATE_SPAN ||
scanner()->current_token() == Token::TEMPLATE_TAIL);
if (!scanner()->has_invalid_template_escape()) { if (!scanner()->has_invalid_template_escape()) {
return true; return true;
} }
...@@ -811,7 +813,6 @@ class ParserBase { ...@@ -811,7 +813,6 @@ class ParserBase {
scanner()->invalid_template_escape_message()); scanner()->invalid_template_escape_message());
*ok = false; *ok = false;
} }
scanner()->clear_invalid_template_escape();
return false; return false;
} }
......
...@@ -19,10 +19,7 @@ ...@@ -19,10 +19,7 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
// Scoped helper for saving & restoring scanner error state. class Scanner::ErrorState {
// This is used for tagged template literals, in which normally forbidden
// escape sequences are allowed.
class ErrorState {
public: public:
ErrorState(MessageTemplate::Template* message_stack, ErrorState(MessageTemplate::Template* message_stack,
Scanner::Location* location_stack) Scanner::Location* location_stack)
...@@ -31,7 +28,7 @@ class ErrorState { ...@@ -31,7 +28,7 @@ class ErrorState {
location_stack_(location_stack), location_stack_(location_stack),
old_location_(*location_stack) { old_location_(*location_stack) {
*message_stack_ = MessageTemplate::kNone; *message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid(); *location_stack_ = Location::invalid();
} }
~ErrorState() { ~ErrorState() {
...@@ -39,17 +36,16 @@ class ErrorState { ...@@ -39,17 +36,16 @@ class ErrorState {
*location_stack_ = old_location_; *location_stack_ = old_location_;
} }
void MoveErrorTo(MessageTemplate::Template* message_dest, void MoveErrorTo(TokenDesc* dest) {
Scanner::Location* location_dest) {
if (*message_stack_ == MessageTemplate::kNone) { if (*message_stack_ == MessageTemplate::kNone) {
return; return;
} }
if (*message_dest == MessageTemplate::kNone) { if (dest->invalid_template_escape_message == MessageTemplate::kNone) {
*message_dest = *message_stack_; dest->invalid_template_escape_message = *message_stack_;
*location_dest = *location_stack_; dest->invalid_template_escape_location = *location_stack_;
} }
*message_stack_ = MessageTemplate::kNone; *message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid(); *location_stack_ = Location::invalid();
} }
private: private:
...@@ -397,6 +393,7 @@ Token::Value Scanner::Next() { ...@@ -397,6 +393,7 @@ Token::Value Scanner::Next() {
next_.location.end_pos = pos + 1; next_.location.end_pos = pos + 1;
next_.literal_chars = nullptr; next_.literal_chars = nullptr;
next_.raw_literal_chars = nullptr; next_.raw_literal_chars = nullptr;
next_.invalid_template_escape_message = MessageTemplate::kNone;
Advance(); Advance();
return current_.token; return current_.token;
} }
...@@ -609,6 +606,7 @@ Token::Value Scanner::ScanHtmlComment() { ...@@ -609,6 +606,7 @@ Token::Value Scanner::ScanHtmlComment() {
void Scanner::Scan() { void Scanner::Scan() {
next_.literal_chars = NULL; next_.literal_chars = NULL;
next_.raw_literal_chars = NULL; next_.raw_literal_chars = NULL;
next_.invalid_template_escape_message = MessageTemplate::kNone;
Token::Value token; Token::Value token;
do { do {
// Remember the position of the next token // Remember the position of the next token
...@@ -889,6 +887,8 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const { ...@@ -889,6 +887,8 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const {
// - TEMPLATE_*: need both literal + raw literal chars. // - TEMPLATE_*: need both literal + raw literal chars.
// - IDENTIFIERS, STRINGS, etc.: need a literal, but no raw literal. // - IDENTIFIERS, STRINGS, etc.: need a literal, but no raw literal.
// - all others: should have neither. // - all others: should have neither.
// Furthermore, only TEMPLATE_* tokens can have a
// invalid_template_escape_message.
switch (token.token) { switch (token.token) {
case Token::UNINITIALIZED: case Token::UNINITIALIZED:
...@@ -909,10 +909,12 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const { ...@@ -909,10 +909,12 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const {
case Token::STRING: case Token::STRING:
DCHECK_NOT_NULL(token.literal_chars); DCHECK_NOT_NULL(token.literal_chars);
DCHECK_NULL(token.raw_literal_chars); DCHECK_NULL(token.raw_literal_chars);
DCHECK_EQ(token.invalid_template_escape_message, MessageTemplate::kNone);
break; break;
default: default:
DCHECK_NULL(token.literal_chars); DCHECK_NULL(token.literal_chars);
DCHECK_NULL(token.raw_literal_chars); DCHECK_NULL(token.raw_literal_chars);
DCHECK_EQ(token.invalid_template_escape_message, MessageTemplate::kNone);
break; break;
} }
} }
...@@ -1117,10 +1119,8 @@ Token::Value Scanner::ScanTemplateSpan() { ...@@ -1117,10 +1119,8 @@ Token::Value Scanner::ScanTemplateSpan() {
DCHECK_EQ(!success, has_error()); DCHECK_EQ(!success, has_error());
// For templates, invalid escape sequence checking is handled in the // For templates, invalid escape sequence checking is handled in the
// parser. // parser.
scanner_error_state.MoveErrorTo(&invalid_template_escape_message_, scanner_error_state.MoveErrorTo(&next_);
&invalid_template_escape_location_); octal_error_state.MoveErrorTo(&next_);
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
...@@ -1736,7 +1736,9 @@ void Scanner::SeekNext(size_t position) { ...@@ -1736,7 +1736,9 @@ void Scanner::SeekNext(size_t position) {
// 1, Reset the current_, next_ and next_next_ tokens // 1, Reset the current_, next_ and next_next_ tokens
// (next_ + next_next_ will be overwrittem by Next(), // (next_ + next_next_ will be overwrittem by Next(),
// current_ will remain unchanged, so overwrite it fully.) // current_ will remain unchanged, so overwrite it fully.)
current_ = {{0, 0}, nullptr, nullptr, 0, Token::UNINITIALIZED}; current_ = {
{0, 0}, nullptr, nullptr, 0, Token::UNINITIALIZED, MessageTemplate::kNone,
{0, 0}};
next_.token = Token::UNINITIALIZED; next_.token = Token::UNINITIALIZED;
next_next_.token = Token::UNINITIALIZED; next_next_.token = Token::UNINITIALIZED;
// 2, reset the source to the desired position, // 2, reset the source to the desired position,
......
...@@ -215,19 +215,15 @@ class Scanner { ...@@ -215,19 +215,15 @@ class Scanner {
Location error_location() const { return scanner_error_location_; } Location error_location() const { return scanner_error_location_; }
bool has_invalid_template_escape() const { bool has_invalid_template_escape() const {
return invalid_template_escape_message_ != MessageTemplate::kNone; return current_.invalid_template_escape_message != MessageTemplate::kNone;
} }
MessageTemplate::Template invalid_template_escape_message() const { MessageTemplate::Template invalid_template_escape_message() const {
return invalid_template_escape_message_; DCHECK(has_invalid_template_escape());
return current_.invalid_template_escape_message;
} }
Location invalid_template_escape_location() const { Location invalid_template_escape_location() const {
return invalid_template_escape_location_;
}
void clear_invalid_template_escape() {
DCHECK(has_invalid_template_escape()); DCHECK(has_invalid_template_escape());
invalid_template_escape_message_ = MessageTemplate::kNone; return current_.invalid_template_escape_location;
invalid_template_escape_location_ = Location::invalid();
} }
// Similar functions for the upcoming token. // Similar functions for the upcoming token.
...@@ -345,6 +341,11 @@ class Scanner { ...@@ -345,6 +341,11 @@ class Scanner {
bool FoundHtmlComment() const { return found_html_comment_; } bool FoundHtmlComment() const { return found_html_comment_; }
private: private:
// 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;
// Scoped helper for literal recording. Automatically drops the literal // Scoped helper for literal recording. Automatically drops the literal
// if aborting the scanning before it's complete. // if aborting the scanning before it's complete.
class LiteralScope { class LiteralScope {
...@@ -457,6 +458,8 @@ class Scanner { ...@@ -457,6 +458,8 @@ class Scanner {
LiteralBuffer* raw_literal_chars; LiteralBuffer* raw_literal_chars;
uint32_t smi_value_; uint32_t smi_value_;
Token::Value token; Token::Value token;
MessageTemplate::Template invalid_template_escape_message;
Location invalid_template_escape_location;
}; };
static const int kCharacterLookaheadBufferSize = 1; static const int kCharacterLookaheadBufferSize = 1;
...@@ -475,15 +478,17 @@ class Scanner { ...@@ -475,15 +478,17 @@ class Scanner {
current_.token = Token::UNINITIALIZED; current_.token = Token::UNINITIALIZED;
current_.literal_chars = NULL; current_.literal_chars = NULL;
current_.raw_literal_chars = NULL; current_.raw_literal_chars = NULL;
current_.invalid_template_escape_message = MessageTemplate::kNone;
next_.token = Token::UNINITIALIZED; next_.token = Token::UNINITIALIZED;
next_.literal_chars = NULL; next_.literal_chars = NULL;
next_.raw_literal_chars = NULL; next_.raw_literal_chars = NULL;
next_.invalid_template_escape_message = MessageTemplate::kNone;
next_next_.token = Token::UNINITIALIZED; next_next_.token = Token::UNINITIALIZED;
next_next_.literal_chars = NULL; next_next_.literal_chars = NULL;
next_next_.raw_literal_chars = NULL; next_next_.raw_literal_chars = NULL;
next_next_.invalid_template_escape_message = MessageTemplate::kNone;
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,
...@@ -774,9 +779,6 @@ class Scanner { ...@@ -774,9 +779,6 @@ 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
......
...@@ -7161,6 +7161,12 @@ TEST(TemplateEscapesPositiveTests) { ...@@ -7161,6 +7161,12 @@ TEST(TemplateEscapesPositiveTests) {
"tag`\\u{110000}${0}right`", "tag`\\u{110000}${0}right`",
"tag`left${0}\\u{110000}`", "tag`left${0}\\u{110000}`",
"tag`left${0}\\u{110000}${1}right`", "tag`left${0}\\u{110000}${1}right`",
"tag` ${tag`\\u`}`",
"tag` ``\\u`",
"tag`\\u`` `",
"tag`\\u``\\u`",
"` ${tag`\\u`}`",
"` ``\\u`",
NULL}; NULL};
// clang-format on // clang-format on
...@@ -7239,6 +7245,8 @@ TEST(TemplateEscapesNegativeTests) { ...@@ -7239,6 +7245,8 @@ TEST(TemplateEscapesNegativeTests) {
"`left${0}\\u{110000}`", "`left${0}\\u{110000}`",
"`left${0}\\u{110000}${1}right`", "`left${0}\\u{110000}${1}right`",
"`\\1``\\2`", "`\\1``\\2`",
"tag` ${`\\u`}`",
"`\\u```",
NULL}; NULL};
// clang-format on // clang-format on
......
...@@ -783,3 +783,90 @@ check({ ...@@ -783,3 +783,90 @@ check({
1 1
] ]
})`left${0}\u{110000}${1}right`; })`left${0}\u{110000}${1}right`;
function checkMultiple(expectedArray) {
let results = [];
return function consume(strs, ...args) {
if (typeof strs === 'undefined') {
assertArrayEquals(expectedArray, results);
} else {
results.push({cooked: strs, raw: strs.raw, exprs: args});
return consume;
}
};
}
checkMultiple([{
'cooked': [
undefined
],
'raw': [
'\\u',
],
'exprs': []
}, {
'cooked': [
undefined
],
'raw': [
'\\u',
],
'exprs': []
}])`\u``\u`();
checkMultiple([{
'cooked': [
' '
],
'raw': [
' ',
],
'exprs': []
}, {
'cooked': [
undefined
],
'raw': [
'\\u',
],
'exprs': []
}])` ``\u`();
checkMultiple([{
'cooked': [
undefined
],
'raw': [
'\\u',
],
'exprs': []
}, {
'cooked': [
' '
],
'raw': [
' ',
],
'exprs': []
}])`\u`` `();
checkMultiple([{
'cooked': [
' '
],
'raw': [
' ',
],
'exprs': []
}, {
'cooked': [
' '
],
'raw': [
' ',
],
'exprs': []
}])` `` `();
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