// 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::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) { 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()); CHECK(!module->Evaluate(env.local()).IsEmpty()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); ExpectInt32("Object.expando", 10); CHECK(!try_catch.HasCaught()); } TEST(ModuleEvaluationError) { 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()); { v8::TryCatch inner_try_catch(isolate); CHECK(module->Evaluate(env.local()).IsEmpty()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); ExpectInt32("Object.x", 1); } { v8::TryCatch inner_try_catch(isolate); CHECK(module->Evaluate(env.local()).IsEmpty()); CHECK(inner_try_catch.HasCaught()); CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom"))); CHECK_EQ(Module::kErrored, module->GetStatus()); Local exception = module->GetException(); CHECK(exception->StrictEquals(v8_str("boom"))); ExpectInt32("Object.x", 1); } CHECK(!try_catch.HasCaught()); } TEST(ModuleEvaluationCompletion1) { 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()); CHECK(module->Evaluate(env.local()).ToLocalChecked()->IsUndefined()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); CHECK(module->Evaluate(env.local()).ToLocalChecked()->IsUndefined()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); } CHECK(!try_catch.HasCaught()); } TEST(ModuleEvaluationCompletion2) { 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()); CHECK(module->Evaluate(env.local()) .ToLocalChecked() ->StrictEquals(v8_str("gaga"))); CHECK_EQ(Module::kEvaluated, module->GetStatus()); CHECK(module->Evaluate(env.local()).ToLocalChecked()->IsUndefined()); CHECK_EQ(Module::kEvaluated, module->GetStatus()); } CHECK(!try_catch.HasCaught()); } TEST(ModuleNamespace) { 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()); } } // anonymous namespace