Skip to content

Commit

Permalink
Bug 1484948 - Parse dynamic module import syntax but throw SyntaxErro…
Browse files Browse the repository at this point in the history
…r if used r=jorendorff
  • Loading branch information
jonco3 committed Sep 7, 2018
1 parent 63a0166 commit af31b3e
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 32 deletions.
32 changes: 32 additions & 0 deletions js/src/builtin/ReflectParse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,9 @@ class NodeBuilder
MOZ_MUST_USE bool metaProperty(HandleValue meta, HandleValue property, TokenPos* pos,
MutableHandleValue dst);

MOZ_MUST_USE bool callImportExpression(HandleValue ident, HandleValue arg, TokenPos* pos,
MutableHandleValue dst);

MOZ_MUST_USE bool super(TokenPos* pos, MutableHandleValue dst);

/*
Expand Down Expand Up @@ -1744,6 +1747,20 @@ NodeBuilder::metaProperty(HandleValue meta, HandleValue property, TokenPos* pos,
dst);
}

bool
NodeBuilder::callImportExpression(HandleValue ident, HandleValue arg, TokenPos* pos,
MutableHandleValue dst)
{
RootedValue cb(cx, callbacks[AST_CALL_IMPORT]);
if (!cb.isNull())
return callback(cb, arg, pos, dst);

return newNode(AST_CALL_IMPORT, pos,
"ident", ident,
"arg", arg,
dst);
}

bool
NodeBuilder::super(TokenPos* pos, MutableHandleValue dst)
{
Expand Down Expand Up @@ -3287,6 +3304,21 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
builder.metaProperty(firstIdent, secondIdent, &pn->pn_pos, dst);
}

case ParseNodeKind::CallImport:
{
MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));

RootedValue ident(cx);
RootedValue arg(cx);

HandlePropertyName name = cx->names().import;
return identifier(name, &pn->pn_left->pn_pos, &ident) &&
expression(pn->pn_right, &arg) &&
builder.callImportExpression(ident, arg, &pn->pn_pos, dst);
}

case ParseNodeKind::SetThis:
// SETTHIS is used to assign the result of a super() call to |this|.
// It's not part of the original AST, so just forward to the call.
Expand Down
13 changes: 11 additions & 2 deletions js/src/frontend/BytecodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,11 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
*answer = true;
return true;

case ParseNodeKind::CallImport:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;

// Every part of a loop might be effect-free, but looping infinitely *is*
// an effect. (Language lawyer trivia: C++ says threads can be assumed
// to exit or have side effects, C++14 [intro.multithread]p27, so a C++
Expand Down Expand Up @@ -8232,8 +8237,8 @@ bool BytecodeEmitter::emitOptionalTree(ParseNode* pn, OptionalEmitter& oe,
kind == ParseNodeKind::NewTarget ||
kind == ParseNodeKind::ImportMeta;

bool isCallExpression = kind == ParseNodeKind::SetThis; /* ||
kind == ParseNodeKind::CallImport; Waterfox is missing bug 1484948 */
bool isCallExpression = kind == ParseNodeKind::SetThis ||
kind == ParseNodeKind::CallImport;

MOZ_ASSERT(isMemberExpression || isCallExpression,
"Unknown ParseNodeKind for OptionalChain");
Expand Down Expand Up @@ -9992,6 +9997,10 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
return false;
break;

case ParseNodeKind::CallImport:
reportError(nullptr, JSMSG_NO_DYNAMIC_IMPORT);
return false;

case ParseNodeKind::SetThis:
if (!emitSetThis(pn))
return false;
Expand Down
6 changes: 6 additions & 0 deletions js/src/frontend/FoldConstants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result)
case ParseNodeKind::ExportSpec:
case ParseNodeKind::Export:
case ParseNodeKind::ExportBatchSpec:
case ParseNodeKind::CallImport:
*result = false;
return true;

Expand Down Expand Up @@ -1871,6 +1872,11 @@ Fold(JSContext* cx, ParseNode** pnp, Parser<FullParseHandler, char16_t>& parser,
MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::PosHolder));
return true;

case ParseNodeKind::CallImport:
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
return Fold(cx, &pn->pn_right, parser, inGenexpLambda);

case ParseNodeKind::ClassNames:
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (ParseNode*& outerBinding = pn->pn_left) {
Expand Down
4 changes: 4 additions & 0 deletions js/src/frontend/FullParseHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,10 @@ class FullParseHandler
return new_<BinaryNode>(ParseNodeKind::ImportMeta, JSOP_NOP, importHolder, metaHolder);
}

ParseNode* newCallImport(ParseNode* importHolder, ParseNode* singleArg) {
return new_<BinaryNode>(ParseNodeKind::CallImport, JSOP_NOP, importHolder, singleArg);
}

