0ceee9ad28
Adds support for parsing top level await to V8, as well as many tests. This is the final cl in the series to add support for top level await to v8. Spec is here: https://tc39.es/proposal-top-level-await/#sec-execute-async-module Bug: v8:9344 Change-Id: Ie8f17ad8c7c60d1f6996d134ae154416cc1f31e3 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1703878 Reviewed-by: Georg Neis <neis@chromium.org> Reviewed-by: Adam Klein <adamk@chromium.org> Commit-Queue: Joshua Litt <joshualitt@chromium.org> Cr-Commit-Position: refs/heads/master@{#63946}
890 lines
33 KiB
C++
890 lines
33 KiB
C++
// 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<v8::Value> resource_name, Isolate* isolate) {
|
|
ScriptOrigin origin(resource_name, Local<v8::Integer>(), Local<v8::Integer>(),
|
|
Local<v8::Boolean>(), Local<v8::Integer>(),
|
|
Local<v8::Value>(), Local<v8::Boolean>(),
|
|
Local<v8::Boolean>(), True(isolate));
|
|
return origin;
|
|
}
|
|
|
|
static Local<Module> dep1;
|
|
static Local<Module> dep2;
|
|
MaybeLocal<Module> ResolveCallback(Local<Context> context,
|
|
Local<String> specifier,
|
|
Local<Module> 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<Module>();
|
|
}
|
|
}
|
|
|
|
TEST(ModuleInstantiationFailures1) {
|
|
Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
LocalContext env;
|
|
v8::TryCatch try_catch(isolate);
|
|
|
|
Local<Module> module;
|
|
{
|
|
Local<String> 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<String> 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<String> 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<Module> root;
|
|
{
|
|
Local<String> 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<String> 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<String> 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<String> 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<String> 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<Module> CompileSpecifierAsModuleResolveCallback(
|
|
Local<Context> context, Local<String> specifier, Local<Module> 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<String> 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> 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<Value> result = module->Evaluate(env.local());
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
if (i::FLAG_harmony_top_level_await) {
|
|
Local<Promise> promise = Local<Promise>::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<String> 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> 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<Value> result_1;
|
|
{
|
|
v8::TryCatch inner_try_catch(isolate);
|
|
result_1 = module->Evaluate(env.local());
|
|
CHECK_EQ(Module::kErrored, module->GetStatus());
|
|
Local<Value> 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<Value> result_2;
|
|
{
|
|
v8::TryCatch inner_try_catch(isolate);
|
|
result_2 = module->Evaluate(env.local());
|
|
CHECK_EQ(Module::kErrored, module->GetStatus());
|
|
Local<Value> 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> promise_1 =
|
|
Local<Promise>::Cast(result_1.ToLocalChecked());
|
|
Local<Promise> promise_2 =
|
|
Local<Promise>::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<Module> failure_module;
|
|
static Local<Module> dependent_module;
|
|
MaybeLocal<Module> ResolveCallbackForModuleEvaluationError2(
|
|
Local<Context> context, Local<String> specifier, Local<Module> 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<String> 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<Value> result_1;
|
|
{
|
|
v8::TryCatch inner_try_catch(isolate);
|
|
result_1 = failure_module->Evaluate(env.local());
|
|
CHECK_EQ(Module::kErrored, failure_module->GetStatus());
|
|
Local<Value> 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<String> 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<Value> result_2;
|
|
{
|
|
v8::TryCatch inner_try_catch(isolate);
|
|
result_2 = dependent_module->Evaluate(env.local());
|
|
CHECK_EQ(Module::kErrored, dependent_module->GetStatus());
|
|
Local<Value> 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> promise_1 =
|
|
Local<Promise>::Cast(result_1.ToLocalChecked());
|
|
Local<Promise> promise_2 =
|
|
Local<Promise>::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<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Value> result_1 = module->Evaluate(env.local()).ToLocalChecked();
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
Local<Value> result_2 = module->Evaluate(env.local()).ToLocalChecked();
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
|
|
if (i::FLAG_harmony_top_level_await) {
|
|
Local<Promise> promise = Local<Promise>::Cast(result_1);
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
|
|
// Second evaluation should return the same promise.
|
|
Local<Promise> promise_too = Local<Promise>::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<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Value> result_1 = module->Evaluate(env.local()).ToLocalChecked();
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
|
|
Local<Value> result_2 = module->Evaluate(env.local()).ToLocalChecked();
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
if (i::FLAG_harmony_top_level_await) {
|
|
Local<Promise> promise = Local<Promise>::Cast(result_1);
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
|
|
// Second Evaluation should return the same promise.
|
|
Local<Promise> promise_too = Local<Promise>::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<v8::Object> ReferenceError =
|
|
CompileRun("ReferenceError")->ToObject(env.local()).ToLocalChecked();
|
|
|
|
Local<String> 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> 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<Value> ns = module->GetModuleNamespace();
|
|
CHECK_EQ(Module::kInstantiated, module->GetStatus());
|
|
Local<v8::Object> nsobj = ns->ToObject(env.local()).ToLocalChecked();
|
|
CHECK_EQ(nsobj->CreationContext(), env.local());
|
|
|
|
// 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<v8::Function>()
|
|
->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<v8::Function>()
|
|
->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<v8::Function>()
|
|
->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<v8::Function>()
|
|
->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;
|
|
}
|
|
|
|
TEST(ModuleEvaluationTopLevelAwait) {
|
|
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
|
|
i::FLAG_harmony_top_level_await = true;
|
|
Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
LocalContext env;
|
|
v8::TryCatch try_catch(isolate);
|
|
const char* sources[] = {
|
|
"await 42",
|
|
"import 'await 42';",
|
|
"import '42'; import 'await 42';",
|
|
};
|
|
|
|
for (auto src : sources) {
|
|
Local<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Promise> promise =
|
|
Local<Promise>::Cast(module->Evaluate(env.local()).ToLocalChecked());
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
CHECK(promise->Result()->IsUndefined());
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
|
|
}
|
|
|
|
TEST(ModuleEvaluationTopLevelAwaitError) {
|
|
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
|
|
i::FLAG_harmony_top_level_await = true;
|
|
Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
LocalContext env;
|
|
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<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Promise> promise =
|
|
Local<Promise>::Cast(module->Evaluate(env.local()).ToLocalChecked());
|
|
CHECK_EQ(Module::kErrored, module->GetStatus());
|
|
CHECK_EQ(promise->State(), v8::Promise::kRejected);
|
|
CHECK(promise->Result()->StrictEquals(v8_str("boom")));
|
|
CHECK(module->GetException()->StrictEquals(v8_str("boom")));
|
|
|
|
// TODO(joshualitt) I am not sure, but this might not be supposed to throw
|
|
// because it is async.
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
|
|
}
|
|
|
|
namespace {
|
|
struct DynamicImportData {
|
|
DynamicImportData(Isolate* isolate_, Local<Promise::Resolver> resolver_,
|
|
Local<Context> context_, bool should_resolve_)
|
|
: isolate(isolate_), should_resolve(should_resolve_) {
|
|
resolver.Reset(isolate, resolver_);
|
|
context.Reset(isolate, context_);
|
|
}
|
|
|
|
Isolate* isolate;
|
|
v8::Global<Promise::Resolver> resolver;
|
|
v8::Global<Context> context;
|
|
bool should_resolve;
|
|
};
|
|
|
|
void DoHostImportModuleDynamically(void* import_data) {
|
|
std::unique_ptr<DynamicImportData> import_data_(
|
|
static_cast<DynamicImportData*>(import_data));
|
|
Isolate* isolate(import_data_->isolate);
|
|
HandleScope handle_scope(isolate);
|
|
|
|
Local<Promise::Resolver> resolver(import_data_->resolver.Get(isolate));
|
|
Local<Context> 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, v8_str("boom")).ToChecked();
|
|
}
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallbackResolve(
|
|
Local<Context> context, Local<v8::ScriptOrModule> referrer,
|
|
Local<String> specifier) {
|
|
Isolate* isolate = context->GetIsolate();
|
|
Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context).ToLocalChecked();
|
|
|
|
DynamicImportData* data =
|
|
new DynamicImportData(isolate, resolver, context, true);
|
|
isolate->EnqueueMicrotask(DoHostImportModuleDynamically, data);
|
|
return resolver->GetPromise();
|
|
}
|
|
|
|
v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallbackReject(
|
|
Local<Context> context, Local<v8::ScriptOrModule> referrer,
|
|
Local<String> specifier) {
|
|
Isolate* isolate = context->GetIsolate();
|
|
Local<v8::Promise::Resolver> resolver =
|
|
v8::Promise::Resolver::New(context).ToLocalChecked();
|
|
|
|
DynamicImportData* data =
|
|
new DynamicImportData(isolate, resolver, context, false);
|
|
isolate->EnqueueMicrotask(DoHostImportModuleDynamically, data);
|
|
return resolver->GetPromise();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(ModuleEvaluationTopLevelAwaitDynamicImport) {
|
|
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
|
|
bool previous_dynamic_import_flag_value = i::FLAG_harmony_dynamic_import;
|
|
i::FLAG_harmony_top_level_await = true;
|
|
i::FLAG_harmony_dynamic_import = true;
|
|
Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
isolate->SetHostImportModuleDynamicallyCallback(
|
|
HostImportModuleDynamicallyCallbackResolve);
|
|
LocalContext env;
|
|
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<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Promise> promise =
|
|
Local<Promise>::Cast(module->Evaluate(env.local()).ToLocalChecked());
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
CHECK_EQ(promise->State(), v8::Promise::kPending);
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
isolate->RunMicrotasks();
|
|
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
|
|
}
|
|
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
|
|
i::FLAG_harmony_dynamic_import = previous_dynamic_import_flag_value;
|
|
}
|
|
|
|
TEST(ModuleEvaluationTopLevelAwaitDynamicImportError) {
|
|
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
|
|
bool previous_dynamic_import_flag_value = i::FLAG_harmony_dynamic_import;
|
|
i::FLAG_harmony_top_level_await = true;
|
|
i::FLAG_harmony_dynamic_import = true;
|
|
Isolate* isolate = CcTest::isolate();
|
|
HandleScope scope(isolate);
|
|
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
isolate->SetHostImportModuleDynamicallyCallback(
|
|
HostImportModuleDynamicallyCallbackReject);
|
|
LocalContext env;
|
|
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<String> source_text = v8_str(src);
|
|
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
|
|
ScriptCompiler::Source source(source_text, origin);
|
|
Local<Module> 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<Promise> promise =
|
|
Local<Promise>::Cast(module->Evaluate(env.local()).ToLocalChecked());
|
|
CHECK_EQ(Module::kEvaluated, module->GetStatus());
|
|
CHECK_EQ(promise->State(), v8::Promise::kPending);
|
|
CHECK(!try_catch.HasCaught());
|
|
|
|
isolate->RunMicrotasks();
|
|
CHECK_EQ(Module::kErrored, module->GetStatus());
|
|
CHECK_EQ(promise->State(), v8::Promise::kRejected);
|
|
CHECK(promise->Result()->StrictEquals(v8_str("boom")));
|
|
CHECK(module->GetException()->StrictEquals(v8_str("boom")));
|
|
CHECK(!try_catch.HasCaught());
|
|
}
|
|
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
|
|
i::FLAG_harmony_dynamic_import = previous_dynamic_import_flag_value;
|
|
}
|
|
|
|
} // anonymous namespace
|