Skip to content

Commit

Permalink
Bug 1736060 - Part 6: Implement import assertions for dynamic imports…
Browse files Browse the repository at this point in the history
…. r=mgaudet,arai

Differential Revision: https://phabricator.services.mozilla.com/D126046
  • Loading branch information
Jon4t4n committed Dec 1, 2021
1 parent 75c0dea commit 5790da0
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 20 deletions.
141 changes: 139 additions & 2 deletions js/src/builtin/ModuleObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2370,8 +2370,137 @@ bool ModuleObject::topLevelCapabilityReject(JSContext* cx,
return AsyncFunctionThrown(cx, promise, error);
}

// https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call
// NOTE: The caller needs to handle the promise.
static bool EvaluateDynamicImportOptions(
JSContext* cx, HandleValue optionsArg,
MutableHandleArrayObject assertionArrayArg) {
// Step 10. If options is not undefined, then.
if (optionsArg.isUndefined()) {
return true;
}

// Step 10.a. If Type(options) is not Object,
if (!optionsArg.isObject()) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "import",
"object or undefined", InformalValueTypeName(optionsArg));
return false;
}

RootedObject assertWrapperObject(cx, &optionsArg.toObject());
RootedValue assertValue(cx);

// Step 10.b. Let assertionsObj be Get(options, "assert").
RootedId assertId(cx, NameToId(cx->names().assert_));
if (!GetProperty(cx, assertWrapperObject, assertWrapperObject, assertId,
&assertValue)) {
return false;
}

// Step 10.d. If assertionsObj is not undefined.
if (assertValue.isUndefined()) {
return true;
}

// Step 10.d.i. If Type(assertionsObj) is not Object.
if (!assertValue.isObject()) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "import",
"object or undefined", InformalValueTypeName(assertValue));
return false;
}

// Step 10.d.i. Let keys be EnumerableOwnPropertyNames(assertionsObj, key).
RootedObject assertObject(cx, &assertValue.toObject());
RootedIdVector assertions(cx);
if (!GetPropertyKeys(cx, assertObject, JSITER_OWNONLY, &assertions)) {
return false;
}

uint32_t numberOfAssertions = assertions.length();
if (numberOfAssertions == 0) {
return true;
}

// Step 9 (reordered). Let assertions be a new empty List.
RootedArrayObject assertionArray(
cx, NewDenseFullyAllocatedArray(cx, numberOfAssertions));
if (!assertionArray) {
return false;
}
assertionArray->ensureDenseInitializedLength(0, numberOfAssertions);

// Step 10.d.iv. Let supportedAssertions be
// !HostGetSupportedImportAssertions().
JS::ImportAssertionVector supportedAssertions;
bool succeeded = CallSupportedAssertionsHook(cx, supportedAssertions);
if (!succeeded) {
return false;
}

size_t numberOfValidAssertions = 0;

// Step 10.d.v. For each String key of keys,
RootedId key(cx);
for (size_t i = 0; i < numberOfAssertions; i++) {
key = assertions[i];

// Step 10.d.v.1. Let value be Get(assertionsObj, key).
RootedValue value(cx);
if (!GetProperty(cx, assertObject, assertObject, key, &value)) {
return false;
}

// Step 10.d.v.3. If Type(value) is not String, then.
if (!value.isString()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_EXPECTED_TYPE, "import", "string",
InformalValueTypeName(value));
return false;
}

// Step 10.d.v.4. If supportedAssertions contains key, then Append {
// [[Key]]: key, [[Value]]: value } to assertions.
for (JS::ImportAssertion assertion : supportedAssertions) {
bool supported = false;
switch (assertion) {
case JS::ImportAssertion::Type: {
supported = key.toAtom() == cx->names().type;
} break;
}

if (supported) {
RootedPlainObject assertionObj(cx, NewPlainObject(cx));
if (!assertionObj) {
return false;
}

if (!DefineDataProperty(cx, assertionObj, key, value,
JSPROP_ENUMERATE)) {
return false;
}

assertionArray->initDenseElement(numberOfValidAssertions,
ObjectValue(*assertionObj));
++numberOfValidAssertions;
}
}
}

if (numberOfValidAssertions == 0) {
return true;
}

assertionArray->setLength(numberOfValidAssertions);
assertionArrayArg.set(assertionArray);

return true;
}