ParseNode* newExprStatement(ParseNode* expr, uint32_t end) {
MOZ_ASSERT(expr->pn_pos.end <= end);
return new_<UnaryNode>(ParseNodeKind::ExpressionStatement,
Expand Down
6 changes: 6 additions & 0 deletions js/src/frontend/NameFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,12 @@ class NameResolver
break;
}

case ParseNodeKind::CallImport:
MOZ_ASSERT(cur->isArity(PN_BINARY));
if (!resolve(cur->pn_right, prefix))
return false;
break;

case ParseNodeKind::Dot:
MOZ_ASSERT(cur->isArity(PN_BINARY));

Expand Down
1 change: 1 addition & 0 deletions js/src/frontend/ParseNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class ObjectBox;
F(SuperCall) \
F(SetThis) \
F(ImportMeta) \
F(CallImport) \
\
/* Unary operators. */ \
F(TypeOfName) \
Expand Down
61 changes: 34 additions & 27 deletions js/src/frontend/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5292,15 +5292,15 @@ Parser<SyntaxParseHandler, char16_t>::importDeclaration()

template <class ParseHandler, typename CharT>
typename ParseHandler::Node
Parser<ParseHandler, CharT>::importDeclarationOrImportMeta(YieldHandling yieldHandling)
Parser<ParseHandler, CharT>::importDeclarationOrImportExpr(YieldHandling yieldHandling)
{
MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import));

TokenKind tt;
if (!tokenStream.peekToken(&tt))
return null();

if (tt == TokenKind::Dot)
if (tt == TokenKind::Dot || tt == TokenKind::LeftParen)
return expressionStatement(yieldHandling);

return importDeclaration();
Expand Down Expand Up @@ -7790,7 +7790,7 @@ Parser<ParseHandler, CharT>::statement(YieldHandling yieldHandling)

// ImportDeclaration (only inside modules)
case TokenKind::Import:
return importDeclarationOrImportMeta(yieldHandling);
return importDeclarationOrImportExpr(yieldHandling);

// ExportDeclaration (only inside modules)
case TokenKind::Export:
Expand Down Expand Up @@ -7982,7 +7982,7 @@ Parser<ParseHandler, CharT>::statementListItem(YieldHandling yieldHandling,

// ImportDeclaration (only inside modules)
case TokenKind::Import:
return importDeclarationOrImportMeta(yieldHandling);
return importDeclarationOrImportExpr(yieldHandling);

// ExportDeclaration (only inside modules)
case TokenKind::Export:
Expand Down Expand Up @@ -9290,7 +9290,7 @@ Parser<ParseHandler, CharT>::memberExpr(YieldHandling yieldHandling,
if (!lhs)
return null();
} else if (tt == TokenKind::Import) {
lhs = importMeta();
lhs = importExpr(yieldHandling);
if (!lhs)
return null();
} else {
Expand Down Expand Up @@ -10560,41 +10560,48 @@ Parser<ParseHandler, CharT>::tryNewTarget(Node &newTarget)

template <class ParseHandler, typename CharT>
typename ParseHandler::Node
Parser<ParseHandler, CharT>::importMeta()
Parser<ParseHandler, CharT>::importExpr(YieldHandling yieldHandling)
{
MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import));

uint32_t begin = pos().begin;

if (parseGoal() != ParseGoal::Module) {
errorAt(begin, JSMSG_IMPORT_OUTSIDE_MODULE);
return null();
}

Node importHolder = handler.newPosHolder(pos());
if (!importHolder)
return null();

TokenKind next;
if (!tokenStream.getToken(&next))
return null();
if (next != TokenKind::Dot) {
error(JSMSG_UNEXPECTED_TOKEN, "dot", TokenKindToDesc(next));
return null();
}

if (!tokenStream.getToken(&next))
return null();
if (next != TokenKind::Meta) {
error(JSMSG_UNEXPECTED_TOKEN, "meta", TokenKindToDesc(next));
return null();
}
if (next == TokenKind::Dot) {
if (!tokenStream.getToken(&next))
return null();
if (next != TokenKind::Meta) {
error(JSMSG_UNEXPECTED_TOKEN, "meta", TokenKindToDesc(next));
return null();
}

Node metaHolder = handler.newPosHolder(pos());
if (!metaHolder)
return null();
if (parseGoal() != ParseGoal::Module) {
errorAt(pos().begin, JSMSG_IMPORT_META_OUTSIDE_MODULE);
return null();
}

Node metaHolder = handler.newPosHolder(pos());
if (!metaHolder)
return null();

return handler.newImportMeta(importHolder, metaHolder);
return handler.newImportMeta(importHolder, metaHolder);
} else if (next == TokenKind::LeftParen) {
Node arg = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!arg)
return null();

MUST_MATCH_TOKEN_MOD(TokenKind::RightParen, TokenStream::Operand, JSMSG_PAREN_AFTER_ARGS);

return handler.newCallImport(importHolder, arg);
} else {
error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next));
return null();
}
}

template <class ParseHandler, typename CharT>
Expand Down
4 changes: 2 additions & 2 deletions js/src/frontend/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ class Parser final
Node lexicalDeclaration(YieldHandling yieldHandling, DeclarationKind kind);

