Skip to content

Commit 81d168d

Browse files
sygCommit Bot
authored andcommitted
Implement string literal module export names
Implements tc39/ecma262#2154, which allows module export names to be string literals. Semantics highlights: - It is a SyntaxError for string literal export names to have unpaired UTF16 surrogates. - It is a SyntaxError for string literal export names to be used as the local name without being followed by a 'from' clause. For example, `export { "foo" }` and `export { "foo" as "bar" }` are errors, but `export { "foo" } from "./module.js"` is allowed. The remaining failing test262 test is wrong: tc39/test262#2866 Bug: v8:10964 Change-Id: Ib3e06e1ee6b3f1b60ed7f24e21902e17ddfc0351 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2482335 Commit-Queue: Shu-yu Guo <[email protected]> Reviewed-by: Leszek Swirski <[email protected]> Reviewed-by: Marja Hölttä <[email protected]> Cr-Commit-Position: refs/heads/master@{#70692}
1 parent 757f043 commit 81d168d

9 files changed

Lines changed: 113 additions & 41 deletions

File tree

src/common/message-template.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ namespace internal {
395395
"Async functions can only be declared at the top level or inside a " \
396396
"block.") \
397397
T(IllegalBreak, "Illegal break statement") \
398+
T(ModuleExportNameWithoutFromClause, \
399+
"String literal module export names must be followed by a 'from' clause") \
398400
T(NoIterationStatement, \
399401
"Illegal continue statement: no surrounding iteration statement") \
400402
T(IllegalContinue, \
@@ -419,6 +421,8 @@ namespace internal {
419421
"Invalid left-hand side expression in postfix operation") \
420422
T(InvalidLhsInPrefixOp, \
421423
"Invalid left-hand side expression in prefix operation") \
424+
T(InvalidModuleExportName, \
425+
"Invalid module export name: contains unpaired surrogate") \
422426
T(InvalidRegExpFlags, "Invalid flags supplied to RegExp constructor '%'") \
423427
T(InvalidOrUnexpectedToken, "Invalid or unexpected token") \
424428
T(InvalidPrivateBrand, "Object must be an instance of class %") \

src/parsing/parser.cc

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "src/runtime/runtime.h"
2828
#include "src/strings/char-predicates-inl.h"
2929
#include "src/strings/string-stream.h"
30+
#include "src/strings/unicode-inl.h"
3031
#include "src/tracing/trace-event.h"
3132
#include "src/zone/zone-list-inl.h"
3233

@@ -1071,7 +1072,8 @@ const AstRawString* Parser::ParseModuleSpecifier() {
10711072
}
10721073

10731074
ZoneChunkList<Parser::ExportClauseData>* Parser::ParseExportClause(
1074-
Scanner::Location* reserved_loc) {
1075+
Scanner::Location* reserved_loc,
1076+
Scanner::Location* string_literal_local_name_loc) {
10751077
// ExportClause :
10761078
// '{' '}'
10771079
// '{' ExportsList '}'
@@ -1084,30 +1086,40 @@ ZoneChunkList<Parser::ExportClauseData>* Parser::ParseExportClause(
10841086
// ExportSpecifier :
10851087
// IdentifierName
10861088
// IdentifierName 'as' IdentifierName
1089+
// IdentifierName 'as' ModuleExportName
1090+
// ModuleExportName
1091+
// ModuleExportName 'as' ModuleExportName
1092+
//
1093+
// ModuleExportName :
1094+
// StringLiteral
10871095
ZoneChunkList<ExportClauseData>* export_data =
10881096
zone()->New<ZoneChunkList<ExportClauseData>>(zone());
10891097

10901098
Expect(Token::LBRACE);
10911099

10921100
Token::Value name_tok;
10931101
while ((name_tok = peek()) != Token::RBRACE) {
1094-
// Keep track of the first reserved word encountered in case our
1095-
// caller needs to report an error.
1096-
if (!reserved_loc->IsValid() &&
1097-
!Token::IsValidIdentifier(name_tok, LanguageMode::kStrict, false,
1098-
flags().is_module())) {
1102+
const AstRawString* local_name = ParseExportSpecifierName();
1103+
if (!string_literal_local_name_loc->IsValid() &&
1104+
name_tok == Token::STRING) {
1105+
// Keep track of the first string literal local name exported for error
1106+
// reporting. These must be followed by a 'from' clause.
1107+
*string_literal_local_name_loc = scanner()->location();
1108+
} else if (!reserved_loc->IsValid() &&
1109+
!Token::IsValidIdentifier(name_tok, LanguageMode::kStrict, false,
1110+
flags().is_module())) {
1111+
// Keep track of the first reserved word encountered in case our
1112+
// caller needs to report an error.
10991113
*reserved_loc = scanner()->location();
11001114
}
1101-
const AstRawString* local_name = ParsePropertyName();
1102-
const AstRawString* export_name = nullptr;
1115+
const AstRawString* export_name;
11031116
Scanner::Location location = scanner()->location();
11041117
if (CheckContextualKeyword(ast_value_factory()->as_string())) {
1105-
export_name = ParsePropertyName();
1118+
export_name = ParseExportSpecifierName();
11061119
// Set the location to the whole "a as b" string, so that it makes sense
11071120
// both for errors due to "a" and for errors due to "b".
11081121
location.end_pos = scanner()->location().end_pos;
1109-
}
1110-
if (export_name == nullptr) {
1122+
} else {
11111123
export_name = local_name;
11121124
}
11131125
export_data->push_back({export_name, local_name, location});
@@ -1122,6 +1134,31 @@ ZoneChunkList<Parser::ExportClauseData>* Parser::ParseExportClause(
11221134
return export_data;
11231135
}
11241136

1137+
const AstRawString* Parser::ParseExportSpecifierName() {
1138+
Token::Value next = Next();
1139+
1140+
// IdentifierName
1141+
if (V8_LIKELY(Token::IsPropertyName(next))) {
1142+
return GetSymbol();
1143+
}
1144+
1145+
// ModuleExportName
1146+
if (next == Token::STRING) {
1147+
const AstRawString* export_name = GetSymbol();
1148+
if (V8_LIKELY(export_name->is_one_byte())) return export_name;
1149+
if (!unibrow::Utf16::HasUnpairedSurrogate(
1150+
reinterpret_cast<const uint16_t*>(export_name->raw_data()),
1151+
export_name->length())) {
1152+
return export_name;
1153+
}
1154+
ReportMessage(MessageTemplate::kInvalidModuleExportName);
1155+
return EmptyIdentifierString();
1156+
}
1157+
1158+
ReportUnexpectedToken(next);
1159+
return EmptyIdentifierString();
1160+
}
1161+
11251162
ZonePtrList<const Parser::NamedImport>* Parser::ParseNamedImports(int pos) {
11261163
// NamedImports :
11271164
// '{' '}'
@@ -1135,12 +1172,13 @@ ZonePtrList<const Parser::NamedImport>* Parser::ParseNamedImports(int pos) {
11351172
// ImportSpecifier :
11361173
// BindingIdentifier
11371174
// IdentifierName 'as' BindingIdentifier
1175+
// ModuleExportName 'as' BindingIdentifier
11381176

11391177
Expect(Token::LBRACE);
11401178

11411179
auto result = zone()->New<ZonePtrList<const NamedImport>>(1, zone());
11421180
while (peek() != Token::RBRACE) {
1143-
const AstRawString* import_name = ParsePropertyName();
1181+
const AstRawString* import_name = ParseExportSpecifierName();
11441182
const AstRawString* local_name = import_name;
11451183
Scanner::Location location = scanner()->location();
11461184
// In the presence of 'as', the left-side of the 'as' can
@@ -1450,9 +1488,14 @@ void Parser::ParseExportStar() {
14501488
// export * as x from "...";
14511489
// ~>
14521490
// import * as .x from "..."; export {.x as x};
1491+
//
1492+
// Note that the desugared internal namespace export name (.x above) will
1493+
// never conflict with a string literal export name, as literal string export
1494+
// names in local name positions (i.e. left of 'as' or in a clause without
1495+
// 'as') are disallowed without a following 'from' clause.
14531496

14541497
ExpectContextualKeyword(ast_value_factory()->as_string());
1455-
const AstRawString* export_name = ParsePropertyName();
1498+
const AstRawString* export_name = ParseExportSpecifierName();
14561499
Scanner::Location export_name_loc = scanner()->location();
14571500
const AstRawString* local_name = NextInternalNamespaceExportName();
14581501
Scanner::Location local_name_loc = Scanner::Location::invalid();
@@ -1478,12 +1521,18 @@ Statement* Parser::ParseExportDeclaration() {
14781521
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier ';'
14791522
// 'export' '*' 'as' IdentifierName 'from' ModuleSpecifier
14801523
// [no LineTerminator here] AssertClause ';'
1524+
// 'export' '*' 'as' ModuleExportName 'from' ModuleSpecifier ';'
1525+
// 'export' '*' 'as' ModuleExportName 'from' ModuleSpecifier ';'
1526+
// [no LineTerminator here] AssertClause ';'
14811527
// 'export' ExportClause ('from' ModuleSpecifier)? ';'
14821528
// 'export' ExportClause ('from' ModuleSpecifier [no LineTerminator here]
14831529
// AssertClause)? ';'
14841530
// 'export' VariableStatement
14851531
// 'export' Declaration
14861532
// 'export' 'default' ... (handled in ParseExportDefault)
1533+
//
1534+
// ModuleExportName :
1535+
// StringLiteral
14871536

14881537
Expect(Token::EXPORT);
14891538
Statement* result = nullptr;
@@ -1510,8 +1559,10 @@ Statement* Parser::ParseExportDeclaration() {
15101559
// encountered, and then throw a SyntaxError if we are in the
15111560
// non-FromClause case.
15121561
Scanner::Location reserved_loc = Scanner::Location::invalid();
1562+
Scanner::Location string_literal_local_name_loc =
1563+
Scanner::Location::invalid();
15131564
ZoneChunkList<ExportClauseData>* export_data =
1514-
ParseExportClause(&reserved_loc);
1565+
ParseExportClause(&reserved_loc, &string_literal_local_name_loc);
15151566
if (CheckContextualKeyword(ast_value_factory()->from_string())) {
15161567
Scanner::Location specifier_loc = scanner()->peek_location();
15171568
const AstRawString* module_specifier = ParseModuleSpecifier();
@@ -1533,6 +1584,10 @@ Statement* Parser::ParseExportDeclaration() {
15331584
// No FromClause, so reserved words are invalid in ExportClause.
15341585
ReportMessageAt(reserved_loc, MessageTemplate::kUnexpectedReserved);
15351586
return nullptr;
1587+
} else if (string_literal_local_name_loc.IsValid()) {
1588+
ReportMessageAt(string_literal_local_name_loc,
1589+
MessageTemplate::kModuleExportNameWithoutFromClause);
1590+
return nullptr;
15361591
}
15371592

15381593
ExpectSemicolon();

src/parsing/parser.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
269269
Scanner::Location location;
270270
};
271271
ZoneChunkList<ExportClauseData>* ParseExportClause(
272-
Scanner::Location* reserved_loc);
272+
Scanner::Location* reserved_loc,
273+
Scanner::Location* string_literal_local_name_loc);
273274
struct NamedImport : public ZoneObject {
274275
const AstRawString* import_name;
275276
const AstRawString* local_name;
@@ -280,6 +281,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
280281
local_name(local_name),
281282
location(location) {}
282283
};
284+
const AstRawString* ParseExportSpecifierName();
283285
ZonePtrList<const NamedImport>* ParseNamedImports(int pos);
284286
using ImportAssertions =
285287
ZoneMap<const AstRawString*,

src/strings/unicode-inl.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ int Mapping<T, s>::CalculateValue(uchar c, uchar n, uchar* result) {
5959
}
6060
#endif // !V8_INTL_SUPPORT
6161

62+
bool Utf16::HasUnpairedSurrogate(const uint16_t* code_units, size_t length) {
63+
for (size_t i = 0; i < length; ++i) {
64+
const int code_unit = code_units[i];
65+
if (IsLeadSurrogate(code_unit)) {
66+
// The current code unit is a leading surrogate. Check if it is followed
67+
// by a trailing surrogate.
68+
if (i == length - 1) return true;
69+
if (!IsTrailSurrogate(code_units[i + 1])) return true;
70+
// Skip the paired trailing surrogate.
71+
++i;
72+
} else if (IsTrailSurrogate(code_unit)) {
73+
// All paired trailing surrogates are skipped above, so this branch is
74+
// only for those that are unpaired.
75+
return true;
76+
}
77+
}
78+
return false;
79+
}
80+
6281
// Decodes UTF-8 bytes incrementally, allowing the decoding of bytes as they
6382
// stream in. This **must** be followed by a call to ValueOfIncrementalFinish
6483
// when the stream is complete, to ensure incomplete sequences are handled.

src/strings/unicode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ class Utf16 {
128128
static inline uint16_t TrailSurrogate(uint32_t char_code) {
129129
return 0xdc00 + (char_code & 0x3ff);
130130
}
131+
static inline bool HasUnpairedSurrogate(const uint16_t* code_units,
132+
size_t length);
131133
};
132134

133135
class Latin1 {

test/cctest/interpreter/bytecode_expectations/PrivateAccessorAccess.golden

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ bytecodes: [
8484
B(Mov), R(this), R(0),
8585
B(Mov), R(context), R(2),
8686
/* 48 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
87-
/* 53 S> */ B(Wide), B(LdaSmi), I16(270),
87+
/* 53 S> */ B(Wide), B(LdaSmi), I16(272),
8888
B(Star), R(3),
8989
B(LdaConstant), U8(0),
9090
B(Star), R(4),
@@ -115,7 +115,7 @@ bytecodes: [
115115
B(Mov), R(this), R(0),
116116
B(Mov), R(context), R(2),
117117
/* 41 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
118-
/* 46 S> */ B(Wide), B(LdaSmi), I16(269),
118+
/* 46 S> */ B(Wide), B(LdaSmi), I16(271),
119119
B(Star), R(3),
120120
B(LdaConstant), U8(0),
121121
B(Star), R(4),
@@ -146,7 +146,7 @@ bytecodes: [
146146
B(Mov), R(this), R(0),
147147
B(Mov), R(context), R(2),
148148
/* 48 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
149-
/* 53 S> */ B(Wide), B(LdaSmi), I16(270),
149+
/* 53 S> */ B(Wide), B(LdaSmi), I16(272),
150150
B(Star), R(3),
151151
B(LdaConstant), U8(0),
152152
B(Star), R(4),
@@ -177,7 +177,7 @@ bytecodes: [
177177
B(Mov), R(this), R(0),
178178
B(Mov), R(context), R(2),
179179
/* 41 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
180-
/* 46 S> */ B(Wide), B(LdaSmi), I16(269),
180+
/* 46 S> */ B(Wide), B(LdaSmi), I16(271),
181181
B(Star), R(4),
182182
B(LdaConstant), U8(0),
183183
B(Star), R(5),

test/cctest/interpreter/bytecode_expectations/PrivateMethodAccess.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ bytecodes: [
5757
B(Mov), R(this), R(0),
5858
B(Mov), R(context), R(2),
5959
/* 44 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
60-
/* 49 S> */ B(Wide), B(LdaSmi), I16(268),
60+
/* 49 S> */ B(Wide), B(LdaSmi), I16(270),
6161
B(Star), R(3),
6262
B(LdaConstant), U8(0),
6363
B(Star), R(4),
@@ -89,7 +89,7 @@ bytecodes: [
8989
B(Mov), R(this), R(0),
9090
B(Mov), R(context), R(2),
9191
/* 44 E> */ B(CallRuntime), U16(Runtime::kAddPrivateBrand), R(0), U8(3),
92-
/* 49 S> */ B(Wide), B(LdaSmi), I16(268),
92+
/* 49 S> */ B(Wide), B(LdaSmi), I16(270),
9393
B(Star), R(3),
9494
B(LdaConstant), U8(0),
9595
B(Star), R(4),

test/cctest/interpreter/bytecode_expectations/StaticPrivateMethodAccess.golden

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ bytecodes: [
2525
B(TestReferenceEqual), R(this),
2626
B(Mov), R(this), R(1),
2727
B(JumpIfTrue), U8(18),
28-
B(Wide), B(LdaSmi), I16(266),
28+
B(Wide), B(LdaSmi), I16(268),
2929
B(Star), R(2),
3030
B(LdaConstant), U8(0),
3131
B(Star), R(3),
@@ -56,7 +56,7 @@ frame size: 2
5656
parameter count: 1
5757
bytecode array length: 16
5858
bytecodes: [
59-
/* 56 S> */ B(Wide), B(LdaSmi), I16(268),
59+
/* 56 S> */ B(Wide), B(LdaSmi), I16(270),
6060
B(Star), R(0),
6161
B(LdaConstant), U8(0),
6262
B(Star), R(1),
@@ -83,7 +83,7 @@ frame size: 2
8383
parameter count: 1
8484
bytecode array length: 16
8585
bytecodes: [
86-
/* 56 S> */ B(Wide), B(LdaSmi), I16(268),
86+
/* 56 S> */ B(Wide), B(LdaSmi), I16(270),
8787
B(Star), R(0),
8888
B(LdaConstant), U8(0),
8989
B(Star), R(1),
@@ -122,7 +122,7 @@ bytecodes: [
122122
/* 94 E> */ B(TestReferenceEqual), R(this),
123123
B(Mov), R(this), R(0),
124124
B(JumpIfTrue), U8(18),
125-
B(Wide), B(LdaSmi), I16(266),
125+
B(Wide), B(LdaSmi), I16(268),
126126
B(Star), R(2),
127127
B(LdaConstant), U8(0),
128128
B(Star), R(3),
@@ -144,7 +144,7 @@ bytecodes: [
144144
/* 109 E> */ B(TestReferenceEqual), R(this),
145145
B(Mov), R(this), R(1),
146146
B(JumpIfTrue), U8(18),
147-
B(Wide), B(LdaSmi), I16(267),
147+
B(Wide), B(LdaSmi), I16(269),
148148
B(Star), R(3),
149149
B(LdaConstant), U8(0),
150150
B(Star), R(4),
@@ -159,7 +159,7 @@ bytecodes: [
159159
/* 133 E> */ B(TestReferenceEqual), R(this),
160160
B(Mov), R(this), R(0),
161161
B(JumpIfTrue), U8(18),
162-
B(Wide), B(LdaSmi), I16(266),
162+
B(Wide), B(LdaSmi), I16(268),
163163
B(Star), R(2),
164164
B(LdaConstant), U8(0),
165165
B(Star), R(3),
@@ -189,7 +189,7 @@ frame size: 2
189189
parameter count: 1
190190
bytecode array length: 16
191191
bytecodes: [
192-
/* 60 S> */ B(Wide), B(LdaSmi), I16(270),
192+
/* 60 S> */ B(Wide), B(LdaSmi), I16(272),
193193
B(Star), R(0),
194194
B(LdaConstant), U8(0),
195195
B(Star), R(1),
@@ -215,7 +215,7 @@ frame size: 2
215215
parameter count: 1
216216
bytecode array length: 16
217217
bytecodes: [
218-
/* 53 S> */ B(Wide), B(LdaSmi), I16(269),
218+
/* 53 S> */ B(Wide), B(LdaSmi), I16(271),
219219
B(Star), R(0),
220220
B(LdaConstant), U8(0),
221221
B(Star), R(1),
@@ -241,7 +241,7 @@ frame size: 2
241241
parameter count: 1
242242
bytecode array length: 16
243243
bytecodes: [
244-
/* 60 S> */ B(Wide), B(LdaSmi), I16(270),
244+
/* 60 S> */ B(Wide), B(LdaSmi), I16(272),
245245
B(Star), R(0),
246246
B(LdaConstant), U8(0),
247247
B(Star), R(1),
@@ -267,7 +267,7 @@ frame size: 3
267267
parameter count: 1
268268
bytecode array length: 16
269269
bytecodes: [
270-
/* 46 S> */ B(Wide), B(LdaSmi), I16(269),
270+
/* 46 S> */ B(Wide), B(LdaSmi), I16(271),
271271
B(Star), R(1),
272272
B(LdaConstant), U8(0),
273273
B(Star), R(2),

0 commit comments

Comments
 (0)