JSObject* js::StartDynamicModuleImport(JSContext* cx, HandleScript script,
HandleValue specifierArg) {
HandleValue specifierArg,
HandleValue optionsArg) {
RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx));
if (!promiseConstructor) {
return nullptr;
Expand Down Expand Up @@ -2418,8 +2547,16 @@ JSObject* js::StartDynamicModuleImport(JSContext* cx, HandleScript script,
return promise;
}

RootedArrayObject assertionArray(cx);
if (!EvaluateDynamicImportOptions(cx, optionsArg, &assertionArray)) {
if (!RejectPromiseWithPendingError(cx, promise)) {
return nullptr;
}
return promise;
}

RootedObject moduleRequest(
cx, ModuleRequestObject::create(cx, specifierAtom, nullptr));
cx, ModuleRequestObject::create(cx, specifierAtom, assertionArray));
if (!moduleRequest) {
if (!RejectPromiseWithPendingError(cx, promise)) {
return nullptr;
Expand Down
2 changes: 1 addition & 1 deletion js/src/builtin/ModuleObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ bool AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc,
Value* vp);

JSObject* StartDynamicModuleImport(JSContext* cx, HandleScript script,
HandleValue specifier);
HandleValue specifier, HandleValue options);

bool OnModuleEvaluationFailure(JSContext* cx, HandleObject evaluationPromise);

Expand Down
30 changes: 27 additions & 3 deletions js/src/frontend/BytecodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,7 @@ bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) {
return true;

case ParseNodeKind::CallImportExpr:
case ParseNodeKind::CallImportSpec:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
Expand Down Expand Up @@ -1543,6 +1544,9 @@ bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) {
case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import
case ParseNodeKind::ImportNamespaceSpec: // by ParseNodeKind::Import
case ParseNodeKind::ImportAssertion: // by ParseNodeKind::Import
case ParseNodeKind::ImportAssertionList: // by ParseNodeKind::Import
case ParseNodeKind::ImportModuleRequest: // by ParseNodeKind::Import
case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export
Expand Down Expand Up @@ -11635,12 +11639,32 @@ bool BytecodeEmitter::emitTree(
}
break;

case ParseNodeKind::CallImportExpr:
if (!emitTree(pn->as<BinaryNode>().right()) ||
!emit1(JSOp::DynamicImport)) {
case ParseNodeKind::CallImportExpr: {
BinaryNode* spec = &pn->as<BinaryNode>().right()->as<BinaryNode>();

if (!emitTree(spec->left())) {
// [stack] specifier
return false;
}

if (!spec->right()->isKind(ParseNodeKind::PosHolder)) {
// [stack] specifier options
if (!emitTree(spec->right())) {
return false;
}
} else {
// [stack] specifier undefined
if (!emit1(JSOp::Undefined)) {
return false;
}
}

if (!emit1(JSOp::DynamicImport)) {
return false;
}

break;
}

case ParseNodeKind::SetThis:
if (!emitSetThis(&pn->as<BinaryNode>())) {
Expand Down
1 change: 1 addition & 0 deletions js/src/frontend/FoldConstants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ static bool ContainsHoistedDeclaration(JSContext* cx, ParseNode* node,
case ParseNodeKind::ExportStmt:
case ParseNodeKind::ExportBatchSpecStmt:
case ParseNodeKind::CallImportExpr:
case ParseNodeKind::CallImportSpec:
case ParseNodeKind::ImportAssertionList:
case ParseNodeKind::ImportAssertion:
case ParseNodeKind::ImportModuleRequest:
Expand Down
5 changes: 5 additions & 0 deletions js/src/frontend/FullParseHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,11 @@ class FullParseHandler {
singleArg);
}

BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) {
return new_<BinaryNode>(ParseNodeKind::CallImportSpec, specifierArg,
optionalArg);
}

UnaryNodeType newExprStatement(Node expr, uint32_t end) {
MOZ_ASSERT(expr->pn_pos.end <= end);
return new_<UnaryNode>(ParseNodeKind::ExpressionStmt,
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 @@ -170,6 +170,7 @@ class FunctionBox;
F(SetThis, BinaryNode) \
F(ImportMetaExpr, BinaryNode) \
F(CallImportExpr, BinaryNode) \
F(CallImportSpec, BinaryNode) \
F(InitExpr, BinaryNode) \
\
/* Unary operators. */ \
Expand Down
55 changes: 54 additions & 1 deletion js/src/frontend/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12076,11 +12076,64 @@ GeneralParser<ParseHandler, Unit>::importExpr(YieldHandling yieldHandling,
return null();
}

if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
return null();
}

Node optionalArg;
if (options().importAssertions) {
if (next == TokenKind::Comma) {
tokenStream.consumeKnownToken(TokenKind::Comma,
TokenStream::SlashIsRegExp);

if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
return null();
}

if (next != TokenKind::RightParen) {
optionalArg =
assignExpr(InAllowed, yieldHandling, TripledotProhibited);
if (!optionalArg) {
return null();
}

if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
return null();
}

if (next == TokenKind::Comma) {
tokenStream.consumeKnownToken(TokenKind::Comma,
TokenStream::SlashIsRegExp);
}
} else {
optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
if (!optionalArg) {
return null();
}
}
} else {
optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
if (!optionalArg) {
return null();
}
}
} else {
optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
if (!optionalArg) {
return null();
}
}

if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_ARGS)) {
return null();
}