Node importDeclaration();
Node importDeclarationOrImportMeta(YieldHandling yieldHandling);
Node importDeclarationOrImportExpr(YieldHandling yieldHandling);

bool processExport(Node node);
bool processExportFrom(Node node);
Expand Down Expand Up @@ -774,7 +774,7 @@ class Parser final
bool tryNewTarget(Node& newTarget);
bool checkAndMarkSuperScope();

Node importMeta();
Node importExpr(YieldHandling yieldHandling);

Node methodDefinition(uint32_t toStringStart, PropertyType propType, HandleAtom funName);

Expand Down
3 changes: 3 additions & 0 deletions js/src/frontend/SyntaxParseHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ class SyntaxParseHandler
Node newImportMeta(Node importHolder, Node metaHolder) {
return NodeGeneric;
}
Node newCallImport(Node importHolder, Node singleArg) {
return NodeGeneric;
}

Node newSetThis(Node thisName, Node value) { return value; }

Expand Down
85 changes: 85 additions & 0 deletions js/src/jit-test/tests/modules/dynamic-import-expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
load(libdir + "match.js");
load(libdir + "asserts.js");

var { Pattern, MatchError } = Match;

program = (elts) => Pattern({
type: "Program",
body: elts
});
expressionStatement = (expression) => Pattern({
type: "ExpressionStatement",
expression: expression
});
assignmentExpression = (left, operator, right) => Pattern({
type: "AssignmentExpression",
operator: operator,
left: left,
right: right
});
ident = (name) => Pattern({
type: "Identifier",
name: name
});
importCall = (ident, singleArg) => Pattern({
type: "CallImport",
ident: ident,
arg: singleArg
});

function parseAsClassicScript(source)
{
return Reflect.parse(source);
}

function parseAsModuleScript(source)
{
return Reflect.parse(source, {target: "module"});
}

for (let parse of [parseAsModuleScript, parseAsClassicScript]) {
program([
expressionStatement(
importCall(
ident("import"),
ident("foo")
)
)
]).assert(parse("import(foo);"));

program([
expressionStatement(
assignmentExpression(
ident("x"),
"=",
importCall(
ident("import"),
ident("foo")
)
)
)
]).assert(parse("x = import(foo);"));
}

function assertParseThrowsSyntaxError(source)
{
assertThrowsInstanceOf(() => parseAsClassicScript(source), SyntaxError);
assertThrowsInstanceOf(() => parseAsModuleScript(source), SyntaxError);
}

assertParseThrowsSyntaxError("import");
assertParseThrowsSyntaxError("import(");
assertParseThrowsSyntaxError("import(1,");
assertParseThrowsSyntaxError("import(1, 2");
assertParseThrowsSyntaxError("import(1, 2)");
assertParseThrowsSyntaxError("x = import");
assertParseThrowsSyntaxError("x = import(");
assertParseThrowsSyntaxError("x = import(1,");
assertParseThrowsSyntaxError("x = import(1, 2");
assertParseThrowsSyntaxError("x = import(1, 2)");

// import() is not implemented.
assertThrowsInstanceOf(() => eval("import('foo')"),
SyntaxError);
assertThrowsInstanceOf(() => parseModule("import('foo')"),
SyntaxError);
3 changes: 2 additions & 1 deletion js/src/js.msg
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'fro
MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}")
MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character")
MSG_DEF(JSMSG_IMPORT_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "the import keyword may only appear in a module")
MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module")
MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
MSG_DEF(JSMSG_OF_AFTER_FOR_LOOP_DECL, 0, JSEXN_SYNTAXERR, "a declaration in the head of a for-of loop can't have an initializer")
MSG_DEF(JSMSG_IN_AFTER_LEXICAL_FOR_DECL,0,JSEXN_SYNTAXERR, "a lexical declaration in the head of a for-in loop can't have an initializer")
Expand Down Expand Up @@ -599,6 +599,7 @@ MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import")
MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT, 0, JSEXN_SYNTAXERR, "export not found for namespace")
MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
MSG_DEF(JSMSG_BAD_MODULE_STATUS, 0, JSEXN_INTERNALERR, "module record has unexpected status")
MSG_DEF(JSMSG_NO_DYNAMIC_IMPORT, 0, JSEXN_SYNTAXERR, "dynamic module import is not implemented")

// Promise
MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
Expand Down
1 change: 1 addition & 0 deletions js/src/jsast.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ASTDEF(AST_YIELD_EXPR, "YieldExpression", "yieldExpres
ASTDEF(AST_CLASS_EXPR, "ClassExpression", "classExpression")
ASTDEF(AST_METAPROPERTY, "MetaProperty", "metaProperty")
ASTDEF(AST_SUPER, "Super", "super")
ASTDEF(AST_CALL_IMPORT, "CallImport", "callImport")

ASTDEF(AST_EMPTY_STMT, "EmptyStatement", "emptyStatement")
ASTDEF(AST_BLOCK_STMT, "BlockStatement", "blockStatement")
Expand Down

0 comments on commit af31b3e

Please sign in to comment.