[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}
This commit is contained in:
bakkot 2017-03-03 14:08:57 -08:00 committed by Commit bot
parent 3a20c322bb
commit baa74e89b6
5 changed files with 132 additions and 32 deletions

View File

@ -797,10 +797,12 @@ class ParserBase {
}
// 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.
// appears in the current template literal token. 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) {
DCHECK(scanner()->current_token() == Token::TEMPLATE_SPAN ||
scanner()->current_token() == Token::TEMPLATE_TAIL);
if (!scanner()->has_invalid_template_escape()) {
return true;
}
@ -811,7 +813,6 @@ class ParserBase {
scanner()->invalid_template_escape_message());
*ok = false;
}
scanner()->clear_invalid_template_escape();
return false;
}

View File

@ -19,10 +19,7 @@
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 {
class Scanner::ErrorState {
public:
ErrorState(MessageTemplate::Template* message_stack,
Scanner::Location* location_stack)
@ -31,7 +28,7 @@ class ErrorState {
location_stack_(location_stack),
old_location_(*location_stack) {
*message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid();
*location_stack_ = Location::invalid();
}
~ErrorState() {
@ -39,17 +36,16 @@ class ErrorState {
*location_stack_ = old_location_;
}
void MoveErrorTo(MessageTemplate::Template* message_dest,
Scanner::Location* location_dest) {
void MoveErrorTo(TokenDesc* dest) {
if (*message_stack_ == MessageTemplate::kNone) {
return;
}
if (*message_dest == MessageTemplate::kNone) {
*message_dest = *message_stack_;
*location_dest = *location_stack_;
if (dest->invalid_template_escape_message == MessageTemplate::kNone) {
dest->invalid_template_escape_message = *message_stack_;
dest->invalid_template_escape_location = *location_stack_;
}
*message_stack_ = MessageTemplate::kNone;
*location_stack_ = Scanner::Location::invalid();
*location_stack_ = Location::invalid();
}
private:
@ -397,6 +393,7 @@ Token::Value Scanner::Next() {
next_.location.end_pos = pos + 1;
next_.literal_chars = nullptr;
next_.raw_literal_chars = nullptr;
next_.invalid_template_escape_message = MessageTemplate::kNone;
Advance();
return current_.token;
}
@ -609,6 +606,7 @@ Token::Value Scanner::ScanHtmlComment() {
void Scanner::Scan() {
next_.literal_chars = NULL;
next_.raw_literal_chars = NULL;
next_.invalid_template_escape_message = MessageTemplate::kNone;
Token::Value token;
do {
// Remember the position of the next token
@ -889,6 +887,8 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const {
// - TEMPLATE_*: need both literal + raw literal chars.
// - IDENTIFIERS, STRINGS, etc.: need a literal, but no raw literal.
// - all others: should have neither.
// Furthermore, only TEMPLATE_* tokens can have a
// invalid_template_escape_message.
switch (token.token) {
case Token::UNINITIALIZED:
@ -909,10 +909,12 @@ void Scanner::SanityCheckTokenDesc(const TokenDesc& token) const {
case Token::STRING:
DCHECK_NOT_NULL(token.literal_chars);
DCHECK_NULL(token.raw_literal_chars);
DCHECK_EQ(token.invalid_template_escape_message, MessageTemplate::kNone);
break;
default:
DCHECK_NULL(token.literal_chars);
DCHECK_NULL(token.raw_literal_chars);
DCHECK_EQ(token.invalid_template_escape_message, MessageTemplate::kNone);
break;
}
}
@ -1117,10 +1119,8 @@ Token::Value Scanner::ScanTemplateSpan() {
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_);
scanner_error_state.MoveErrorTo(&next_);
octal_error_state.MoveErrorTo(&next_);
}
} else if (c < 0) {
// Unterminated template literal
@ -1736,7 +1736,9 @@ void Scanner::SeekNext(size_t position) {
// 1, Reset the current_, next_ and next_next_ tokens
// (next_ + next_next_ will be overwrittem by Next(),
// 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_next_.token = Token::UNINITIALIZED;
// 2, reset the source to the desired position,

View File

@ -215,19 +215,15 @@ class Scanner {
Location error_location() const { return scanner_error_location_; }
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 {
return invalid_template_escape_message_;
DCHECK(has_invalid_template_escape());
return current_.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();
return current_.invalid_template_escape_location;
}
// Similar functions for the upcoming token.
@ -345,6 +341,11 @@ class Scanner {
bool FoundHtmlComment() const { return found_html_comment_; }
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
// if aborting the scanning before it's complete.
class LiteralScope {
@ -457,6 +458,8 @@ class Scanner {
LiteralBuffer* raw_literal_chars;
uint32_t smi_value_;
Token::Value token;
MessageTemplate::Template invalid_template_escape_message;
Location invalid_template_escape_location;
};
static const int kCharacterLookaheadBufferSize = 1;
@ -475,15 +478,17 @@ class Scanner {
current_.token = Token::UNINITIALIZED;
current_.literal_chars = NULL;
current_.raw_literal_chars = NULL;
current_.invalid_template_escape_message = MessageTemplate::kNone;
next_.token = Token::UNINITIALIZED;
next_.literal_chars = NULL;
next_.raw_literal_chars = NULL;
next_.invalid_template_escape_message = MessageTemplate::kNone;
next_next_.token = Token::UNINITIALIZED;
next_next_.literal_chars = NULL;
next_next_.raw_literal_chars = NULL;
next_next_.invalid_template_escape_message = MessageTemplate::kNone;
found_html_comment_ = false;
scanner_error_ = MessageTemplate::kNone;
invalid_template_escape_message_ = MessageTemplate::kNone;
}
void ReportScannerError(const Location& location,
@ -774,9 +779,6 @@ class Scanner {
MessageTemplate::Template scanner_error_;
Location scanner_error_location_;
MessageTemplate::Template invalid_template_escape_message_;
Location invalid_template_escape_location_;
};
} // namespace internal

View File

@ -7161,6 +7161,12 @@ TEST(TemplateEscapesPositiveTests) {
"tag`\\u{110000}${0}right`",
"tag`left${0}\\u{110000}`",
"tag`left${0}\\u{110000}${1}right`",
"tag` ${tag`\\u`}`",
"tag` ``\\u`",
"tag`\\u`` `",
"tag`\\u``\\u`",
"` ${tag`\\u`}`",
"` ``\\u`",
NULL};
// clang-format on
@ -7239,6 +7245,8 @@ TEST(TemplateEscapesNegativeTests) {
"`left${0}\\u{110000}`",
"`left${0}\\u{110000}${1}right`",
"`\\1``\\2`",
"tag` ${`\\u`}`",
"`\\u```",
NULL};
// clang-format on

View File

@ -783,3 +783,90 @@ check({
1
]
})`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': []
}])` `` `();