return handler_.newCallImport(importHolder, arg);
Node spec = handler_.newCallImportSpec(arg, optionalArg);
if (!spec) {
return null();
}

return handler_.newCallImport(importHolder, spec);
} else {
error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next));
return null();
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 @@ -464,6 +464,9 @@ class SyntaxParseHandler {
BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) {
return NodeGeneric;
}
BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) {
return NodeGeneric;
}

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

Expand Down
12 changes: 9 additions & 3 deletions js/src/jit-test/tests/modules/dynamic-import-expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,16 @@ function assertParseThrowsSyntaxError(source)
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)");

if (!getRealmConfiguration()['importAssertions']) {
assertParseThrowsSyntaxError("import(1, 2");
assertParseThrowsSyntaxError("import(1, 2)");
assertParseThrowsSyntaxError("x = import(1, 2)");
}
else {
assertParseThrowsSyntaxError("import(1, 2, 3)");
}
7 changes: 4 additions & 3 deletions js/src/jit/BaselineCodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6389,14 +6389,15 @@ bool BaselineInterpreterCodeGen::emit_ImportMeta() {

template <typename Handler>
bool BaselineCodeGen<Handler>::emit_DynamicImport() {
// Put specifier value in R0.
frame.popRegsAndSync(1);
// Put specifier into R0 and object value into R1
frame.popRegsAndSync(2);

prepareVMCall();
pushArg(R1);
pushArg(R0);
pushScriptArg();

using Fn = JSObject* (*)(JSContext*, HandleScript, HandleValue);
using Fn = JSObject* (*)(JSContext*, HandleScript, HandleValue, HandleValue);
if (!callVM<Fn, js::StartDynamicModuleImport>()) {
return false;
}
Expand Down
3 changes: 2 additions & 1 deletion js/src/jit/CodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3454,10 +3454,11 @@ void CodeGenerator::visitModuleMetadata(LModuleMetadata* lir) {
}

void CodeGenerator::visitDynamicImport(LDynamicImport* lir) {
pushArg(ToValue(lir, LDynamicImport::OptionsIndex));
pushArg(ToValue(lir, LDynamicImport::SpecifierIndex));
pushArg(ImmGCPtr(current->mir()->info().script()));

using Fn = JSObject* (*)(JSContext*, HandleScript, HandleValue);
using Fn = JSObject* (*)(JSContext*, HandleScript, HandleValue, HandleValue);
callVM<Fn, js::StartDynamicModuleImport>(lir);
}

Expand Down
1 change: 1 addition & 0 deletions js/src/jit/LIROps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,7 @@
result_type: WordSized
operands:
specifier: BoxedValue
options: BoxedValue
call_instruction: true
mir_op: true

Expand Down
4 changes: 2 additions & 2 deletions js/src/jit/Lowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2971,8 +2971,8 @@ void LIRGenerator::visitModuleMetadata(MModuleMetadata* ins) {
}

void LIRGenerator::visitDynamicImport(MDynamicImport* ins) {
LDynamicImport* lir =
new (alloc()) LDynamicImport(useBoxAtStart(ins->specifier()));
LDynamicImport* lir = new (alloc()) LDynamicImport(
useBoxAtStart(ins->specifier()), useBoxAtStart(ins->options()));
defineReturn(lir, ins);
assignSafepoint(lir, ins);
}
Expand Down
Loading

0 comments on commit 5790da0

Please sign in to comment.