// Copyright 2022 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/v8-function.h" #include "src/flags/flags.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace { using ModuleTest = v8::TestWithContext; using v8::Context; using v8::Data; using v8::FixedArray; using v8::HandleScope; using v8::Int32; using v8::Isolate; using v8::Local; using v8::Location; using v8::MaybeLocal; using v8::Module; using v8::ModuleRequest; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::Value; ScriptOrigin ModuleOrigin(Local resource_name, Isolate* isolate) { ScriptOrigin origin(isolate, resource_name, 0, 0, false, -1, Local(), false, false, true); return origin; } static Local dep1; static Local dep2; MaybeLocal ResolveCallback(Local context, Local specifier, Local import_assertions, Local referrer) { CHECK_EQ(0, import_assertions->Length()); Isolate* isolate = context->GetIsolate(); if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./dep1.js").ToLocalChecked())) { return dep1; } else if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./dep2.js").ToLocalChecked())) { return dep2; } else { isolate->ThrowException( String::NewFromUtf8(isolate, "boom").ToLocalChecked()); return MaybeLocal(); } } TEST_F(ModuleTest, ModuleInstantiationFailures1) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local module; { Local source_text = NewString( "import './foo.js';\n" "export {} from './bar.js';"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); Local module_requests = module->GetModuleRequests(); CHECK_EQ(2, module_requests->Length()); Local module_request_0 = module_requests->Get(context(), 0).As(); CHECK( NewString("./foo.js")->StrictEquals(module_request_0->GetSpecifier())); int offset = module_request_0->GetSourceOffset(); CHECK_EQ(7, offset); Location loc = module->SourceOffsetToLocation(offset); CHECK_EQ(0, loc.GetLineNumber()); CHECK_EQ(7, loc.GetColumnNumber()); CHECK_EQ(0, module_request_0->GetImportAssertions()->Length()); Local module_request_1 = module_requests->Get(context(), 1).As(); CHECK( NewString("./bar.js")->StrictEquals(module_request_1->GetSpecifier())); offset = module_request_1->GetSourceOffset(); CHECK_EQ(34, offset); loc = module->SourceOffsetToLocation(offset); CHECK_EQ(1, loc.GetLineNumber()); CHECK_EQ(15, loc.GetColumnNumber()); CHECK_EQ(0, module_request_1->GetImportAssertions()->Length()); } // Instantiation should fail. { v8::TryCatch inner_try_catch(isolate()); CHECK(module->InstantiateModule(context(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(NewString("boom"))); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); } // Start over again... { Local source_text = NewString( "import './dep1.js';\n" "export {} from './bar.js';"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } // dep1.js { Local source_text = NewString(""); ScriptOrigin origin = ModuleOrigin(NewString("dep1.js"), isolate()); ScriptCompiler::Source source(source_text, origin); dep1 = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } // Instantiation should fail because a sub-module fails to resolve. { v8::TryCatch inner_try_catch(isolate()); CHECK(module->InstantiateModule(context(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(NewString("boom"))); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); } CHECK(!try_catch.HasCaught()); } static Local fooModule; static Local barModule; MaybeLocal ResolveCallbackWithImportAssertions( Local context, Local specifier, Local import_assertions, Local referrer) { Isolate* isolate = context->GetIsolate(); if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./foo.js").ToLocalChecked())) { CHECK_EQ(0, import_assertions->Length()); return fooModule; } else if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./bar.js").ToLocalChecked())) { CHECK_EQ(3, import_assertions->Length()); Local assertion_key = import_assertions->Get(context, 0).As().As(); CHECK(String::NewFromUtf8(isolate, "a") .ToLocalChecked() ->StrictEquals(assertion_key)); Local assertion_value = import_assertions->Get(context, 1).As().As(); CHECK(String::NewFromUtf8(isolate, "b") .ToLocalChecked() ->StrictEquals(assertion_value)); Local assertion_source_offset_object = import_assertions->Get(context, 2); Local assertion_source_offset_int32 = assertion_source_offset_object.As() ->ToInt32(context) .ToLocalChecked(); int32_t assertion_source_offset = assertion_source_offset_int32->Value(); CHECK_EQ(65, assertion_source_offset); Location loc = referrer->SourceOffsetToLocation(assertion_source_offset); CHECK_EQ(1, loc.GetLineNumber()); CHECK_EQ(35, loc.GetColumnNumber()); return barModule; } else { isolate->ThrowException( String::NewFromUtf8(isolate, "boom").ToLocalChecked()); return MaybeLocal(); } } TEST_F(ModuleTest, ModuleInstantiationWithImportAssertions) { bool prev_import_assertions = i::v8_flags.harmony_import_assertions; i::v8_flags.harmony_import_assertions = true; HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local module; { Local source_text = NewString( "import './foo.js' assert { };\n" "export {} from './bar.js' assert { a: 'b' };"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); Local module_requests = module->GetModuleRequests(); CHECK_EQ(2, module_requests->Length()); Local module_request_0 = module_requests->Get(context(), 0).As(); CHECK( NewString("./foo.js")->StrictEquals(module_request_0->GetSpecifier())); int offset = module_request_0->GetSourceOffset(); CHECK_EQ(7, offset); Location loc = module->SourceOffsetToLocation(offset); CHECK_EQ(0, loc.GetLineNumber()); CHECK_EQ(7, loc.GetColumnNumber()); CHECK_EQ(0, module_request_0->GetImportAssertions()->Length()); Local module_request_1 = module_requests->Get(context(), 1).As(); CHECK( NewString("./bar.js")->StrictEquals(module_request_1->GetSpecifier())); offset = module_request_1->GetSourceOffset(); CHECK_EQ(45, offset); loc = module->SourceOffsetToLocation(offset); CHECK_EQ(1, loc.GetLineNumber()); CHECK_EQ(15, loc.GetColumnNumber()); Local import_assertions_1 = module_request_1->GetImportAssertions(); CHECK_EQ(3, import_assertions_1->Length()); Local assertion_key = import_assertions_1->Get(context(), 0).As(); CHECK(NewString("a")->StrictEquals(assertion_key)); Local assertion_value = import_assertions_1->Get(context(), 1).As(); CHECK(NewString("b")->StrictEquals(assertion_value)); int32_t assertion_source_offset = import_assertions_1->Get(context(), 2).As()->Value(); CHECK_EQ(65, assertion_source_offset); loc = module->SourceOffsetToLocation(assertion_source_offset); CHECK_EQ(1, loc.GetLineNumber()); CHECK_EQ(35, loc.GetColumnNumber()); } // foo.js { Local source_text = NewString("Object.expando = 40"); ScriptOrigin origin = ModuleOrigin(NewString("foo.js"), isolate()); ScriptCompiler::Source source(source_text, origin); fooModule = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } // bar.js { Local source_text = NewString("Object.expando += 2"); ScriptOrigin origin = ModuleOrigin(NewString("bar.js"), isolate()); ScriptCompiler::Source source(source_text, origin); barModule = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } CHECK( module->InstantiateModule(context(), ResolveCallbackWithImportAssertions) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); MaybeLocal result = module->Evaluate(context()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local promise = Local::Cast(result.ToLocalChecked()); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // TODO(v8:12781): One IsInt32 matcher be added in // gmock-support.h, we could use IsInt32 to replace // this. { Local result = RunJS("Object.expando"); CHECK(result->IsInt32()); CHECK_EQ(42, result->Int32Value(context()).FromJust()); } CHECK(!try_catch.HasCaught()); i::v8_flags.harmony_import_assertions = prev_import_assertions; } TEST_F(ModuleTest, ModuleInstantiationFailures2) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); // root1.js Local root; { Local source_text = NewString("import './dep1.js'; import './dep2.js'"); ScriptOrigin origin = ModuleOrigin(NewString("root1.js"), isolate()); ScriptCompiler::Source source(source_text, origin); root = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } // dep1.js { Local source_text = NewString("export let x = 42"); ScriptOrigin origin = ModuleOrigin(NewString("dep1.js"), isolate()); ScriptCompiler::Source source(source_text, origin); dep1 = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } // dep2.js { Local source_text = NewString("import {foo} from './dep3.js'"); ScriptOrigin origin = ModuleOrigin(NewString("dep2.js"), isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate()); CHECK(root->InstantiateModule(context(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(NewString("boom"))); CHECK_EQ(Module::kUninstantiated, root->GetStatus()); CHECK_EQ(Module::kUninstantiated, dep1->GetStatus()); CHECK_EQ(Module::kUninstantiated, dep2->GetStatus()); } // Change dep2.js { Local source_text = NewString("import {foo} from './dep2.js'"); ScriptOrigin origin = ModuleOrigin(NewString("dep2.js"), isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate()); CHECK(root->InstantiateModule(context(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(!inner_try_catch.Exception()->StrictEquals(NewString("boom"))); CHECK_EQ(Module::kUninstantiated, root->GetStatus()); CHECK_EQ(Module::kInstantiated, dep1->GetStatus()); CHECK_EQ(Module::kUninstantiated, dep2->GetStatus()); } // Change dep2.js again { Local source_text = NewString("import {foo} from './dep3.js'"); ScriptOrigin origin = ModuleOrigin(NewString("dep2.js"), isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate()); CHECK(root->InstantiateModule(context(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(NewString("boom"))); CHECK_EQ(Module::kUninstantiated, root->GetStatus()); CHECK_EQ(Module::kInstantiated, dep1->GetStatus()); CHECK_EQ(Module::kUninstantiated, dep2->GetStatus()); } } static MaybeLocal CompileSpecifierAsModuleResolveCallback( Local context, Local specifier, Local import_assertions, Local referrer) { CHECK_EQ(0, import_assertions->Length()); Isolate* isolate = context->GetIsolate(); ScriptOrigin origin = ModuleOrigin( String::NewFromUtf8(isolate, "module.js").ToLocalChecked(), isolate); ScriptCompiler::Source source(specifier, origin); return ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } TEST_F(ModuleTest, ModuleEvaluation) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local source_text = NewString( "import 'Object.expando = 5';" "import 'Object.expando *= 2';"); ScriptOrigin origin = ModuleOrigin( String::NewFromUtf8(isolate(), "file.js").ToLocalChecked(), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); MaybeLocal result = module->Evaluate(context()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local promise = result.ToLocalChecked().As(); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // TODO(v8:12781): One IsInt32 matcher be added in // gmock-support.h, we could use IsInt32 to replace // this. { Local result = RunJS("Object.expando"); CHECK(result->IsInt32()); CHECK_EQ(10, result->Int32Value(context()).FromJust()); } CHECK(!try_catch.HasCaught()); } TEST_F(ModuleTest, ModuleEvaluationError1) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local source_text = NewString("Object.x = (Object.x || 0) + 1; throw 'boom';"); ScriptOrigin origin = ModuleOrigin( String::NewFromUtf8(isolate(), "file.js").ToLocalChecked(), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); { v8::TryCatch inner_try_catch(isolate()); MaybeLocal result = module->Evaluate(context()); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(NewString("boom"))); // TODO(v8:12781): One IsInt32 matcher be added in // gmock-support.h, we could use IsInt32 to replace // this. { Local result = RunJS("Object.x"); CHECK(result->IsInt32()); CHECK_EQ(1, result->Int32Value(context()).FromJust()); } // With top level await, we do not throw and errored evaluation returns // a rejected promise with the exception. CHECK(!inner_try_catch.HasCaught()); Local promise = result.ToLocalChecked().As(); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK_EQ(promise->Result(), module->GetException()); } { v8::TryCatch inner_try_catch(isolate()); MaybeLocal result = module->Evaluate(context()); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(NewString("boom"))); // TODO(v8:12781): One IsInt32 matcher be added in // gmock-support.h, we could use IsInt32 to replace // this. { Local result = RunJS("Object.x"); CHECK(result->IsInt32()); CHECK_EQ(1, result->Int32Value(context()).FromJust()); } // With top level await, we do not throw and errored evaluation returns // a rejected promise with the exception. CHECK(!inner_try_catch.HasCaught()); Local promise = result.ToLocalChecked().As(); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK_EQ(promise->Result(), module->GetException()); } CHECK(!try_catch.HasCaught()); } static Local failure_module; static Local dependent_module; MaybeLocal ResolveCallbackForModuleEvaluationError2( Local context, Local specifier, Local import_assertions, Local referrer) { CHECK_EQ(0, import_assertions->Length()); Isolate* isolate = context->GetIsolate(); if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./failure.js").ToLocalChecked())) { return failure_module; } else { CHECK(specifier->StrictEquals( String::NewFromUtf8(isolate, "./dependent.js").ToLocalChecked())); return dependent_module; } } TEST_F(ModuleTest, ModuleEvaluationError2) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local failure_text = NewString("throw 'boom';"); ScriptOrigin failure_origin = ModuleOrigin(NewString("failure.js"), isolate()); ScriptCompiler::Source failure_source(failure_text, failure_origin); failure_module = ScriptCompiler::CompileModule(isolate(), &failure_source) .ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, failure_module->GetStatus()); CHECK(failure_module ->InstantiateModule(context(), ResolveCallbackForModuleEvaluationError2) .FromJust()); CHECK_EQ(Module::kInstantiated, failure_module->GetStatus()); { v8::TryCatch inner_try_catch(isolate()); MaybeLocal result = failure_module->Evaluate(context()); CHECK_EQ(Module::kErrored, failure_module->GetStatus()); Local exception = failure_module->GetException(); CHECK(exception->StrictEquals(NewString("boom"))); // With top level await, we do not throw and errored evaluation returns // a rejected promise with the exception. CHECK(!inner_try_catch.HasCaught()); Local promise = result.ToLocalChecked().As(); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK_EQ(promise->Result(), failure_module->GetException()); } Local dependent_text = NewString("import './failure.js'; export const c = 123;"); ScriptOrigin dependent_origin = ModuleOrigin(NewString("dependent.js"), isolate()); ScriptCompiler::Source dependent_source(dependent_text, dependent_origin); dependent_module = ScriptCompiler::CompileModule(isolate(), &dependent_source) .ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, dependent_module->GetStatus()); CHECK(dependent_module ->InstantiateModule(context(), ResolveCallbackForModuleEvaluationError2) .FromJust()); CHECK_EQ(Module::kInstantiated, dependent_module->GetStatus()); { v8::TryCatch inner_try_catch(isolate()); MaybeLocal result = dependent_module->Evaluate(context()); CHECK_EQ(Module::kErrored, dependent_module->GetStatus()); Local exception = dependent_module->GetException(); CHECK(exception->StrictEquals(NewString("boom"))); CHECK_EQ(exception, failure_module->GetException()); // With top level await, we do not throw and errored evaluation returns // a rejected promise with the exception. CHECK(!inner_try_catch.HasCaught()); Local promise = result.ToLocalChecked().As(); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK_EQ(promise->Result(), failure_module->GetException()); CHECK(failure_module->GetException()->StrictEquals(NewString("boom"))); } CHECK(!try_catch.HasCaught()); } TEST_F(ModuleTest, ModuleEvaluationCompletion1) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); const char* sources[] = { "", "var a = 1", "import '42'", "export * from '42'", "export {} from '42'", "export {}", "var a = 1; export {a}", "export function foo() {}", "export class C extends null {}", "export let a = 1", "export default 1", "export default function foo() {}", "export default function () {}", "export default (function () {})", "export default class C extends null {}", "export default (class C extends null {})", "for (var i = 0; i < 5; ++i) {}", }; for (auto src : sources) { Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); // Evaluate twice. Local result_1 = module->Evaluate(context()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local result_2 = module->Evaluate(context()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local promise = result_1.As(); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // Second evaluation should return the same promise. Local promise_too = result_2.As(); CHECK_EQ(promise, promise_too); CHECK_EQ(promise_too->State(), v8::Promise::kFulfilled); CHECK(promise_too->Result()->IsUndefined()); } CHECK(!try_catch.HasCaught()); } TEST_F(ModuleTest, ModuleEvaluationCompletion2) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); const char* sources[] = { "'gaga'; ", "'gaga'; var a = 1", "'gaga'; import '42'", "'gaga'; export * from '42'", "'gaga'; export {} from '42'", "'gaga'; export {}", "'gaga'; var a = 1; export {a}", "'gaga'; export function foo() {}", "'gaga'; export class C extends null {}", "'gaga'; export let a = 1", "'gaga'; export default 1", "'gaga'; export default function foo() {}", "'gaga'; export default function () {}", "'gaga'; export default (function () {})", "'gaga'; export default class C extends null {}", "'gaga'; export default (class C extends null {})", }; for (auto src : sources) { Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local result_1 = module->Evaluate(context()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local result_2 = module->Evaluate(context()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local promise = result_1.As(); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // Second Evaluation should return the same promise. Local promise_too = result_2.As(); CHECK_EQ(promise, promise_too); CHECK_EQ(promise_too->State(), v8::Promise::kFulfilled); CHECK(promise_too->Result()->IsUndefined()); } CHECK(!try_catch.HasCaught()); } TEST_F(ModuleTest, ModuleNamespace) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); Local ReferenceError = RunJS("ReferenceError")->ToObject(context()).ToLocalChecked(); Local source_text = NewString( "import {a, b} from 'export var a = 1; export let b = 2';" "export function geta() {return a};" "export function getb() {return b};" "export let radio = 3;" "export var gaga = 4;"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local ns = module->GetModuleNamespace(); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local nsobj = ns->ToObject(context()).ToLocalChecked(); CHECK_EQ(nsobj->GetCreationContext().ToLocalChecked(), context()); // a, b CHECK(nsobj->Get(context(), NewString("a")).ToLocalChecked()->IsUndefined()); CHECK(nsobj->Get(context(), NewString("b")).ToLocalChecked()->IsUndefined()); // geta { auto geta = nsobj->Get(context(), NewString("geta")).ToLocalChecked(); auto a = geta.As() ->Call(context(), geta, 0, nullptr) .ToLocalChecked(); CHECK(a->IsUndefined()); } // getb { v8::TryCatch inner_try_catch(isolate()); auto getb = nsobj->Get(context(), NewString("getb")).ToLocalChecked(); CHECK(getb.As()->Call(context(), getb, 0, nullptr).IsEmpty()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception() ->InstanceOf(context(), ReferenceError) .FromJust()); } // radio { v8::TryCatch inner_try_catch(isolate()); // https://bugs.chromium.org/p/v8/issues/detail?id=7235 // CHECK(nsobj->Get(context(), NewString("radio")).IsEmpty()); CHECK(nsobj->Get(context(), NewString("radio")) .ToLocalChecked() ->IsUndefined()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception() ->InstanceOf(context(), ReferenceError) .FromJust()); } // gaga { auto gaga = nsobj->Get(context(), NewString("gaga")).ToLocalChecked(); CHECK(gaga->IsUndefined()); } CHECK(!try_catch.HasCaught()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); module->Evaluate(context()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); // geta { auto geta = nsobj->Get(context(), NewString("geta")).ToLocalChecked(); auto a = geta.As() ->Call(context(), geta, 0, nullptr) .ToLocalChecked(); CHECK_EQ(1, a->Int32Value(context()).FromJust()); } // getb { auto getb = nsobj->Get(context(), NewString("getb")).ToLocalChecked(); auto b = getb.As() ->Call(context(), getb, 0, nullptr) .ToLocalChecked(); CHECK_EQ(2, b->Int32Value(context()).FromJust()); } // radio { auto radio = nsobj->Get(context(), NewString("radio")).ToLocalChecked(); CHECK_EQ(3, radio->Int32Value(context()).FromJust()); } // gaga { auto gaga = nsobj->Get(context(), NewString("gaga")).ToLocalChecked(); CHECK_EQ(4, gaga->Int32Value(context()).FromJust()); } CHECK(!try_catch.HasCaught()); } TEST_F(ModuleTest, ModuleEvaluationTopLevelAwait) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); const char* sources[] = { "await 42", "import 'await 42';", "import '42'; import 'await 42';", }; for (auto src : sources) { Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local promise = Local::Cast(module->Evaluate(context()).ToLocalChecked()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); CHECK(!try_catch.HasCaught()); } } TEST_F(ModuleTest, ModuleEvaluationTopLevelAwaitError) { HandleScope scope(isolate()); const char* sources[] = { "await 42; throw 'boom';", "import 'await 42; throw \"boom\";';", "import '42'; import 'await 42; throw \"boom\";';", }; for (auto src : sources) { v8::TryCatch try_catch(isolate()); Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local promise = Local::Cast(module->Evaluate(context()).ToLocalChecked()); CHECK_EQ(Module::kErrored, module->GetStatus()); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK(promise->Result()->StrictEquals(NewString("boom"))); CHECK(module->GetException()->StrictEquals(NewString("boom"))); // TODO(cbruni) I am not sure, but this might not be supposed to throw // because it is async. CHECK(!try_catch.HasCaught()); } } namespace { struct DynamicImportData { DynamicImportData(Isolate* isolate_, Local resolver_, Local context_, bool should_resolve_) : isolate(isolate_), should_resolve(should_resolve_) { resolver.Reset(isolate, resolver_); context.Reset(isolate, context_); } Isolate* isolate; v8::Global resolver; v8::Global context; bool should_resolve; }; void DoHostImportModuleDynamically(void* import_data) { std::unique_ptr import_data_( static_cast(import_data)); Isolate* isolate(import_data_->isolate); HandleScope handle_scope(isolate); Local resolver(import_data_->resolver.Get(isolate)); Local realm(import_data_->context.Get(isolate)); Context::Scope context_scope(realm); if (import_data_->should_resolve) { resolver->Resolve(realm, True(isolate)).ToChecked(); } else { resolver ->Reject(realm, String::NewFromUtf8(isolate, "boom").ToLocalChecked()) .ToChecked(); } } v8::MaybeLocal HostImportModuleDynamicallyCallbackResolve( Local context, Local host_defined_options, Local resource_name, Local specifier, Local import_assertions) { Isolate* isolate = context->GetIsolate(); Local resolver = v8::Promise::Resolver::New(context).ToLocalChecked(); DynamicImportData* data = new DynamicImportData(isolate, resolver, context, true); isolate->EnqueueMicrotask(DoHostImportModuleDynamically, data); return resolver->GetPromise(); } v8::MaybeLocal HostImportModuleDynamicallyCallbackReject( Local context, Local host_defined_options, Local resource_name, Local specifier, Local import_assertions) { Isolate* isolate = context->GetIsolate(); Local resolver = v8::Promise::Resolver::New(context).ToLocalChecked(); DynamicImportData* data = new DynamicImportData(isolate, resolver, context, false); isolate->EnqueueMicrotask(DoHostImportModuleDynamically, data); return resolver->GetPromise(); } } // namespace TEST_F(ModuleTest, ModuleEvaluationTopLevelAwaitDynamicImport) { HandleScope scope(isolate()); isolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); isolate()->SetHostImportModuleDynamicallyCallback( HostImportModuleDynamicallyCallbackResolve); v8::TryCatch try_catch(isolate()); const char* sources[] = { "await import('foo');", "import 'await import(\"foo\");';", "import '42'; import 'await import(\"foo\");';", }; for (auto src : sources) { Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local promise = Local::Cast(module->Evaluate(context()).ToLocalChecked()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); CHECK_EQ(promise->State(), v8::Promise::kPending); CHECK(!try_catch.HasCaught()); isolate()->PerformMicrotaskCheckpoint(); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); } } TEST_F(ModuleTest, ModuleEvaluationTopLevelAwaitDynamicImportError) { HandleScope scope(isolate()); isolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); isolate()->SetHostImportModuleDynamicallyCallback( HostImportModuleDynamicallyCallbackReject); v8::TryCatch try_catch(isolate()); const char* sources[] = { "await import('foo');", "import 'await import(\"foo\");';", "import '42'; import 'await import(\"foo\");';", }; for (auto src : sources) { Local source_text = NewString(src); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local promise = Local::Cast(module->Evaluate(context()).ToLocalChecked()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); CHECK_EQ(promise->State(), v8::Promise::kPending); CHECK(!try_catch.HasCaught()); isolate()->PerformMicrotaskCheckpoint(); CHECK_EQ(Module::kErrored, module->GetStatus()); CHECK_EQ(promise->State(), v8::Promise::kRejected); CHECK(promise->Result()->StrictEquals(NewString("boom"))); CHECK(module->GetException()->StrictEquals(NewString("boom"))); CHECK(!try_catch.HasCaught()); } } TEST_F(ModuleTest, TerminateExecutionTopLevelAwaitSync) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); context() ->Global() ->Set(context(), NewString("terminate"), v8::Function::New(context(), [](const v8::FunctionCallbackInfo& info) { info.GetIsolate()->TerminateExecution(); }) .ToLocalChecked()) .ToChecked(); Local source_text = NewString("terminate(); while (true) {}"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK(module->Evaluate(context()).IsEmpty()); CHECK(try_catch.HasCaught()); CHECK(try_catch.HasTerminated()); CHECK_EQ(module->GetStatus(), Module::kErrored); CHECK_EQ(module->GetException(), v8::Null(isolate())); } TEST_F(ModuleTest, TerminateExecutionTopLevelAwaitAsync) { HandleScope scope(isolate()); v8::TryCatch try_catch(isolate()); context() ->Global() ->Set(context(), NewString("terminate"), v8::Function::New(context(), [](const v8::FunctionCallbackInfo& info) { info.GetIsolate()->TerminateExecution(); }) .ToLocalChecked()) .ToChecked(); Local eval_promise = Promise::Resolver::New(context()).ToLocalChecked(); context() ->Global() ->Set(context(), NewString("evalPromise"), eval_promise) .ToChecked(); Local source_text = NewString("await evalPromise; terminate(); while (true) {}"); ScriptOrigin origin = ModuleOrigin(NewString("file.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(module ->InstantiateModule(context(), CompileSpecifierAsModuleResolveCallback) .FromJust()); Local promise = Local::Cast(module->Evaluate(context()).ToLocalChecked()); CHECK_EQ(module->GetStatus(), Module::kEvaluated); CHECK_EQ(promise->State(), Promise::PromiseState::kPending); CHECK(!try_catch.HasCaught()); CHECK(!try_catch.HasTerminated()); eval_promise->Resolve(context(), v8::Undefined(isolate())).ToChecked(); CHECK(try_catch.HasCaught()); CHECK(try_catch.HasTerminated()); CHECK_EQ(promise->State(), Promise::PromiseState::kPending); // The termination exception doesn't trigger the module's // catch handler, so the module isn't transitioned to kErrored. CHECK_EQ(module->GetStatus(), Module::kEvaluated); } static Local async_leaf_module; static Local sync_leaf_module; static Local cycle_self_module; static Local cycle_one_module; static Local cycle_two_module; MaybeLocal ResolveCallbackForIsGraphAsyncTopLevelAwait( Local context, Local specifier, Local import_assertions, Local referrer) { CHECK_EQ(0, import_assertions->Length()); Isolate* isolate = context->GetIsolate(); if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./async_leaf.js").ToLocalChecked())) { return async_leaf_module; } else if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./sync_leaf.js") .ToLocalChecked())) { return sync_leaf_module; } else if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./cycle_self.js") .ToLocalChecked())) { return cycle_self_module; } else if (specifier->StrictEquals( String::NewFromUtf8(isolate, "./cycle_one.js") .ToLocalChecked())) { return cycle_one_module; } else { CHECK(specifier->StrictEquals( String::NewFromUtf8(isolate, "./cycle_two.js").ToLocalChecked())); return cycle_two_module; } } TEST_F(ModuleTest, IsGraphAsyncTopLevelAwait) { HandleScope scope(isolate()); { Local source_text = NewString("await notExecuted();"); ScriptOrigin origin = ModuleOrigin(NewString("async_leaf.js"), isolate()); ScriptCompiler::Source source(source_text, origin); async_leaf_module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(async_leaf_module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(async_leaf_module->IsGraphAsync()); } { Local source_text = NewString("notExecuted();"); ScriptOrigin origin = ModuleOrigin(NewString("sync_leaf.js"), isolate()); ScriptCompiler::Source source(source_text, origin); sync_leaf_module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(sync_leaf_module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(!sync_leaf_module->IsGraphAsync()); } { Local source_text = NewString("import './async_leaf.js'"); ScriptOrigin origin = ModuleOrigin(NewString("import_async.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(module->IsGraphAsync()); } { Local source_text = NewString("import './sync_leaf.js'"); ScriptOrigin origin = ModuleOrigin(NewString("import_sync.js"), isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(!module->IsGraphAsync()); } { Local source_text = NewString( "import './cycle_self.js'\n" "import './async_leaf.js'"); ScriptOrigin origin = ModuleOrigin(NewString("cycle_self.js"), isolate()); ScriptCompiler::Source source(source_text, origin); cycle_self_module = ScriptCompiler::CompileModule(isolate(), &source).ToLocalChecked(); CHECK(cycle_self_module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(cycle_self_module->IsGraphAsync()); } { Local source_text1 = NewString("import './cycle_two.js'"); ScriptOrigin origin1 = ModuleOrigin(NewString("cycle_one.js"), isolate()); ScriptCompiler::Source source1(source_text1, origin1); cycle_one_module = ScriptCompiler::CompileModule(isolate(), &source1).ToLocalChecked(); Local source_text2 = NewString( "import './cycle_one.js'\n" "import './async_leaf.js'"); ScriptOrigin origin2 = ModuleOrigin(NewString("cycle_two.js"), isolate()); ScriptCompiler::Source source2(source_text2, origin2); cycle_two_module = ScriptCompiler::CompileModule(isolate(), &source2).ToLocalChecked(); CHECK(cycle_one_module ->InstantiateModule(context(), ResolveCallbackForIsGraphAsyncTopLevelAwait) .FromJust()); CHECK(cycle_one_module->IsGraphAsync()); CHECK(cycle_two_module->IsGraphAsync()); } } } // anonymous namespace