// Copyright 2016 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 "src/flags/flags.h" #include "test/cctest/cctest.h" namespace { using v8::Context; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::Module; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::Value; ScriptOrigin ModuleOrigin(Local resource_name, Isolate* isolate) { ScriptOrigin origin(resource_name, Local(), Local(), Local(), Local(), Local(), Local(), Local(), True(isolate)); return origin; } static Local dep1; static Local dep2; MaybeLocal ResolveCallback(Local context, Local specifier, Local referrer) { Isolate* isolate = CcTest::isolate(); if (specifier->StrictEquals(v8_str("./dep1.js"))) { return dep1; } else if (specifier->StrictEquals(v8_str("./dep2.js"))) { return dep2; } else { isolate->ThrowException(v8_str("boom")); return MaybeLocal(); } } TEST(ModuleInstantiationFailures1) { Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); Local module; { Local source_text = v8_str( "import './foo.js';\n" "export {} from './bar.js';"); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK_EQ(2, module->GetModuleRequestsLength()); CHECK(v8_str("./foo.js")->StrictEquals(module->GetModuleRequest(0))); v8::Location loc = module->GetModuleRequestLocation(0); CHECK_EQ(0, loc.GetLineNumber()); CHECK_EQ(7, loc.GetColumnNumber()); CHECK(v8_str("./bar.js")->StrictEquals(module->GetModuleRequest(1))); loc = module->GetModuleRequestLocation(1); CHECK_EQ(1, loc.GetLineNumber()); CHECK_EQ(15, loc.GetColumnNumber()); } // Instantiation should fail. { v8::TryCatch inner_try_catch(isolate); CHECK(module->InstantiateModule(env.local(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); } // Start over again... { Local source_text = v8_str( "import './dep1.js';\n" "export {} from './bar.js';"); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } // dep1.js { Local source_text = v8_str(""); ScriptOrigin origin = ModuleOrigin(v8_str("dep1.js"), CcTest::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(env.local(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); } CHECK(!try_catch.HasCaught()); } TEST(ModuleInstantiationFailures2) { Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); // root1.js Local root; { Local source_text = v8_str("import './dep1.js'; import './dep2.js'"); ScriptOrigin origin = ModuleOrigin(v8_str("root1.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); root = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } // dep1.js { Local source_text = v8_str("export let x = 42"); ScriptOrigin origin = ModuleOrigin(v8_str("dep1.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); dep1 = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } // dep2.js { Local source_text = v8_str("import {foo} from './dep3.js'"); ScriptOrigin origin = ModuleOrigin(v8_str("dep2.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate); CHECK(root->InstantiateModule(env.local(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("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 = v8_str("import {foo} from './dep2.js'"); ScriptOrigin origin = ModuleOrigin(v8_str("dep2.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate); CHECK(root->InstantiateModule(env.local(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(!inner_try_catch.Exception()->StrictEquals(v8_str("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 = v8_str("import {foo} from './dep3.js'"); ScriptOrigin origin = ModuleOrigin(v8_str("dep2.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); dep2 = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); } { v8::TryCatch inner_try_catch(isolate); CHECK(root->InstantiateModule(env.local(), ResolveCallback).IsNothing()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("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 referrer) { ScriptOrigin origin = ModuleOrigin(v8_str("module.js"), CcTest::isolate()); ScriptCompiler::Source source(specifier, origin); return ScriptCompiler::CompileModule(CcTest::isolate(), &source) .ToLocalChecked(); } TEST(ModuleEvaluation) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); Local source_text = v8_str( "import 'Object.expando = 5';" "import 'Object.expando *= 2';"); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(env.local(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); MaybeLocal result = module->Evaluate(env.local()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); if (i::FLAG_harmony_top_level_await) { Local promise = Local::Cast(result.ToLocalChecked()); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); } else { CHECK(!result.IsEmpty()); ExpectInt32("Object.expando", 10); } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } TEST(ModuleEvaluationError1) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); Local source_text = v8_str("Object.x = (Object.x || 0) + 1; throw 'boom';"); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(env.local(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); MaybeLocal result_1; { v8::TryCatch inner_try_catch(isolate); result_1 = module->Evaluate(env.local()); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); ExpectInt32("Object.x", 1); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); } MaybeLocal result_2; { v8::TryCatch inner_try_catch(isolate); result_2 = module->Evaluate(env.local()); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); ExpectInt32("Object.x", 1); if (i::FLAG_harmony_top_level_await) { // With top level await we do not rethrow the exception. CHECK(!inner_try_catch.HasCaught()); } else { CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); } } if (i::FLAG_harmony_top_level_await) { // With top level await, errored evaluation returns a rejected promise // with the exception. Local promise_1 = Local::Cast(result_1.ToLocalChecked()); Local promise_2 = Local::Cast(result_2.ToLocalChecked()); CHECK_EQ(promise_1->State(), v8::Promise::kRejected); CHECK_EQ(promise_2->State(), v8::Promise::kRejected); CHECK_EQ(promise_1->Result(), module->GetException()); CHECK_EQ(promise_2->Result(), module->GetException()); } else { CHECK(result_1.IsEmpty() && result_2.IsEmpty()); } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } static Local failure_module; static Local dependent_module; MaybeLocal ResolveCallbackForModuleEvaluationError2( Local context, Local specifier, Local referrer) { if (specifier->StrictEquals(v8_str("./failure.js"))) { return failure_module; } else { CHECK(specifier->StrictEquals(v8_str("./dependent.js"))); return dependent_module; } } TEST(ModuleEvaluationError2) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); Local failure_text = v8_str("throw 'boom';"); ScriptOrigin failure_origin = ModuleOrigin(v8_str("failure.js"), CcTest::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(env.local(), ResolveCallbackForModuleEvaluationError2) .FromJust()); CHECK_EQ(Module::kInstantiated, failure_module->GetStatus()); MaybeLocal result_1; { v8::TryCatch inner_try_catch(isolate); result_1 = failure_module->Evaluate(env.local()); CHECK_EQ(Module::kErrored, failure_module->GetStatus()); Local exception = failure_module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); } Local dependent_text = v8_str("import './failure.js'; export const c = 123;"); ScriptOrigin dependent_origin = ModuleOrigin(v8_str("dependent.js"), CcTest::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(env.local(), ResolveCallbackForModuleEvaluationError2) .FromJust()); CHECK_EQ(Module::kInstantiated, dependent_module->GetStatus()); MaybeLocal result_2; { v8::TryCatch inner_try_catch(isolate); result_2 = dependent_module->Evaluate(env.local()); CHECK_EQ(Module::kErrored, dependent_module->GetStatus()); Local exception = dependent_module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); CHECK_EQ(exception, failure_module->GetException()); if (i::FLAG_harmony_top_level_await) { // With top level await we do not rethrow the exception. CHECK(!inner_try_catch.HasCaught()); } else { CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); } } if (i::FLAG_harmony_top_level_await) { // With top level await, errored evaluation returns a rejected promise // with the exception. Local promise_1 = Local::Cast(result_1.ToLocalChecked()); Local promise_2 = Local::Cast(result_2.ToLocalChecked()); CHECK_EQ(promise_1->State(), v8::Promise::kRejected); CHECK_EQ(promise_2->State(), v8::Promise::kRejected); CHECK_EQ(promise_1->Result(), failure_module->GetException()); CHECK_EQ(promise_2->Result(), failure_module->GetException()); } else { CHECK(result_1.IsEmpty() && result_2.IsEmpty()); } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } TEST(ModuleEvaluationCompletion1) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; 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 = v8_str(src); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(env.local(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); // Evaluate twice. Local result_1 = module->Evaluate(env.local()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local result_2 = module->Evaluate(env.local()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); if (i::FLAG_harmony_top_level_await) { Local promise = Local::Cast(result_1); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // Second evaluation should return the same promise. Local promise_too = Local::Cast(result_2); CHECK_EQ(promise, promise_too); CHECK_EQ(promise_too->State(), v8::Promise::kFulfilled); CHECK(promise_too->Result()->IsUndefined()); } else { CHECK(result_1->IsUndefined()); CHECK(result_2->IsUndefined()); } } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } TEST(ModuleEvaluationCompletion2) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; 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 = v8_str(src); ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(env.local(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local result_1 = module->Evaluate(env.local()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); Local result_2 = module->Evaluate(env.local()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); if (i::FLAG_harmony_top_level_await) { Local promise = Local::Cast(result_1); CHECK_EQ(promise->State(), v8::Promise::kFulfilled); CHECK(promise->Result()->IsUndefined()); // Second Evaluation should return the same promise. Local promise_too = Local::Cast(result_2); CHECK_EQ(promise, promise_too); CHECK_EQ(promise_too->State(), v8::Promise::kFulfilled); CHECK(promise_too->Result()->IsUndefined()); } else { CHECK(result_1->StrictEquals(v8_str("gaga"))); CHECK(result_2->IsUndefined()); } } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } TEST(ModuleNamespace) { bool prev_top_level_await = i::FLAG_harmony_top_level_await; for (auto top_level_await : {true, false}) { i::FLAG_harmony_top_level_await = top_level_await; Isolate* isolate = CcTest::isolate(); HandleScope scope(isolate); LocalContext env; v8::TryCatch try_catch(isolate); Local ReferenceError = CompileRun("ReferenceError")->ToObject(env.local()).ToLocalChecked(); Local source_text = v8_str( "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(v8_str("file.js"), CcTest::isolate()); ScriptCompiler::Source source(source_text, origin); Local module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); CHECK_EQ(Module::kUninstantiated, module->GetStatus()); CHECK(module ->InstantiateModule(env.local(), CompileSpecifierAsModuleResolveCallback) .FromJust()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local ns = module->GetModuleNamespace(); CHECK_EQ(Module::kInstantiated, module->GetStatus()); Local nsobj = ns->ToObject(env.local()).ToLocalChecked(); // a, b CHECK(nsobj->Get(env.local(), v8_str("a")).ToLocalChecked()->IsUndefined()); CHECK(nsobj->Get(env.local(), v8_str("b")).ToLocalChecked()->IsUndefined()); // geta { auto geta = nsobj->Get(env.local(), v8_str("geta")).ToLocalChecked(); auto a = geta.As() ->Call(env.local(), geta, 0, nullptr) .ToLocalChecked(); CHECK(a->IsUndefined()); } // getb { v8::TryCatch inner_try_catch(isolate); auto getb = nsobj->Get(env.local(), v8_str("getb")).ToLocalChecked(); CHECK(getb.As() ->Call(env.local(), getb, 0, nullptr) .IsEmpty()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception() ->InstanceOf(env.local(), ReferenceError) .FromJust()); } // radio { v8::TryCatch inner_try_catch(isolate); // https://bugs.chromium.org/p/v8/issues/detail?id=7235 // CHECK(nsobj->Get(env.local(), v8_str("radio")).IsEmpty()); CHECK(nsobj->Get(env.local(), v8_str("radio")) .ToLocalChecked() ->IsUndefined()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception() ->InstanceOf(env.local(), ReferenceError) .FromJust()); } // gaga { auto gaga = nsobj->Get(env.local(), v8_str("gaga")).ToLocalChecked(); CHECK(gaga->IsUndefined()); } CHECK(!try_catch.HasCaught()); CHECK_EQ(Module::kInstantiated, module->GetStatus()); module->Evaluate(env.local()).ToLocalChecked(); CHECK_EQ(Module::kEvaluated, module->GetStatus()); // geta { auto geta = nsobj->Get(env.local(), v8_str("geta")).ToLocalChecked(); auto a = geta.As() ->Call(env.local(), geta, 0, nullptr) .ToLocalChecked(); CHECK_EQ(1, a->Int32Value(env.local()).FromJust()); } // getb { auto getb = nsobj->Get(env.local(), v8_str("getb")).ToLocalChecked(); auto b = getb.As() ->Call(env.local(), getb, 0, nullptr) .ToLocalChecked(); CHECK_EQ(2, b->Int32Value(env.local()).FromJust()); } // radio { auto radio = nsobj->Get(env.local(), v8_str("radio")).ToLocalChecked(); CHECK_EQ(3, radio->Int32Value(env.local()).FromJust()); } // gaga { auto gaga = nsobj->Get(env.local(), v8_str("gaga")).ToLocalChecked(); CHECK_EQ(4, gaga->Int32Value(env.local()).FromJust()); } CHECK(!try_catch.HasCaught()); } i::FLAG_harmony_top_level_await = prev_top_level_await; } } // anonymous namespace