From f9fc1ea0ab200e2d61fd343b7fce6e208d8367a0 Mon Sep 17 00:00:00 2001 From: ZYSzys <17367077526@163.com> Date: Thu, 13 Dec 2018 10:33:23 +0800 Subject: [PATCH 001/209] src: use v8:: for consistency in util MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport-PR-URL: https://github.com/nodejs/node/pull/25005 PR-URL: #23934 Reviewed-By: Anna Henningsen Reviewed-By: Michaël Zasso Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Franziska Hinkelmann --- src/node_util.cc | 20 +++++++++++++------- src/util.cc | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/node_util.cc b/src/node_util.cc index 5adecf4d9753c3..4dc7ce1bbfd887 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -6,17 +6,23 @@ namespace util { using v8::ALL_PROPERTIES; using v8::Array; +using v8::Boolean; using v8::Context; using v8::FunctionCallbackInfo; +using v8::IndexFilter; using v8::Integer; +using v8::KeyCollectionMode; using v8::Local; +using v8::NewStringType; using v8::Object; using v8::ONLY_CONFIGURABLE; using v8::ONLY_ENUMERABLE; using v8::ONLY_WRITABLE; using v8::Private; using v8::Promise; +using v8::PropertyFilter; using v8::Proxy; +using v8::ReadOnly; using v8::SKIP_STRINGS; using v8::SKIP_SYMBOLS; using v8::String; @@ -35,13 +41,13 @@ static void GetOwnNonIndexProperties( Local properties; - v8::PropertyFilter filter = - static_cast(args[1].As()->Value()); + PropertyFilter filter = + static_cast(args[1].As()->Value()); if (!object->GetPropertyNames( - context, v8::KeyCollectionMode::kOwnOnly, + context, KeyCollectionMode::kOwnOnly, filter, - v8::IndexFilter::kSkipIndices) + IndexFilter::kSkipIndices) .ToLocal(&properties)) { return; } @@ -94,7 +100,7 @@ static void PreviewEntries(const FunctionCallbackInfo& args) { return args.GetReturnValue().Set(entries); Local ret = Array::New(env->isolate(), 2); ret->Set(env->context(), 0, entries).FromJust(); - ret->Set(env->context(), 1, v8::Boolean::New(env->isolate(), is_key_value)) + ret->Set(env->context(), 1, Boolean::New(env->isolate(), is_key_value)) .FromJust(); return args.GetReturnValue().Set(ret); } @@ -169,7 +175,7 @@ void SafeGetenv(const FunctionCallbackInfo& args) { args.GetReturnValue() .Set(String::NewFromUtf8( args.GetIsolate(), text.c_str(), - v8::NewStringType::kNormal).ToLocalChecked()); + NewStringType::kNormal).ToLocalChecked()); } void Initialize(Local target, @@ -191,7 +197,7 @@ void Initialize(Local target, env->context(), OneByteString(env->isolate(), "pushValToArrayMax"), Integer::NewFromUnsigned(env->isolate(), NODE_PUSH_VAL_TO_ARRAY_MAX), - v8::ReadOnly).FromJust(); + ReadOnly).FromJust(); #define V(name) \ target->Set(context, \ diff --git a/src/util.cc b/src/util.cc index a0f0b0bf89657d..9f0b8ebc9d7596 100644 --- a/src/util.cc +++ b/src/util.cc @@ -102,7 +102,7 @@ BufferValue::BufferValue(Isolate* isolate, Local value) { void LowMemoryNotification() { if (v8_initialized) { - auto isolate = v8::Isolate::GetCurrent(); + auto isolate = Isolate::GetCurrent(); if (isolate != nullptr) { isolate->LowMemoryNotification(); } @@ -134,7 +134,7 @@ std::set ParseCommaSeparatedSet(const std::string& in) { return out; } -void ThrowErrStringTooLong(v8::Isolate* isolate) { +void ThrowErrStringTooLong(Isolate* isolate) { isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); } From fa12b24c0c523c06043806755bef52674b622c72 Mon Sep 17 00:00:00 2001 From: Ali Ijaz Sheikh Date: Thu, 27 Dec 2018 15:41:08 -0800 Subject: [PATCH 002/209] deps: V8: backport 442977e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: Merged: [wasm] Fix dispatch table instance update This CL fixes a bug where the receiving instance was updated improperly in the dispatch table(s) of an imported table. BUG=chromium:875322 R=​mstarzinger@chromium.org CC=titzer@chromium.org Change-Id: Iff24953a1fb6a8ab794e12a7a976d544b56fc3c2 Originally-reviewed-on: https://chromium-review.googlesource.com/1196886 No-Try: true No-Presubmit: true No-Treechecks: true Reviewed-on: https://chromium-review.googlesource.com/1212922 Reviewed-by: Michael Starzinger Commit-Queue: Clemens Hammacher Cr-Commit-Position: refs/branch-heads/6.9@{#45} Cr-Branched-From: d7b61abe7b48928aed739f02bf7695732d359e7e-refs/heads/6.9.427@{#1} Cr-Branched-From: b7e108d6016bf6b7de3a34e6d61cb522f5193460-refs/heads/master@{#54504} Refs: https://github.com/v8/v8/commit/442977e1ae21e3df4e7cc5cc880f728dcc0c2711 PR-URL: https://github.com/nodejs/node/pull/25242 Reviewed-By: Michaël Zasso --- common.gypi | 2 +- deps/v8/src/wasm/module-compiler.cc | 15 +- deps/v8/test/mjsunit/wasm/import-table.js | 244 ++++++++++++++++++++++ 3 files changed, 252 insertions(+), 9 deletions(-) create mode 100644 deps/v8/test/mjsunit/wasm/import-table.js diff --git a/common.gypi b/common.gypi index 0a4ed881a5b925..f68c3e31ce9f92 100644 --- a/common.gypi +++ b/common.gypi @@ -33,7 +33,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.12', + 'v8_embedder_string': '-node.48', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/src/wasm/module-compiler.cc b/deps/v8/src/wasm/module-compiler.cc index 180c025a4965da..2b0eac818aa2fe 100644 --- a/deps/v8/src/wasm/module-compiler.cc +++ b/deps/v8/src/wasm/module-compiler.cc @@ -2653,20 +2653,20 @@ void InstanceBuilder::LoadTableSegments(Handle instance) { // Update the local dispatch table first. uint32_t sig_id = module_->signature_ids[function->sig_index]; - WasmInstanceObject* target_instance = *instance; + Handle target_instance = instance; Address call_target; const bool is_import = func_index < module_->num_imported_functions; if (is_import) { // For imported calls, take target instance and address from the // import table. ImportedFunctionEntry entry(instance, func_index); - target_instance = entry.instance(); + target_instance = handle(entry.instance(), isolate_); call_target = entry.target(); } else { call_target = native_module->GetCallTargetForFunction(func_index); } IndirectFunctionTableEntry(instance, table_index) - .set(sig_id, target_instance, call_target); + .set(sig_id, *target_instance, call_target); if (!table_instance.table_object.is_null()) { // Update the table object's other dispatch tables. @@ -2700,17 +2700,16 @@ void InstanceBuilder::LoadTableSegments(Handle instance) { } table_instance.js_wrappers->set(table_index, *js_wrappers_[func_index]); - // UpdateDispatchTables() should update this instance as well. + // UpdateDispatchTables() updates all other dispatch tables, since + // we have not yet added the dispatch table we are currently building. WasmTableObject::UpdateDispatchTables( isolate_, table_instance.table_object, table_index, function->sig, - instance, call_target); + target_instance, call_target); } } } - // TODO(titzer): we add the new dispatch table at the end to avoid - // redundant work and also because the new instance is not yet fully - // initialized. + // Add the new dispatch table at the end to avoid redundant lookups. if (!table_instance.table_object.is_null()) { // Add the new dispatch table to the WebAssembly.Table object. WasmTableObject::AddDispatchTable(isolate_, table_instance.table_object, diff --git a/deps/v8/test/mjsunit/wasm/import-table.js b/deps/v8/test/mjsunit/wasm/import-table.js new file mode 100644 index 00000000000000..bb8bf7807b5cf5 --- /dev/null +++ b/deps/v8/test/mjsunit/wasm/import-table.js @@ -0,0 +1,244 @@ +// Copyright 2018 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. + +// Flags: --expose-wasm + +load("test/mjsunit/wasm/wasm-constants.js"); +load("test/mjsunit/wasm/wasm-module-builder.js"); + +function addConstFunc(builder, val) { + return builder.addFunction("const" + val, kSig_i_v) + .addBody(wasmI32Const(val)).index; +} + +function addSigs(builder, pad) { + for (let i = 0; i < pad; i++) { + let params = []; + for (let j = 0; j < i; j++) params.push(kWasmF32); + builder.addType(makeSig(params, [])); + } + + return { i_v: builder.addType(kSig_i_v) }; +} + +let kTableSize = 50; + +(function TestAliasedImportedTable() { + print(arguments.callee.name); + + { + let builder = new WasmModuleBuilder(); + let signums = addSigs(builder, 1); + + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f15 = addConstFunc(builder, 15); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f17 = addConstFunc(builder, 17); + builder.addExport("f15", f15); + builder.addExport("f17", f17); + builder.addFunctionTableInit(15, false, [f15], true); + builder.addFunctionTableInit(1, false, [call.index], true); + + var mod1 = builder.toModule(); + } + + { + let builder = new WasmModuleBuilder(); + let signums = addSigs(builder, 5); // ensure different sigids + + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f15 = builder.addImport("m", "f15", kSig_i_v); + let f17 = builder.addImport("m", "f17", kSig_i_v); + let f21 = addConstFunc(builder, 21); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f26 = addConstFunc(builder, 26); + builder.addFunctionTableInit(17, false, [f17], true); + builder.addFunctionTableInit(21, false, [f21], true); + builder.addFunctionTableInit(26, false, [f26], true); + builder.addFunctionTableInit(5, false, [call.index], true); + + var mod2 = builder.toModule(); + } + + var table = new WebAssembly.Table({initial: kTableSize, + maximum: kTableSize, element: "anyfunc"}); + var i1 = new WebAssembly.Instance(mod1, {m: {table: table}}); + + var i2 = new WebAssembly.Instance(mod2, + {m: {table: table, f15: i1.exports.f15, f17: i1.exports.f17}}); + + for (i of [15, 17, 21, 26]) { + print(i); + assertEquals(i, i1.exports.call(i)); + assertEquals(i, i2.exports.call(i)); + } + for (i of [0, 1, 5, 16]) { + assertThrows(() => i1.exports.call(i)); + assertThrows(() => i2.exports.call(i)); + } +})(); + +function addConstFuncUsingGlobal(builder, val) { + let g = builder.addGlobal(kWasmI32, false); + g.init = val; + return builder.addFunction("global" + val, kSig_i_v) + .addBody([kExprGetGlobal, g.index]).index; +} + +(function TestAliasedImportedTableInstanceGlobals() { + print(arguments.callee.name); + + { + let builder = new WasmModuleBuilder(); + let signums = addSigs(builder, 1); + + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f14 = addConstFuncUsingGlobal(builder, 14); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f18 = addConstFuncUsingGlobal(builder, 18); + builder.addExport("f14", f14); + builder.addExport("f18", f18); + builder.addFunctionTableInit(14, false, [f14], true); + builder.addFunctionTableInit(1, false, [call.index], true); + + var mod1 = builder.toModule(); + } + + { + let builder = new WasmModuleBuilder(); + let signums = addSigs(builder, 3); // ensure different sigids + + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f14 = builder.addImport("m", "f14", kSig_i_v); + let f18 = builder.addImport("m", "f18", kSig_i_v); + let f22 = addConstFuncUsingGlobal(builder, 22); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f28 = addConstFuncUsingGlobal(builder, 28); + builder.addFunctionTableInit(18, false, [f18], true); + builder.addFunctionTableInit(22, false, [f22], true); + builder.addFunctionTableInit(28, false, [f28], true); + builder.addFunctionTableInit(5, false, [call.index], true); + + var mod2 = builder.toModule(); + } + + var table = new WebAssembly.Table({initial: kTableSize, + maximum: kTableSize, element: "anyfunc"}); + var i1 = new WebAssembly.Instance(mod1, {m: {table: table}}); + + var i2 = new WebAssembly.Instance(mod2, + {m: {table: table, f14: i1.exports.f14, f18: i1.exports.f18}}); + + for (i of [14, 18, 22, 28]) { + print(i); + assertEquals(i, i1.exports.call(i)); + assertEquals(i, i2.exports.call(i)); + } + for (i of [0, 1, 5, 16]) { + assertThrows(() => i1.exports.call(i)); + assertThrows(() => i2.exports.call(i)); + } +})(); + + +function addConstFuncUsingMemory(builder, val) { + var addr = builder.address; + builder.address += 8; + var bytes = [val & 0xff, (val>>8) & 0xff, (val>>16) & 0xff, (val>>24) & 0xff]; + builder.addDataSegment(addr, bytes); + return builder.addFunction("mem" + val, kSig_i_v) + .addBody([ + ...wasmI32Const(addr), + kExprI32LoadMem, 0, 0 + ]).index; +} + +(function TestAliasedImportedTableInstanceMemories() { + print(arguments.callee.name); + + { + let builder = new WasmModuleBuilder(); + builder.address = 8; + let signums = addSigs(builder, 1); + + builder.addMemory(1, 1, false); + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f13 = addConstFuncUsingMemory(builder, 13); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f19 = addConstFuncUsingMemory(builder, 19); + builder.addExport("f13", f13); + builder.addExport("f19", f19); + builder.addFunctionTableInit(13, false, [f13], true); + builder.addFunctionTableInit(1, false, [call.index], true); + + var mod1 = builder.toModule(); + } + + { + let builder = new WasmModuleBuilder(); + builder.address = 8; + let signums = addSigs(builder, 4); // ensure different sigids + + builder.addMemory(1, 1, false); + builder.addImportedTable("m", "table", kTableSize, kTableSize); + let f13 = builder.addImport("m", "f13", kSig_i_v); + let f19 = builder.addImport("m", "f19", kSig_i_v); + let f23 = addConstFuncUsingMemory(builder, 23); + let call = builder.addFunction("call", kSig_i_i) + .addBody([ + kExprGetLocal, 0, + kExprCallIndirect, signums.i_v, kTableZero + ]) + .exportAs("call"); + let f29 = addConstFuncUsingMemory(builder, 29); + builder.addFunctionTableInit(19, false, [f19], true); + builder.addFunctionTableInit(23, false, [f23], true); + builder.addFunctionTableInit(29, false, [f29], true); + builder.addFunctionTableInit(5, false, [call.index], true); + + var mod2 = builder.toModule(); + } + + var table = new WebAssembly.Table({initial: kTableSize, + maximum: kTableSize, element: "anyfunc"}); + var i1 = new WebAssembly.Instance(mod1, {m: {table: table}}); + + var i2 = new WebAssembly.Instance(mod2, + {m: {table: table, f13: i1.exports.f13, f19: i1.exports.f19}}); + + for (i of [13, 19, 23, 29]) { + print(i); + assertEquals(i, i1.exports.call(i)); + assertEquals(i, i2.exports.call(i)); + } + for (i of [0, 1, 5, 16]) { + assertThrows(() => i1.exports.call(i)); + assertThrows(() => i2.exports.call(i)); + } +})(); From 67239ab15661145a90914e6e5caf5d8b52c96be6 Mon Sep 17 00:00:00 2001 From: Alexey Kozyatinskiy Date: Thu, 13 Sep 2018 19:10:39 -0700 Subject: [PATCH 003/209] deps: cherry-pick d9fbfeb from upstream V8 Original commit message: inspector: return [[StableObjectId]] as internal property This property might be useful for fast '===' check. R=dgozman@chromium.org,yangguo@chromium.org Bug: none Cq-Include-Trybots: luci.chromium.try:linux_chromium_headless_rel;luci.chromium.try:linux_chromium_rel_ng;master.tryserver.blink:linux_trusty_blink_rel Change-Id: Iabc3555ce1ec2c14cf0ccd40b7d964ae144e7352 Reviewed-on: https://chromium-review.googlesource.com/1226411 Reviewed-by: Dmitry Gozman Reviewed-by: Yang Guo Reviewed-by: Jakob Gruber Commit-Queue: Aleksey Kozyatinskiy Cr-Commit-Position: refs/heads/master@{#56095} PR-URL: https://github.com/nodejs/node/pull/25330 --- common.gypi | 2 +- deps/v8/src/api.cc | 41 +++++++ deps/v8/src/api.h | 1 + deps/v8/src/bootstrapper.cc | 4 +- deps/v8/src/contexts.h | 1 + deps/v8/src/counters.h | 3 + deps/v8/src/debug/debug-interface.h | 14 +++ deps/v8/src/inspector/v8-debugger.cc | 26 +++++ deps/v8/src/inspector/v8-debugger.h | 6 + .../debugger/eval-scopes-expected.txt | 6 + ...kip-variables-with-empty-name-expected.txt | 6 + deps/v8/test/inspector/protocol-test.js | 2 + .../inspector/runtime/es6-module-expected.txt | 6 + .../runtime/get-properties-expected.txt | 20 ++++ .../get-properties-on-proxy-expected.txt | 12 ++ .../test/inspector/runtime/get-properties.js | 5 +- .../internal-properties-entries-expected.txt | 108 ++++++++++++++++++ .../runtime/internal-properties-expected.txt | 82 +++++++++++-- .../runtime/stable-object-id-expected.txt | 14 +++ .../inspector/runtime/stable-object-id.js | 77 +++++++++++++ .../runtime-remote-object-expected.txt | 18 +++ 21 files changed, 444 insertions(+), 10 deletions(-) create mode 100644 deps/v8/test/inspector/runtime/stable-object-id-expected.txt create mode 100644 deps/v8/test/inspector/runtime/stable-object-id.js diff --git a/common.gypi b/common.gypi index f68c3e31ce9f92..5e06df763c6942 100644 --- a/common.gypi +++ b/common.gypi @@ -33,7 +33,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.48', + 'v8_embedder_string': '-node.49', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/src/api.cc b/deps/v8/src/api.cc index f68759f33bb16e..cacd3995fc67b9 100644 --- a/deps/v8/src/api.cc +++ b/deps/v8/src/api.cc @@ -10001,6 +10001,47 @@ debug::TypeProfile::ScriptData debug::TypeProfile::GetScriptData( return ScriptData(i, type_profile_); } +v8::MaybeLocal debug::WeakMap::Get(v8::Local context, + v8::Local key) { + PREPARE_FOR_EXECUTION(context, WeakMap, Get, Value); + auto self = Utils::OpenHandle(this); + Local result; + i::Handle argv[] = {Utils::OpenHandle(*key)}; + has_pending_exception = + !ToLocal(i::Execution::Call(isolate, isolate->weakmap_get(), self, + arraysize(argv), argv), + &result); + RETURN_ON_FAILED_EXECUTION(Value); + RETURN_ESCAPED(result); +} + +v8::MaybeLocal debug::WeakMap::Set( + v8::Local context, v8::Local key, + v8::Local value) { + PREPARE_FOR_EXECUTION(context, WeakMap, Set, WeakMap); + auto self = Utils::OpenHandle(this); + i::Handle result; + i::Handle argv[] = {Utils::OpenHandle(*key), + Utils::OpenHandle(*value)}; + has_pending_exception = !i::Execution::Call(isolate, isolate->weakmap_set(), + self, arraysize(argv), argv) + .ToHandle(&result); + RETURN_ON_FAILED_EXECUTION(WeakMap); + RETURN_ESCAPED(Local::Cast(Utils::ToLocal(result))); +} + +Local debug::WeakMap::New(v8::Isolate* isolate) { + i::Isolate* i_isolate = reinterpret_cast(isolate); + LOG_API(i_isolate, WeakMap, New); + ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate); + i::Handle obj = i_isolate->factory()->NewJSWeakMap(); + return ToApiHandle(obj); +} + +debug::WeakMap* debug::WeakMap::Cast(v8::Value* value) { + return static_cast(value); +} + const char* CpuProfileNode::GetFunctionNameStr() const { const i::ProfileNode* node = reinterpret_cast(this); return node->entry()->name(); diff --git a/deps/v8/src/api.h b/deps/v8/src/api.h index 342ab855ac011c..8df1a1539b8a44 100644 --- a/deps/v8/src/api.h +++ b/deps/v8/src/api.h @@ -124,6 +124,7 @@ class RegisteredExtension { V(Proxy, JSProxy) \ V(debug::GeneratorObject, JSGeneratorObject) \ V(debug::Script, Script) \ + V(debug::WeakMap, JSWeakMap) \ V(Promise, JSPromise) \ V(Primitive, Object) \ V(PrimitiveArray, FixedArray) \ diff --git a/deps/v8/src/bootstrapper.cc b/deps/v8/src/bootstrapper.cc index 548ef5109a9a68..f0cd00a62c2491 100644 --- a/deps/v8/src/bootstrapper.cc +++ b/deps/v8/src/bootstrapper.cc @@ -3250,7 +3250,9 @@ void Genesis::InitializeGlobal(Handle global_object, SimpleInstallFunction(prototype, "delete", Builtins::kWeakMapPrototypeDelete, 1, true); - SimpleInstallFunction(prototype, "get", Builtins::kWeakMapGet, 1, true); + Handle weakmap_get = SimpleInstallFunction(prototype, "get", + Builtins::kWeakMapGet, 1, true); + native_context()->set_weakmap_get(*weakmap_get); SimpleInstallFunction(prototype, "has", Builtins::kWeakMapHas, 1, true); Handle weakmap_set = SimpleInstallFunction( prototype, "set", Builtins::kWeakMapPrototypeSet, 2, true); diff --git a/deps/v8/src/contexts.h b/deps/v8/src/contexts.h index b66bb94a4b41ec..af75fa7243264e 100644 --- a/deps/v8/src/contexts.h +++ b/deps/v8/src/contexts.h @@ -108,6 +108,7 @@ enum ContextLookupFlags { V(WASM_RUNTIME_ERROR_FUNCTION_INDEX, JSFunction, \ wasm_runtime_error_function) \ V(WEAKMAP_SET_INDEX, JSFunction, weakmap_set) \ + V(WEAKMAP_GET_INDEX, JSFunction, weakmap_get) \ V(WEAKSET_ADD_INDEX, JSFunction, weakset_add) #define NATIVE_CONTEXT_FIELDS(V) \ diff --git a/deps/v8/src/counters.h b/deps/v8/src/counters.h index 255c8db7c6bb54..e873252fa1bcaa 100644 --- a/deps/v8/src/counters.h +++ b/deps/v8/src/counters.h @@ -730,6 +730,9 @@ class RuntimeCallTimer final { V(Map_Has) \ V(Map_New) \ V(Map_Set) \ + V(WeakMap_Get) \ + V(WeakMap_Set) \ + V(WeakMap_New) \ V(Message_GetEndColumn) \ V(Message_GetLineNumber) \ V(Message_GetSourceLine) \ diff --git a/deps/v8/src/debug/debug-interface.h b/deps/v8/src/debug/debug-interface.h index 2210b4e87f1bc6..169b73a9ea8116 100644 --- a/deps/v8/src/debug/debug-interface.h +++ b/deps/v8/src/debug/debug-interface.h @@ -515,6 +515,20 @@ bool SetFunctionBreakpoint(v8::Local function, v8::Platform* GetCurrentPlatform(); +class WeakMap : public v8::Object { + public: + V8_WARN_UNUSED_RESULT v8::MaybeLocal Get( + v8::Local context, v8::Local key); + V8_WARN_UNUSED_RESULT v8::MaybeLocal Set( + v8::Local context, v8::Local key, + v8::Local value); + + static Local New(v8::Isolate* isolate); + V8_INLINE static WeakMap* Cast(Value* obj); + + private: + WeakMap(); +}; } // namespace debug } // namespace v8 diff --git a/deps/v8/src/inspector/v8-debugger.cc b/deps/v8/src/inspector/v8-debugger.cc index 6e4e6a1752e3ab..4fec951390c2f0 100644 --- a/deps/v8/src/inspector/v8-debugger.cc +++ b/deps/v8/src/inspector/v8-debugger.cc @@ -703,11 +703,37 @@ v8::MaybeLocal V8Debugger::generatorScopes( return getTargetScopes(context, generator, GENERATOR); } +v8::MaybeLocal V8Debugger::stableObjectId( + v8::Local context, v8::Local value) { + DCHECK(value->IsObject()); + if (m_stableObjectId.IsEmpty()) { + m_stableObjectId.Reset(m_isolate, v8::debug::WeakMap::New(m_isolate)); + } + v8::Local stableObjectId = + m_stableObjectId.Get(m_isolate); + v8::Local idValue; + if (!stableObjectId->Get(context, value).ToLocal(&idValue) || + !idValue->IsUint32()) { + idValue = v8::Integer::NewFromUnsigned(m_isolate, ++m_lastStableObjectId); + stableObjectId->Set(context, value, idValue).ToLocalChecked(); + } + return idValue.As(); +} + v8::MaybeLocal V8Debugger::internalProperties( v8::Local context, v8::Local value) { v8::Local properties; if (!v8::debug::GetInternalProperties(m_isolate, value).ToLocal(&properties)) return v8::MaybeLocal(); + if (value->IsObject()) { + v8::Local id; + if (stableObjectId(context, value).ToLocal(&id)) { + createDataProperty( + context, properties, properties->Length(), + toV8StringInternalized(m_isolate, "[[StableObjectId]]")); + createDataProperty(context, properties, properties->Length(), id); + } + } if (value->IsFunction()) { v8::Local function = value.As(); v8::Local location; diff --git a/deps/v8/src/inspector/v8-debugger.h b/deps/v8/src/inspector/v8-debugger.h index 351e5b66adbf8b..dd8c7f66192183 100644 --- a/deps/v8/src/inspector/v8-debugger.h +++ b/deps/v8/src/inspector/v8-debugger.h @@ -185,6 +185,9 @@ class V8Debugger : public v8::debug::DebugDelegate { int currentContextGroupId(); + v8::MaybeLocal stableObjectId(v8::Local, + v8::Local); + v8::Isolate* m_isolate; V8InspectorImpl* m_inspector; int m_enableCount; @@ -241,6 +244,9 @@ class V8Debugger : public v8::debug::DebugDelegate { std::unique_ptr m_terminateExecutionCallback; + uint32_t m_lastStableObjectId = 0; + v8::Global m_stableObjectId; + WasmTranslation m_wasmTranslation; DISALLOW_COPY_AND_ASSIGN(V8Debugger); diff --git a/deps/v8/test/inspector/debugger/eval-scopes-expected.txt b/deps/v8/test/inspector/debugger/eval-scopes-expected.txt index 71d6618c8e8387..4c93498c68f134 100644 --- a/deps/v8/test/inspector/debugger/eval-scopes-expected.txt +++ b/deps/v8/test/inspector/debugger/eval-scopes-expected.txt @@ -2,6 +2,12 @@ Tests that variables introduced in eval scopes are accessible { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true diff --git a/deps/v8/test/inspector/debugger/scope-skip-variables-with-empty-name-expected.txt b/deps/v8/test/inspector/debugger/scope-skip-variables-with-empty-name-expected.txt index 626f9787c37993..6fbe355eff54c1 100644 --- a/deps/v8/test/inspector/debugger/scope-skip-variables-with-empty-name-expected.txt +++ b/deps/v8/test/inspector/debugger/scope-skip-variables-with-empty-name-expected.txt @@ -2,6 +2,12 @@ Tests that scopes do not report variables with empty names { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true diff --git a/deps/v8/test/inspector/protocol-test.js b/deps/v8/test/inspector/protocol-test.js index 9b7ba0aafe6f37..7163dd68785a00 100644 --- a/deps/v8/test/inspector/protocol-test.js +++ b/deps/v8/test/inspector/protocol-test.js @@ -45,6 +45,8 @@ InspectorTest.logMessage = function(originalMessage) { var objects = [ message ]; while (objects.length) { var object = objects.shift(); + if (object && object.name === '[[StableObjectId]]') + object.value = ''; for (var key in object) { if (nonStableFields.has(key)) object[key] = `<${key}>`; diff --git a/deps/v8/test/inspector/runtime/es6-module-expected.txt b/deps/v8/test/inspector/runtime/es6-module-expected.txt index 25ba52e0341c2e..051ef6ceae183c 100644 --- a/deps/v8/test/inspector/runtime/es6-module-expected.txt +++ b/deps/v8/test/inspector/runtime/es6-module-expected.txt @@ -128,6 +128,12 @@ console.log(239) { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true diff --git a/deps/v8/test/inspector/runtime/get-properties-expected.txt b/deps/v8/test/inspector/runtime/get-properties-expected.txt index 8b48e65c3b7060..5707ffc5afd2de 100644 --- a/deps/v8/test/inspector/runtime/get-properties-expected.txt +++ b/deps/v8/test/inspector/runtime/get-properties-expected.txt @@ -5,6 +5,7 @@ Running test: testObject5 foo own string cat Internal properties [[PrimitiveValue]] number 5 + [[StableObjectId]]: Running test: testNotOwn __defineGetter__ inherited function undefined @@ -23,6 +24,8 @@ Running test: testNotOwn toLocaleString inherited function undefined toString inherited function undefined valueOf inherited function undefined +Internal properties + [[StableObjectId]]: Running test: testAccessorsOnly b own no value, getter, setter @@ -34,6 +37,8 @@ Running test: testArray 2 own string blue __proto__ own object undefined length own number 3 +Internal properties + [[StableObjectId]]: Running test: testBound __proto__ own function undefined @@ -42,14 +47,19 @@ Running test: testBound Internal properties [[BoundArgs]] object undefined [[BoundThis]] object undefined + [[StableObjectId]]: [[TargetFunction]] function undefined Running test: testObjectThrowsLength __proto__ own object undefined length own no value, getter +Internal properties + [[StableObjectId]]: Running test: testTypedArrayWithoutLength __proto__ own object undefined +Internal properties + [[StableObjectId]]: Running test: testArrayBuffer [[Int8Array]] @@ -62,6 +72,8 @@ Running test: testArrayBuffer 6 own number 1 7 own number 1 __proto__ own object undefined +Internal properties + [[StableObjectId]]: [[Uint8Array]] 0 own number 1 1 own number 1 @@ -72,18 +84,26 @@ Running test: testArrayBuffer 6 own number 1 7 own number 1 __proto__ own object undefined +Internal properties + [[StableObjectId]]: [[Int16Array]] 0 own number 257 1 own number 257 2 own number 257 3 own number 257 __proto__ own object undefined +Internal properties + [[StableObjectId]]: [[Int32Array]] 0 own number 16843009 1 own number 16843009 __proto__ own object undefined +Internal properties + [[StableObjectId]]: Running test: testArrayBufferWithBrokenUintCtor [[Int8Array]] own object undefined [[Uint8Array]] own object undefined __proto__ own object undefined +Internal properties + [[StableObjectId]]: diff --git a/deps/v8/test/inspector/runtime/get-properties-on-proxy-expected.txt b/deps/v8/test/inspector/runtime/get-properties-on-proxy-expected.txt index a0437f4af6d644..efde782ae32b78 100644 --- a/deps/v8/test/inspector/runtime/get-properties-on-proxy-expected.txt +++ b/deps/v8/test/inspector/runtime/get-properties-on-proxy-expected.txt @@ -54,6 +54,10 @@ Testing regular Proxy value : false } } + [3] : { + name : [[StableObjectId]] + value : + } ] result : [ ] @@ -114,6 +118,10 @@ Testing revocable Proxy value : false } } + [3] : { + name : [[StableObjectId]] + value : + } ] result : [ ] @@ -166,6 +174,10 @@ Testing revocable Proxy value : true } } + [3] : { + name : [[StableObjectId]] + value : + } ] result : [ ] diff --git a/deps/v8/test/inspector/runtime/get-properties.js b/deps/v8/test/inspector/runtime/get-properties.js index d2b2c754a311e2..0386fdea6d87c5 100644 --- a/deps/v8/test/inspector/runtime/get-properties.js +++ b/deps/v8/test/inspector/runtime/get-properties.js @@ -94,7 +94,10 @@ async function logGetPropertiesResult(objectId, flags = { ownProperties: true }) for (var i = 0; i < internalPropertyArray.length; i++) { var p = internalPropertyArray[i]; var v = p.value; - InspectorTest.log(" " + p.name + " " + v.type + " " + v.value); + if (p.name !== '[[StableObjectId]]') + InspectorTest.log(" " + p.name + " " + v.type + " " + v.value); + else + InspectorTest.log(" [[StableObjectId]]: "); } } diff --git a/deps/v8/test/inspector/runtime/internal-properties-entries-expected.txt b/deps/v8/test/inspector/runtime/internal-properties-entries-expected.txt index d395067efe72b1..1d09e8dc1ebe77 100644 --- a/deps/v8/test/inspector/runtime/internal-properties-entries-expected.txt +++ b/deps/v8/test/inspector/runtime/internal-properties-entries-expected.txt @@ -15,6 +15,12 @@ expression: new Map([[1,2],[3,4]]) { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -65,6 +71,12 @@ expression: new Map() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -97,6 +109,12 @@ expression: new Map([[1,2],[3,4]]).entries() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -151,6 +169,12 @@ expression: it = new Map([[1,2],[3,4]]).entries(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -190,6 +214,12 @@ expression: it = new Map([[1,2],[3,4]]).keys(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -229,6 +259,12 @@ expression: it = new Map([[1,2],[3,4]]).values(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -265,6 +301,12 @@ expression: it = new Map([[1,2],[3,4]]).entries(); it.next(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -295,6 +337,12 @@ expression: new Set([1,2]) { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -345,6 +393,12 @@ expression: new Set() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -375,6 +429,12 @@ expression: new Set([1,2]).values() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -428,6 +488,12 @@ expression: it = new Set([1,2]).values(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -467,6 +533,12 @@ expression: it = new Set([1,2]).keys(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -506,6 +578,12 @@ expression: it = new Set([1,2]).entries(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -542,6 +620,12 @@ expression: it = new Set([1,2]).values(); it.next(); it.next(); it { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -566,6 +650,12 @@ expression: new WeakMap() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -594,6 +684,12 @@ expression: new WeakMap([[{ a: 2 }, 42]]) { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -632,6 +728,12 @@ expression: new WeakSet() { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : false @@ -659,6 +761,12 @@ expression: new WeakSet([{a:2}]) { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true diff --git a/deps/v8/test/inspector/runtime/internal-properties-expected.txt b/deps/v8/test/inspector/runtime/internal-properties-expected.txt index a2e38ab013f472..c114696eb81352 100644 --- a/deps/v8/test/inspector/runtime/internal-properties-expected.txt +++ b/deps/v8/test/inspector/runtime/internal-properties-expected.txt @@ -7,6 +7,10 @@ expression: (function* foo() { yield 1 }) result : { internalProperties : [ [0] : { + name : [[StableObjectId]] + value : + } + [1] : { name : [[FunctionLocation]] value : { description : Object @@ -19,14 +23,14 @@ expression: (function* foo() { yield 1 }) } } } - [1] : { + [2] : { name : [[IsGenerator]] value : { type : boolean value : true } } - [2] : { + [3] : { name : [[Scopes]] value : { className : Array @@ -47,6 +51,10 @@ expression: (function foo() {}) result : { internalProperties : [ [0] : { + name : [[StableObjectId]] + value : + } + [1] : { name : [[FunctionLocation]] value : { description : Object @@ -59,7 +67,7 @@ expression: (function foo() {}) } } } - [1] : { + [2] : { name : [[Scopes]] value : { className : Array @@ -87,6 +95,10 @@ expression: new Number(239) value : 239 } } + [1] : { + name : [[StableObjectId]] + value : + } ] } } @@ -102,6 +114,10 @@ expression: new Boolean(false) value : false } } + [1] : { + name : [[StableObjectId]] + value : + } ] } } @@ -117,6 +133,10 @@ expression: new String('abc') value : abc } } + [1] : { + name : [[StableObjectId]] + value : + } ] } } @@ -133,6 +153,10 @@ expression: Object(Symbol(42)) type : symbol } } + [1] : { + name : [[StableObjectId]] + value : + } ] } } @@ -149,6 +173,10 @@ expression: Object(BigInt(2)) unserializableValue : 2n } } + [1] : { + name : [[StableObjectId]] + value : + } ] } } @@ -174,6 +202,10 @@ expression: Promise.resolve(42) value : 42 } } + [2] : { + name : [[StableObjectId]] + value : + } ] } } @@ -195,6 +227,10 @@ expression: new Promise(() => undefined) type : undefined } } + [2] : { + name : [[StableObjectId]] + value : + } ] } } @@ -231,6 +267,10 @@ expression: gen1 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -243,7 +283,7 @@ expression: gen1 } } } - [4] : { + [5] : { name : [[Scopes]] value : { className : Array @@ -287,6 +327,10 @@ expression: gen1.next();gen1 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -299,7 +343,7 @@ expression: gen1.next();gen1 } } } - [4] : { + [5] : { name : [[Scopes]] value : { className : Array @@ -343,6 +387,10 @@ expression: gen1.next();gen1 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -391,6 +439,10 @@ expression: gen2 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -403,7 +455,7 @@ expression: gen2 } } } - [4] : { + [5] : { name : [[Scopes]] value : { className : Array @@ -447,6 +499,10 @@ expression: gen2.next();gen2 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -459,7 +515,7 @@ expression: gen2.next();gen2 } } } - [4] : { + [5] : { name : [[Scopes]] value : { className : Array @@ -503,6 +559,10 @@ expression: gen2.next();gen2 } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[GeneratorLocation]] value : { description : Object @@ -548,6 +608,10 @@ expression: (new Map([[1,2]])).entries() } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[Entries]] value : { className : Array @@ -588,6 +652,10 @@ expression: (new Set([[1,2]])).entries() } } [3] : { + name : [[StableObjectId]] + value : + } + [4] : { name : [[Entries]] value : { className : Array diff --git a/deps/v8/test/inspector/runtime/stable-object-id-expected.txt b/deps/v8/test/inspector/runtime/stable-object-id-expected.txt new file mode 100644 index 00000000000000..937727e1089f59 --- /dev/null +++ b/deps/v8/test/inspector/runtime/stable-object-id-expected.txt @@ -0,0 +1,14 @@ +Checks that protocol returns the same RemoteObjectId for the same object + +Running test: testGlobal +Compare global evaluated twice: true + +Running test: testObject +Compare object evaluated twice: true + +Running test: testObjectInArray +Compare first and second element: true + +Running test: testObjectOnPause +Compare global and this: true +Compare a and a on pause: true diff --git a/deps/v8/test/inspector/runtime/stable-object-id.js b/deps/v8/test/inspector/runtime/stable-object-id.js new file mode 100644 index 00000000000000..9a65e67d7f6855 --- /dev/null +++ b/deps/v8/test/inspector/runtime/stable-object-id.js @@ -0,0 +1,77 @@ +// Copyright 2018 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. + +let {session, contextGroup, Protocol} = InspectorTest.start( + 'Checks that protocol returns the same RemoteObjectId for the same object'); + +InspectorTest.runAsyncTestSuite([ + async function testGlobal() { + const {result:{result:{objectId:firstId}}} = + await Protocol.Runtime.evaluate({expression: 'this'}); + const firstStableId = await stableObjectId(firstId); + const {result:{result:{objectId:secondId}}} = + await Protocol.Runtime.evaluate({expression: 'this'}); + const secondStableId = await stableObjectId(secondId); + InspectorTest.log( + `Compare global evaluated twice: ${firstStableId === secondStableId}`); + }, + + async function testObject() { + const {result:{result:{objectId:firstId}}} = + await Protocol.Runtime.evaluate({expression: 'this.a = {}, this.a'}); + const firstStableId = await stableObjectId(firstId); + const {result:{result:{objectId:secondId}}} = + await Protocol.Runtime.evaluate({expression: 'this.a'}); + const secondStableId = await stableObjectId(secondId); + InspectorTest.log( + `Compare object evaluated twice: ${firstStableId === secondStableId}`); + }, + + async function testObjectInArray() { + await Protocol.Runtime.evaluate({expression: 'this.b = [this.a, this.a]'}); + const {result:{result:{objectId:firstId}}} = + await Protocol.Runtime.evaluate({expression: 'this.b[0]'}); + const firstStableId = await stableObjectId(firstId); + const {result:{result:{objectId:secondId}}} = + await Protocol.Runtime.evaluate({expression: 'this.b[1]'}); + const secondStableId = await stableObjectId(secondId); + InspectorTest.log( + `Compare first and second element: ${firstStableId === secondStableId}`); + }, + + async function testObjectOnPause() { + const {result:{result:{objectId:globalId}}} = + await Protocol.Runtime.evaluate({expression: 'this'}); + const globalStableId = await stableObjectId(globalId); + const {result:{result:{objectId:aId}}} = + await Protocol.Runtime.evaluate({expression: 'this.a'}); + const aStableId = await stableObjectId(aId); + await Protocol.Debugger.enable(); + Protocol.Runtime.evaluate({expression: 'debugger'}); + const {params:{callFrames:[topFrame]}} = + await Protocol.Debugger.oncePaused(); + const topFrameThisStableId = await stableObjectId(topFrame.this.objectId); + InspectorTest.log( + `Compare global and this: ${globalStableId === topFrameThisStableId}`); + + const {result:{result: props}} = await Protocol.Runtime.getProperties({ + objectId: topFrame.scopeChain[0].object.objectId + }); + const {value:{objectId: aIdOnPause}} = props.find(prop => prop.name === 'a'); + const aStableIdOnPause = await stableObjectId(aIdOnPause); + InspectorTest.log(`Compare a and a on pause: ${ + aStableId === aStableIdOnPause}`); + } +]); + +async function stableObjectId(objectId) { + const {result:{ + internalProperties: props + }} = await Protocol.Runtime.getProperties({ + objectId, + ownProperties: true, + generatePreview: false + }); + return props.find(prop => prop.name === '[[StableObjectId]]').value.value; +} diff --git a/deps/v8/test/inspector/sessions/runtime-remote-object-expected.txt b/deps/v8/test/inspector/sessions/runtime-remote-object-expected.txt index a8d0ec0c207895..7c6e69e05d69be 100644 --- a/deps/v8/test/inspector/sessions/runtime-remote-object-expected.txt +++ b/deps/v8/test/inspector/sessions/runtime-remote-object-expected.txt @@ -5,6 +5,12 @@ Retrieving properties in 2 { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -38,6 +44,12 @@ Retrieving properties in 1 { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true @@ -72,6 +84,12 @@ Retrieving properties in 1 { id : result : { + internalProperties : [ + [0] : { + name : [[StableObjectId]] + value : + } + ] result : [ [0] : { configurable : true From 0d1500d9e2306bb66a6ceb7fe66333350b6d3bbc Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 17 Aug 2018 17:26:34 -0500 Subject: [PATCH 004/209] vm: add dynamic import support Backport-PR-URL: https://github.com/nodejs/node/pull/25421 PR-URL: https://github.com/nodejs/node/pull/22381 Reviewed-By: James M Snell Reviewed-By: Guy Bedford Reviewed-By: Tiancheng "Timothy" Gu --- doc/api/errors.md | 5 + doc/api/vm.md | 22 +- lib/internal/bootstrap/loaders.js | 2 + lib/internal/errors.js | 2 + lib/internal/modules/cjs/loader.js | 15 +- lib/internal/modules/esm/translators.js | 22 +- lib/internal/process/esm_loader.js | 49 +- lib/internal/vm/source_text_module.js | 47 +- lib/vm.js | 32 +- src/env-inl.h | 7 + src/env.h | 15 +- src/module_wrap.cc | 113 ++-- src/module_wrap.h | 16 +- src/node_contextify.cc | 618 +++++++++--------- src/node_contextify.h | 32 + test/es-module/test-esm-dynamic-import.js | 35 - .../parallel/test-vm-module-dynamic-import.js | 71 +- 17 files changed, 671 insertions(+), 432 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 72df6b27cb7b08..e050392fc65163 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1795,6 +1795,11 @@ The V8 `BreakIterator` API was used but the full ICU data set is not installed. While using the Performance Timing API (`perf_hooks`), no valid performance entry types were found. + +### ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING + +A dynamic import callback was not specified. + ### ERR_VM_MODULE_ALREADY_LINKED diff --git a/doc/api/vm.md b/doc/api/vm.md index d023d9c216903a..e8c4d1a6cbf2f2 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -167,10 +167,19 @@ const contextifiedSandbox = vm.createContext({ secret: 42 }); in stack traces produced by this `Module`. * `columnOffset` {integer} Specifies the column number offset that is displayed in stack traces produced by this `Module`. - * `initalizeImportMeta` {Function} Called during evaluation of this `Module` + * `initializeImportMeta` {Function} Called during evaluation of this `Module` to initialize the `import.meta`. This function has the signature `(meta, module)`, where `meta` is the `import.meta` object in the `Module`, and `module` is this `vm.SourceTextModule` object. + * `importModuleDynamically` {Function} Called during evaluation of this + module when `import()` is called. This function has the signature + `(specifier, module)` where `specifier` is the specifier passed to + `import()` and `module` is this `vm.SourceTextModule`. If this option is + not specified, calls to `import()` will reject with + [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a + [Module Namespace Object][], but returning a `vm.SourceTextModule` is + recommended in order to take advantage of error tracking, and to avoid + issues with namespaces that contain `then` function exports. Creates a new ES `Module` object. @@ -436,6 +445,15 @@ changes: The `cachedDataProduced` value will be set to either `true` or `false` depending on whether code cache data is produced successfully. This option is deprecated in favor of `script.createCachedData()`. + * `importModuleDynamically` {Function} Called during evaluation of this + module when `import()` is called. This function has the signature + `(specifier, module)` where `specifier` is the specifier passed to + `import()` and `module` is this `vm.SourceTextModule`. If this option is + not specified, calls to `import()` will reject with + [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a + [Module Namespace Object][], but returning a `vm.SourceTextModule` is + recommended in order to take advantage of error tracking, and to avoid + issues with namespaces that contain `then` function exports. Creating a new `vm.Script` object compiles `code` but does not run it. The compiled `vm.Script` can be run later multiple times. The `code` is not bound to @@ -977,6 +995,7 @@ This issue occurs because all contexts share the same microtask and nextTick queues. [`Error`]: errors.html#errors_class_error +[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING [`URL`]: url.html#url_class_url [`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval [`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options @@ -985,6 +1004,7 @@ queues. [`vm.createContext()`]: #vm_vm_createcontext_sandbox_options [`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options [`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options +[Module Namespace Object]: https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects [ECMAScript Module Loader]: esm.html#esm_ecmascript_modules [Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation [GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index e2cfdbf83d38ef..06aee591496440 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -115,6 +115,8 @@ }; } + // Create this WeakMap in js-land because V8 has no C++ API for WeakMap + internalBinding('module_wrap').callbackMap = new WeakMap(); const { ContextifyScript } = process.binding('contextify'); // Set up NativeModule diff --git a/lib/internal/errors.js b/lib/internal/errors.js index b418fa295d2dcc..71b4a87ca0d86f 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -920,6 +920,8 @@ E('ERR_V8BREAKITERATOR', // This should probably be a `TypeError`. E('ERR_VALID_PERFORMANCE_ENTRY_TYPE', 'At least one valid performance entry type is required', Error); +E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING', + 'A dynamic import callback was not specified.', TypeError); E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error); E('ERR_VM_MODULE_DIFFERENT_CONTEXT', 'Linked modules must use the same context', Error); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index fb3770b7299d4e..8e768bf5d07850 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -29,6 +29,7 @@ const assert = require('assert').ok; const fs = require('fs'); const internalFS = require('internal/fs/utils'); const path = require('path'); +const { URL } = require('url'); const { internalModuleReadJSON, internalModuleStat @@ -642,6 +643,13 @@ Module.prototype.require = function(id) { // (needed for setting breakpoint when called with --inspect-brk) var resolvedArgv; +function normalizeReferrerURL(referrer) { + if (typeof referrer === 'string' && path.isAbsolute(referrer)) { + return pathToFileURL(referrer).href; + } + return new URL(referrer).href; +} + // Run the file contents in the correct scope or sandbox. Expose // the correct helper variables (require, module, exports) to @@ -657,7 +665,12 @@ Module.prototype._compile = function(content, filename) { var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, - displayErrors: true + displayErrors: true, + importModuleDynamically: experimentalModules ? async (specifier) => { + if (asyncESM === undefined) lazyLoadESM(); + const loader = await asyncESM.loaderPromise; + return loader.import(specifier, normalizeReferrerURL(filename)); + } : undefined, }); var inspectorWrapper = null; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index df3c446cab7ce7..0c34283b8af9e0 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -1,7 +1,7 @@ 'use strict'; const { NativeModule } = require('internal/bootstrap/loaders'); -const { ModuleWrap } = internalBinding('module_wrap'); +const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); const { stripShebang, stripBOM @@ -15,6 +15,8 @@ const { _makeLong } = require('path'); const { SafeMap } = require('internal/safe_globals'); const { URL } = require('url'); const { debuglog, promisify } = require('util'); +const esmLoader = require('internal/process/esm_loader'); + const readFileAsync = promisify(fs.readFile); const readFileSync = fs.readFileSync; const StringReplace = Function.call.bind(String.prototype.replace); @@ -25,13 +27,27 @@ const debug = debuglog('esm'); const translators = new SafeMap(); module.exports = translators; +function initializeImportMeta(meta, { url }) { + meta.url = url; +} + +async function importModuleDynamically(specifier, { url }) { + const loader = await esmLoader.loaderPromise; + return loader.import(specifier, url); +} + // Strategy for loading a standard JavaScript module translators.set('esm', async (url) => { const source = `${await readFileAsync(new URL(url))}`; debug(`Translating StandardModule ${url}`); + const module = new ModuleWrap(stripShebang(source), url); + callbackMap.set(module, { + initializeImportMeta, + importModuleDynamically, + }); return { - module: new ModuleWrap(stripShebang(source), url), - reflect: undefined + module, + reflect: undefined, }; }); diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index be08a6364781ed..f81053a1c3c3ad 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -2,40 +2,42 @@ const { setImportModuleDynamicallyCallback, - setInitializeImportMetaObjectCallback + setInitializeImportMetaObjectCallback, + callbackMap, } = internalBinding('module_wrap'); const { pathToFileURL } = require('internal/url'); const Loader = require('internal/modules/esm/loader'); -const path = require('path'); -const { URL } = require('url'); const { - initImportMetaMap, - wrapToModuleMap + wrapToModuleMap, } = require('internal/vm/source_text_module'); +const { + ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, +} = require('internal/errors').codes; -function normalizeReferrerURL(referrer) { - if (typeof referrer === 'string' && path.isAbsolute(referrer)) { - return pathToFileURL(referrer).href; +function initializeImportMetaObject(wrap, meta) { + if (callbackMap.has(wrap)) { + const { initializeImportMeta } = callbackMap.get(wrap); + if (initializeImportMeta !== undefined) { + initializeImportMeta(meta, wrapToModuleMap.get(wrap) || wrap); + } } - return new URL(referrer).href; } -function initializeImportMetaObject(wrap, meta) { - const vmModule = wrapToModuleMap.get(wrap); - if (vmModule === undefined) { - // This ModuleWrap belongs to the Loader. - meta.url = wrap.url; - } else { - const initializeImportMeta = initImportMetaMap.get(vmModule); - if (initializeImportMeta !== undefined) { - // This ModuleWrap belongs to vm.SourceTextModule, - // initializer callback was provided. - initializeImportMeta(meta, vmModule); +async function importModuleDynamicallyCallback(wrap, specifier) { + if (callbackMap.has(wrap)) { + const { importModuleDynamically } = callbackMap.get(wrap); + if (importModuleDynamically !== undefined) { + return importModuleDynamically( + specifier, wrapToModuleMap.get(wrap) || wrap); } } + throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); } +setInitializeImportMetaObjectCallback(initializeImportMetaObject); +setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); + let loaderResolve; exports.loaderPromise = new Promise((resolve, reject) => { loaderResolve = resolve; @@ -44,8 +46,6 @@ exports.loaderPromise = new Promise((resolve, reject) => { exports.ESMLoader = undefined; exports.setup = function() { - setInitializeImportMetaObjectCallback(initializeImportMetaObject); - let ESMLoader = new Loader(); const loaderPromise = (async () => { const userLoader = require('internal/options').getOptionValue('--loader'); @@ -60,10 +60,5 @@ exports.setup = function() { })(); loaderResolve(loaderPromise); - setImportModuleDynamicallyCallback(async (referrer, specifier) => { - const loader = await loaderPromise; - return loader.import(specifier, normalizeReferrerURL(referrer)); - }); - exports.ESMLoader = ESMLoader; }; diff --git a/lib/internal/vm/source_text_module.js b/lib/internal/vm/source_text_module.js index 915289a0591373..1898831bf6f238 100644 --- a/lib/internal/vm/source_text_module.js +++ b/lib/internal/vm/source_text_module.js @@ -1,5 +1,6 @@ 'use strict'; +const { isModuleNamespaceObject } = require('util').types; const { URL } = require('internal/url'); const { isContext } = process.binding('contextify'); const { @@ -10,7 +11,7 @@ const { ERR_VM_MODULE_LINKING_ERRORED, ERR_VM_MODULE_NOT_LINKED, ERR_VM_MODULE_NOT_MODULE, - ERR_VM_MODULE_STATUS + ERR_VM_MODULE_STATUS, } = require('internal/errors').codes; const { getConstructorOf, @@ -21,6 +22,7 @@ const { SafePromise } = require('internal/safe_globals'); const { ModuleWrap, + callbackMap, kUninstantiated, kInstantiating, kInstantiated, @@ -43,8 +45,6 @@ const perContextModuleId = new WeakMap(); const wrapMap = new WeakMap(); const dependencyCacheMap = new WeakMap(); const linkingStatusMap = new WeakMap(); -// vm.SourceTextModule -> function -const initImportMetaMap = new WeakMap(); // ModuleWrap -> vm.SourceTextModule const wrapToModuleMap = new WeakMap(); const defaultModuleName = 'vm:module'; @@ -63,7 +63,8 @@ class SourceTextModule { context, lineOffset = 0, columnOffset = 0, - initializeImportMeta + initializeImportMeta, + importModuleDynamically, } = options; if (context !== undefined) { @@ -96,13 +97,16 @@ class SourceTextModule { validateInteger(lineOffset, 'options.lineOffset'); validateInteger(columnOffset, 'options.columnOffset'); - if (initializeImportMeta !== undefined) { - if (typeof initializeImportMeta === 'function') { - initImportMetaMap.set(this, initializeImportMeta); - } else { - throw new ERR_INVALID_ARG_TYPE( - 'options.initializeImportMeta', 'function', initializeImportMeta); - } + if (initializeImportMeta !== undefined && + typeof initializeImportMeta !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.initializeImportMeta', 'function', initializeImportMeta); + } + + if (importModuleDynamically !== undefined && + typeof importModuleDynamically !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.importModuleDynamically', 'function', importModuleDynamically); } const wrap = new ModuleWrap(src, url, context, lineOffset, columnOffset); @@ -110,6 +114,22 @@ class SourceTextModule { linkingStatusMap.set(this, 'unlinked'); wrapToModuleMap.set(wrap, this); + callbackMap.set(wrap, { + initializeImportMeta, + importModuleDynamically: importModuleDynamically ? async (...args) => { + const m = await importModuleDynamically(...args); + if (isModuleNamespaceObject(m)) { + return m; + } + if (!m || !wrapMap.has(m)) + throw new ERR_VM_MODULE_NOT_MODULE(); + const childLinkingStatus = linkingStatusMap.get(m); + if (childLinkingStatus === 'errored') + throw m.error; + return m.namespace; + } : undefined, + }); + Object.defineProperties(this, { url: { value: url, enumerable: true }, context: { value: context, enumerable: true }, @@ -255,6 +275,7 @@ function validateInteger(prop, propName) { module.exports = { SourceTextModule, - initImportMetaMap, - wrapToModuleMap + wrapToModuleMap, + wrapMap, + linkingStatusMap, }; diff --git a/lib/vm.js b/lib/vm.js index dbe48b7b15c194..c8c6f0f7e9473d 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -27,12 +27,13 @@ const { isContext: _isContext, compileFunction: _compileFunction } = process.binding('contextify'); - +const { callbackMap } = internalBinding('module_wrap'); const { ERR_INVALID_ARG_TYPE, - ERR_OUT_OF_RANGE + ERR_OUT_OF_RANGE, + ERR_VM_MODULE_NOT_MODULE, } = require('internal/errors').codes; -const { isUint8Array } = require('internal/util/types'); +const { isModuleNamespaceObject, isUint8Array } = require('util').types; const { validateUint32 } = require('internal/validators'); const kParsingContext = Symbol('script parsing context'); @@ -55,7 +56,8 @@ class Script extends ContextifyScript { columnOffset = 0, cachedData, produceCachedData = false, - [kParsingContext]: parsingContext + importModuleDynamically, + [kParsingContext]: parsingContext, } = options; if (typeof filename !== 'string') { @@ -86,6 +88,28 @@ class Script extends ContextifyScript { } catch (e) { throw e; /* node-do-not-add-exception-line */ } + + if (importModuleDynamically !== undefined) { + if (typeof importModuleDynamically !== 'function') { + throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically', + 'function', + importModuleDynamically); + } + const { wrapMap, linkingStatusMap } = + require('internal/vm/source_text_module'); + callbackMap.set(this, { importModuleDynamically: async (...args) => { + const m = await importModuleDynamically(...args); + if (isModuleNamespaceObject(m)) { + return m; + } + if (!m || !wrapMap.has(m)) + throw new ERR_VM_MODULE_NOT_MODULE(); + const childLinkingStatus = linkingStatusMap.get(m); + if (childLinkingStatus === 'errored') + throw m.error; + return m.namespace; + } }); + } } runInThisContext(options) { diff --git a/src/env-inl.h b/src/env-inl.h index b512412c0ff4f0..4af5add4c8f52c 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -441,6 +441,13 @@ Environment::should_abort_on_uncaught_toggle() { return should_abort_on_uncaught_toggle_; } +inline uint32_t Environment::get_next_module_id() { + return module_id_counter_++; +} +inline uint32_t Environment::get_next_script_id() { + return script_id_counter_++; +} + Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope( Environment* env) : env_(env) { diff --git a/src/env.h b/src/env.h index 1d2baf58b007ea..cd222d70fdf138 100644 --- a/src/env.h +++ b/src/env.h @@ -47,6 +47,10 @@ struct nghttp2_rcbuf; namespace node { +namespace contextify { +class ContextifyScript; +} + namespace fs { class FileHandleReadWrap; } @@ -671,7 +675,13 @@ class Environment { // List of id's that have been destroyed and need the destroy() cb called. inline std::vector* destroy_async_id_list(); - std::unordered_multimap module_map; + std::unordered_multimap hash_to_module_map; + std::unordered_map id_to_module_map; + std::unordered_map + id_to_script_map; + + inline uint32_t get_next_module_id(); + inline uint32_t get_next_script_id(); std::unordered_map package_json_cache; @@ -899,6 +909,9 @@ class Environment { std::shared_ptr options_; + uint32_t module_id_counter_ = 0; + uint32_t script_id_counter_ = 0; + AliasedBuffer should_abort_on_uncaught_toggle_; int should_not_abort_scope_counter_ = 0; diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 816c68d55d16ce..fe6a8e2639c7a7 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -33,7 +33,9 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Module; using v8::Nothing; +using v8::Number; using v8::Object; +using v8::PrimitiveArray; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; @@ -47,18 +49,22 @@ static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; ModuleWrap::ModuleWrap(Environment* env, Local object, Local module, - Local url) : BaseObject(env, object) { + Local url) : + BaseObject(env, object), + id_(env->get_next_module_id()) { module_.Reset(env->isolate(), module); url_.Reset(env->isolate(), url); + env->id_to_module_map.emplace(id_, this); } ModuleWrap::~ModuleWrap() { HandleScope scope(env()->isolate()); Local module = module_.Get(env()->isolate()); - auto range = env()->module_map.equal_range(module->GetIdentityHash()); + env()->id_to_module_map.erase(id_); + auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second == this) { - env()->module_map.erase(it); + env()->hash_to_module_map.erase(it); break; } } @@ -66,15 +72,21 @@ ModuleWrap::~ModuleWrap() { ModuleWrap* ModuleWrap::GetFromModule(Environment* env, Local module) { - ModuleWrap* ret = nullptr; - auto range = env->module_map.equal_range(module->GetIdentityHash()); + auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second->module_ == module) { - ret = it->second; - break; + return it->second; } } - return ret; + return nullptr; +} + +ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) { + auto module_wrap_it = env->id_to_module_map.find(id); + if (module_wrap_it == env->id_to_module_map.end()) { + return nullptr; + } + return module_wrap_it->second; } void ModuleWrap::New(const FunctionCallbackInfo& args) { @@ -126,6 +138,11 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { TryCatch try_catch(isolate); Local module; + Local host_defined_options = + PrimitiveArray::New(isolate, HostDefinedOptions::kLength); + host_defined_options->Set(isolate, HostDefinedOptions::kType, + Number::New(isolate, ScriptType::kModule)); + // compile { ScriptOrigin origin(url, @@ -136,7 +153,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { Local(), // source map URL False(isolate), // is opaque (?) False(isolate), // is WASM - True(isolate)); // is ES6 module + True(isolate), // is ES Module + host_defined_options); Context::Scope context_scope(context); ScriptCompiler::Source source(source_text, origin); if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) { @@ -157,7 +175,10 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { ModuleWrap* obj = new ModuleWrap(env, that, module, url); obj->context_.Reset(isolate, context); - env->module_map.emplace(module->GetIdentityHash(), obj); + env->hash_to_module_map.emplace(module->GetIdentityHash(), obj); + + host_defined_options->Set(isolate, HostDefinedOptions::kID, + Number::New(isolate, obj->id())); that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); args.GetReturnValue().Set(that); @@ -364,19 +385,14 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, Environment* env = Environment::GetCurrent(context); CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. Isolate* isolate = env->isolate(); - if (env->module_map.count(referrer->GetIdentityHash()) == 0) { - env->ThrowError("linking error, unknown module"); - return MaybeLocal(); - } ModuleWrap* dependent = GetFromModule(env, referrer); - if (dependent == nullptr) { env->ThrowError("linking error, null dep"); return MaybeLocal(); } - Utf8Value specifier_utf8(env->isolate(), specifier); + Utf8Value specifier_utf8(isolate, specifier); std::string specifier_std(*specifier_utf8, specifier_utf8.length()); if (dependent->resolve_cache_.count(specifier_std) != 1) { @@ -402,7 +418,7 @@ MaybeLocal ModuleWrap::ResolveCallback(Local context, ModuleWrap* module; ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal()); - return module->module_.Get(env->isolate()); + return module->module_.Get(isolate); } namespace { @@ -703,35 +719,56 @@ static MaybeLocal ImportModuleDynamically( CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. v8::EscapableHandleScope handle_scope(iso); - if (env->context() != context) { - auto maybe_resolver = Promise::Resolver::New(context); - Local resolver; - if (maybe_resolver.ToLocal(&resolver)) { - // TODO(jkrems): Turn into proper error object w/ code - Local error = v8::Exception::Error( - OneByteString(iso, "import() called outside of main context")); - if (resolver->Reject(context, error).IsJust()) { - return handle_scope.Escape(resolver.As()); - } - } - return MaybeLocal(); - } - Local import_callback = env->host_import_module_dynamically_callback(); + + Local options = referrer->GetHostDefinedOptions(); + if (options->Length() != HostDefinedOptions::kLength) { + Local resolver = + Promise::Resolver::New(context).ToLocalChecked(); + resolver + ->Reject(context, + v8::Exception::TypeError(FIXED_ONE_BYTE_STRING( + context->GetIsolate(), "Invalid host defined options"))) + .ToChecked(); + return handle_scope.Escape(resolver->GetPromise()); + } + + Local object; + + int type = options->Get(iso, HostDefinedOptions::kType) + .As() + ->Int32Value(context) + .ToChecked(); + uint32_t id = options->Get(iso, HostDefinedOptions::kID) + .As() + ->Uint32Value(context) + .ToChecked(); + if (type == ScriptType::kScript) { + contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second; + object = wrap->object(); + } else if (type == ScriptType::kModule) { + ModuleWrap* wrap = ModuleWrap::GetFromID(env, id); + object = wrap->object(); + } else { + UNREACHABLE(); + } + Local import_args[] = { - referrer->GetResourceName(), - Local(specifier) + object, + Local(specifier), }; - MaybeLocal maybe_result = import_callback->Call(context, - v8::Undefined(iso), - 2, - import_args); Local result; - if (maybe_result.ToLocal(&result)) { + if (import_callback->Call( + context, + v8::Undefined(iso), + arraysize(import_args), + import_args).ToLocal(&result)) { + CHECK(result->IsPromise()); return handle_scope.Escape(result.As()); } + return MaybeLocal(); } diff --git a/src/module_wrap.h b/src/module_wrap.h index d6593c48135d18..0e352c657580e4 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -17,6 +17,17 @@ enum PackageMainCheck : bool { IgnoreMain = false }; +enum ScriptType : int { + kScript, + kModule, +}; + +enum HostDefinedOptions : int { + kType = 8, + kID = 9, + kLength = 10, +}; + v8::Maybe Resolve(Environment* env, const std::string& specifier, const url::URL& base, @@ -38,6 +49,9 @@ class ModuleWrap : public BaseObject { tracker->TrackField("resolve_cache", resolve_cache_); } + inline uint32_t id() { return id_; } + static ModuleWrap* GetFromID(node::Environment*, uint32_t id); + SET_MEMORY_INFO_NAME(ModuleWrap) SET_SELF_SIZE(ModuleWrap) @@ -69,12 +83,12 @@ class ModuleWrap : public BaseObject { v8::Local referrer); static ModuleWrap* GetFromModule(node::Environment*, v8::Local); - Persistent module_; Persistent url_; bool linked_ = false; std::unordered_map> resolve_cache_; Persistent context_; + uint32_t id_; }; } // namespace loader diff --git a/src/node_contextify.cc b/src/node_contextify.cc index c99f6c7ea5aef5..a1903d9bd9b7e0 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -25,6 +25,7 @@ #include "node_contextify.h" #include "node_context_data.h" #include "node_errors.h" +#include "module_wrap.h" namespace node { namespace contextify { @@ -48,8 +49,10 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::NamedPropertyHandlerConfiguration; +using v8::Number; using v8::Object; using v8::ObjectTemplate; +using v8::PrimitiveArray; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; using v8::PropertyDescriptor; @@ -590,368 +593,381 @@ void ContextifyContext::IndexedPropertyDeleterCallback( args.GetReturnValue().Set(false); } -class ContextifyScript : public BaseObject { - private: - Persistent script_; - - public: - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(ContextifyScript) - SET_SELF_SIZE(ContextifyScript) - - static void Init(Environment* env, Local target) { - HandleScope scope(env->isolate()); - Local class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); - - Local script_tmpl = env->NewFunctionTemplate(New); - script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); - script_tmpl->SetClassName(class_name); - env->SetProtoMethod(script_tmpl, "createCachedData", CreateCachedData); - env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); - env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); - - target->Set(class_name, - script_tmpl->GetFunction(env->context()).ToLocalChecked()); - env->set_script_context_constructor_template(script_tmpl); - } - - - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); - Local context = env->context(); - - CHECK(args.IsConstructCall()); - - const int argc = args.Length(); - CHECK_GE(argc, 2); - - CHECK(args[0]->IsString()); - Local code = args[0].As(); - - CHECK(args[1]->IsString()); - Local filename = args[1].As(); - - Local line_offset; - Local column_offset; - Local cached_data_buf; - bool produce_cached_data = false; - Local parsing_context = context; - - if (argc > 2) { - // new ContextifyScript(code, filename, lineOffset, columnOffset, - // cachedData, produceCachedData, parsingContext) - CHECK_EQ(argc, 7); - CHECK(args[2]->IsNumber()); - line_offset = args[2].As(); - CHECK(args[3]->IsNumber()); - column_offset = args[3].As(); - if (!args[4]->IsUndefined()) { - CHECK(args[4]->IsUint8Array()); - cached_data_buf = args[4].As(); - } - CHECK(args[5]->IsBoolean()); - produce_cached_data = args[5]->IsTrue(); - if (!args[6]->IsUndefined()) { - CHECK(args[6]->IsObject()); - ContextifyContext* sandbox = - ContextifyContext::ContextFromContextifiedSandbox( - env, args[6].As()); - CHECK_NOT_NULL(sandbox); - parsing_context = sandbox->context(); - } - } else { - line_offset = Integer::New(isolate, 0); - column_offset = Integer::New(isolate, 0); - } - - ContextifyScript* contextify_script = - new ContextifyScript(env, args.This()); - - if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( - TRACING_CATEGORY_NODE2(vm, script)) != 0) { - Utf8Value fn(isolate, filename); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( - TRACING_CATEGORY_NODE2(vm, script), - "ContextifyScript::New", - contextify_script, - "filename", TRACE_STR_COPY(*fn)); - } +void ContextifyScript::Init(Environment* env, Local target) { + HandleScope scope(env->isolate()); + Local class_name = + FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); + + Local script_tmpl = env->NewFunctionTemplate(New); + script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + script_tmpl->SetClassName(class_name); + env->SetProtoMethod(script_tmpl, "createCachedData", CreateCachedData); + env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); + env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); + + target->Set(class_name, + script_tmpl->GetFunction(env->context()).ToLocalChecked()); + env->set_script_context_constructor_template(script_tmpl); +} - ScriptCompiler::CachedData* cached_data = nullptr; - if (!cached_data_buf.IsEmpty()) { - ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents(); - uint8_t* data = static_cast(contents.Data()); - cached_data = new ScriptCompiler::CachedData( - data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); - } +void ContextifyScript::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + Local context = env->context(); - ScriptOrigin origin(filename, line_offset, column_offset); - ScriptCompiler::Source source(code, origin, cached_data); - ScriptCompiler::CompileOptions compile_options = - ScriptCompiler::kNoCompileOptions; + CHECK(args.IsConstructCall()); - if (source.GetCachedData() != nullptr) - compile_options = ScriptCompiler::kConsumeCodeCache; + const int argc = args.Length(); + CHECK_GE(argc, 2); - TryCatch try_catch(isolate); - Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env); - Context::Scope scope(parsing_context); + CHECK(args[0]->IsString()); + Local code = args[0].As(); - MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript( - isolate, - &source, - compile_options); + CHECK(args[1]->IsString()); + Local filename = args[1].As(); - if (v8_script.IsEmpty()) { - DecorateErrorStack(env, try_catch); - no_abort_scope.Close(); - try_catch.ReThrow(); - TRACE_EVENT_NESTABLE_ASYNC_END0( - TRACING_CATEGORY_NODE2(vm, script), - "ContextifyScript::New", - contextify_script); - return; + Local line_offset; + Local column_offset; + Local cached_data_buf; + bool produce_cached_data = false; + Local parsing_context = context; + + if (argc > 2) { + // new ContextifyScript(code, filename, lineOffset, columnOffset, + // cachedData, produceCachedData, parsingContext) + CHECK_EQ(argc, 7); + CHECK(args[2]->IsNumber()); + line_offset = args[2].As(); + CHECK(args[3]->IsNumber()); + column_offset = args[3].As(); + if (!args[4]->IsUndefined()) { + CHECK(args[4]->IsUint8Array()); + cached_data_buf = args[4].As(); } - contextify_script->script_.Reset(isolate, v8_script.ToLocalChecked()); - - if (compile_options == ScriptCompiler::kConsumeCodeCache) { - args.This()->Set( - env->cached_data_rejected_string(), - Boolean::New(isolate, source.GetCachedData()->rejected)); - } else if (produce_cached_data) { - const ScriptCompiler::CachedData* cached_data = - ScriptCompiler::CreateCodeCache(v8_script.ToLocalChecked()); - bool cached_data_produced = cached_data != nullptr; - if (cached_data_produced) { - MaybeLocal buf = Buffer::Copy( - env, - reinterpret_cast(cached_data->data), - cached_data->length); - args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); - } - args.This()->Set( - env->cached_data_produced_string(), - Boolean::New(isolate, cached_data_produced)); + CHECK(args[5]->IsBoolean()); + produce_cached_data = args[5]->IsTrue(); + if (!args[6]->IsUndefined()) { + CHECK(args[6]->IsObject()); + ContextifyContext* sandbox = + ContextifyContext::ContextFromContextifiedSandbox( + env, args[6].As()); + CHECK_NOT_NULL(sandbox); + parsing_context = sandbox->context(); } - TRACE_EVENT_NESTABLE_ASYNC_END0( + } else { + line_offset = Integer::New(isolate, 0); + column_offset = Integer::New(isolate, 0); + } + + ContextifyScript* contextify_script = + new ContextifyScript(env, args.This()); + + if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( + TRACING_CATEGORY_NODE2(vm, script)) != 0) { + Utf8Value fn(isolate, filename); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( TRACING_CATEGORY_NODE2(vm, script), "ContextifyScript::New", - contextify_script); + contextify_script, + "filename", TRACE_STR_COPY(*fn)); } - - static bool InstanceOf(Environment* env, const Local& value) { - return !value.IsEmpty() && - env->script_context_constructor_template()->HasInstance(value); + ScriptCompiler::CachedData* cached_data = nullptr; + if (!cached_data_buf.IsEmpty()) { + ArrayBuffer::Contents contents = cached_data_buf->Buffer()->GetContents(); + uint8_t* data = static_cast(contents.Data()); + cached_data = new ScriptCompiler::CachedData( + data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); } + Local host_defined_options = + PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); + host_defined_options->Set(isolate, loader::HostDefinedOptions::kType, + Number::New(isolate, loader::ScriptType::kScript)); + host_defined_options->Set(isolate, loader::HostDefinedOptions::kID, + Number::New(isolate, contextify_script->id())); + + ScriptOrigin origin(filename, + line_offset, // line offset + column_offset, // column offset + False(isolate), // is cross origin + Local(), // script id + Local(), // source map URL + False(isolate), // is opaque (?) + False(isolate), // is WASM + False(isolate), // is ES Module + host_defined_options); + ScriptCompiler::Source source(code, origin, cached_data); + ScriptCompiler::CompileOptions compile_options = + ScriptCompiler::kNoCompileOptions; - static void CreateCachedData(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - ContextifyScript* wrapped_script; - ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); - Local unbound_script = - PersistentToLocal(env->isolate(), wrapped_script->script_); - std::unique_ptr cached_data( - ScriptCompiler::CreateCodeCache(unbound_script)); - if (!cached_data) { - args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked()); - } else { + if (source.GetCachedData() != nullptr) + compile_options = ScriptCompiler::kConsumeCodeCache; + + TryCatch try_catch(isolate); + Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env); + Context::Scope scope(parsing_context); + + MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript( + isolate, + &source, + compile_options); + + if (v8_script.IsEmpty()) { + DecorateErrorStack(env, try_catch); + no_abort_scope.Close(); + try_catch.ReThrow(); + TRACE_EVENT_NESTABLE_ASYNC_END0( + TRACING_CATEGORY_NODE2(vm, script), + "ContextifyScript::New", + contextify_script); + return; + } + contextify_script->script_.Reset(isolate, v8_script.ToLocalChecked()); + + if (compile_options == ScriptCompiler::kConsumeCodeCache) { + args.This()->Set( + env->cached_data_rejected_string(), + Boolean::New(isolate, source.GetCachedData()->rejected)); + } else if (produce_cached_data) { + const ScriptCompiler::CachedData* cached_data = + ScriptCompiler::CreateCodeCache(v8_script.ToLocalChecked()); + bool cached_data_produced = cached_data != nullptr; + if (cached_data_produced) { MaybeLocal buf = Buffer::Copy( env, reinterpret_cast(cached_data->data), cached_data->length); - args.GetReturnValue().Set(buf.ToLocalChecked()); + args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); } + args.This()->Set( + env->cached_data_produced_string(), + Boolean::New(isolate, cached_data_produced)); } + TRACE_EVENT_NESTABLE_ASYNC_END0( + TRACING_CATEGORY_NODE2(vm, script), + "ContextifyScript::New", + contextify_script); +} +bool ContextifyScript::InstanceOf(Environment* env, + const Local& value) { + return !value.IsEmpty() && + env->script_context_constructor_template()->HasInstance(value); +} - static void RunInThisContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); +void ContextifyScript::CreateCachedData( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + ContextifyScript* wrapped_script; + ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); + Local unbound_script = + PersistentToLocal(env->isolate(), wrapped_script->script_); + std::unique_ptr cached_data( + ScriptCompiler::CreateCodeCache(unbound_script)); + if (!cached_data) { + args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked()); + } else { + MaybeLocal buf = Buffer::Copy( + env, + reinterpret_cast(cached_data->data), + cached_data->length); + args.GetReturnValue().Set(buf.ToLocalChecked()); + } +} - ContextifyScript* wrapped_script; - ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); +void ContextifyScript::RunInThisContext( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( - TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script); + ContextifyScript* wrapped_script; + ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); - CHECK_EQ(args.Length(), 3); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( + TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script); - CHECK(args[0]->IsNumber()); - int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); + CHECK_EQ(args.Length(), 3); - CHECK(args[1]->IsBoolean()); - bool display_errors = args[1]->IsTrue(); + CHECK(args[0]->IsNumber()); + int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); - CHECK(args[2]->IsBoolean()); - bool break_on_sigint = args[2]->IsTrue(); + CHECK(args[1]->IsBoolean()); + bool display_errors = args[1]->IsTrue(); - // Do the eval within this context - EvalMachine(env, timeout, display_errors, break_on_sigint, args); + CHECK(args[2]->IsBoolean()); + bool break_on_sigint = args[2]->IsTrue(); - TRACE_EVENT_NESTABLE_ASYNC_END0( - TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script); - } + // Do the eval within this context + EvalMachine(env, timeout, display_errors, break_on_sigint, args); - static void RunInContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + TRACE_EVENT_NESTABLE_ASYNC_END0( + TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script); +} - ContextifyScript* wrapped_script; - ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); +void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - CHECK_EQ(args.Length(), 4); + ContextifyScript* wrapped_script; + ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder()); - CHECK(args[0]->IsObject()); - Local sandbox = args[0].As(); - // Get the context from the sandbox - ContextifyContext* contextify_context = - ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); - CHECK_NOT_NULL(contextify_context); + CHECK_EQ(args.Length(), 4); - if (contextify_context->context().IsEmpty()) - return; + CHECK(args[0]->IsObject()); + Local sandbox = args[0].As(); + // Get the context from the sandbox + ContextifyContext* contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); + CHECK_NOT_NULL(contextify_context); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( - TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script); + if (contextify_context->context().IsEmpty()) + return; - CHECK(args[1]->IsNumber()); - int64_t timeout = args[1]->IntegerValue(env->context()).FromJust(); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( + TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script); - CHECK(args[2]->IsBoolean()); - bool display_errors = args[2]->IsTrue(); + CHECK(args[1]->IsNumber()); + int64_t timeout = args[1]->IntegerValue(env->context()).FromJust(); - CHECK(args[3]->IsBoolean()); - bool break_on_sigint = args[3]->IsTrue(); + CHECK(args[2]->IsBoolean()); + bool display_errors = args[2]->IsTrue(); - // Do the eval within the context - Context::Scope context_scope(contextify_context->context()); - EvalMachine(contextify_context->env(), - timeout, - display_errors, - break_on_sigint, - args); + CHECK(args[3]->IsBoolean()); + bool break_on_sigint = args[3]->IsTrue(); + + // Do the eval within the context + Context::Scope context_scope(contextify_context->context()); + EvalMachine(contextify_context->env(), + timeout, + display_errors, + break_on_sigint, + args); + + TRACE_EVENT_NESTABLE_ASYNC_END0( + TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script); +} - TRACE_EVENT_NESTABLE_ASYNC_END0( - TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script); - } +void ContextifyScript::DecorateErrorStack( + Environment* env, const TryCatch& try_catch) { + Local exception = try_catch.Exception(); - static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) { - Local exception = try_catch.Exception(); + if (!exception->IsObject()) + return; - if (!exception->IsObject()) - return; + Local err_obj = exception.As(); - Local err_obj = exception.As(); + if (IsExceptionDecorated(env, err_obj)) + return; - if (IsExceptionDecorated(env, err_obj)) - return; + AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); + Local stack = err_obj->Get(env->stack_string()); + MaybeLocal maybe_value = + err_obj->GetPrivate( + env->context(), + env->arrow_message_private_symbol()); - AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR); - Local stack = err_obj->Get(env->stack_string()); - MaybeLocal maybe_value = - err_obj->GetPrivate( - env->context(), - env->arrow_message_private_symbol()); + Local arrow; + if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { + return; + } - Local arrow; - if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) { - return; - } + if (stack.IsEmpty() || !stack->IsString()) { + return; + } - if (stack.IsEmpty() || !stack->IsString()) { - return; - } + Local decorated_stack = String::Concat( + env->isolate(), + String::Concat(env->isolate(), + arrow.As(), + FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), + stack.As()); + err_obj->Set(env->stack_string(), decorated_stack); + err_obj->SetPrivate( + env->context(), + env->decorated_private_symbol(), + True(env->isolate())); +} - Local decorated_stack = String::Concat( - env->isolate(), - String::Concat(env->isolate(), - arrow.As(), - FIXED_ONE_BYTE_STRING(env->isolate(), "\n")), - stack.As()); - err_obj->Set(env->stack_string(), decorated_stack); - err_obj->SetPrivate( - env->context(), - env->decorated_private_symbol(), - True(env->isolate())); +bool ContextifyScript::EvalMachine(Environment* env, + const int64_t timeout, + const bool display_errors, + const bool break_on_sigint, + const FunctionCallbackInfo& args) { + if (!env->can_call_into_js()) + return false; + if (!ContextifyScript::InstanceOf(env, args.Holder())) { + env->ThrowTypeError( + "Script methods can only be called on script instances."); + return false; + } + TryCatch try_catch(env->isolate()); + ContextifyScript* wrapped_script; + ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder(), false); + Local unbound_script = + PersistentToLocal(env->isolate(), wrapped_script->script_); + Local