| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| #include "node_contextify.h" |
| |
| #include "base_object-inl.h" |
| #include "cppgc/allocation.h" |
| #include "memory_tracker-inl.h" |
| #include "module_wrap.h" |
| #include "node_context_data.h" |
| #include "node_errors.h" |
| #include "node_external_reference.h" |
| #include "node_internals.h" |
| #include "node_process.h" |
| #include "node_sea.h" |
| #include "node_snapshot_builder.h" |
| #include "node_url.h" |
| #include "node_watchdog.h" |
| #include "util-inl.h" |
| |
| namespace node { |
| namespace contextify { |
| |
| using errors::TryCatchScope; |
| |
| using v8::Array; |
| using v8::ArrayBufferView; |
| using v8::Boolean; |
| using v8::Context; |
| using v8::DictionaryTemplate; |
| using v8::EscapableHandleScope; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::HandleScope; |
| using v8::IndexedPropertyHandlerConfiguration; |
| using v8::IndexFilter; |
| using v8::Int32; |
| using v8::Integer; |
| using v8::Intercepted; |
| using v8::Isolate; |
| using v8::JustVoid; |
| using v8::KeyCollectionMode; |
| using v8::Local; |
| using v8::LocalVector; |
| using v8::Maybe; |
| using v8::MaybeLocal; |
| using v8::MeasureMemoryExecution; |
| using v8::MeasureMemoryMode; |
| using v8::Message; |
| using v8::MicrotaskQueue; |
| using v8::MicrotasksPolicy; |
| using v8::Name; |
| using v8::NamedPropertyHandlerConfiguration; |
| using v8::Nothing; |
| using v8::Object; |
| using v8::ObjectTemplate; |
| using v8::PrimitiveArray; |
| using v8::Promise; |
| using v8::PropertyAttribute; |
| using v8::PropertyCallbackInfo; |
| using v8::PropertyDescriptor; |
| using v8::PropertyFilter; |
| using v8::PropertyHandlerFlags; |
| using v8::Script; |
| using v8::ScriptCompiler; |
| using v8::ScriptOrigin; |
| using v8::String; |
| using v8::Symbol; |
| using v8::Uint32; |
| using v8::UnboundScript; |
| using v8::Value; |
| |
| // The vm module executes code in a sandboxed environment with a different |
| // global object than the rest of the code. This is achieved by applying |
| // every call that changes or queries a property on the global `this` in the |
| // sandboxed code, to the sandbox object. |
| // |
| // The implementation uses V8's interceptors for methods like `set`, `get`, |
| // `delete`, `defineProperty`, and for any query of the property attributes. |
| // Property handlers with interceptors are set on the object template for |
| // the sandboxed code. Handlers for both named properties and for indexed |
| // properties are used. Their functionality is almost identical, the indexed |
| // interceptors mostly just call the named interceptors. |
| // |
| // For every `get` of a global property in the sandboxed context, the |
| // interceptor callback checks the sandbox object for the property. |
| // If the property is defined on the sandbox, that result is returned to |
| // the original call instead of finishing the query on the global object. |
| // |
| // For every `set` of a global property, the interceptor callback defines or |
| // changes the property both on the sandbox and the global proxy. |
| |
| namespace { |
| |
| // Convert an int to a V8 Name (String or Symbol). |
| MaybeLocal<String> Uint32ToName(Local<Context> context, uint32_t index) { |
| return Uint32::New(Isolate::GetCurrent(), index)->ToString(context); |
| } |
| |
| } // anonymous namespace |
| |
| ContextifyContext* ContextifyContext::New(Environment* env, |
| Local<Object> sandbox_obj, |
| ContextOptions* options) { |
| Local<ObjectTemplate> object_template; |
| HandleScope scope(env->isolate()); |
| CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla); |
| if (!sandbox_obj.IsEmpty()) { |
| // Do not use the template with interceptors for vanilla contexts. |
| object_template = env->contextify_global_template(); |
| DCHECK(!object_template.IsEmpty()); |
| } |
| |
| const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); |
| |
| MicrotaskQueue* queue = |
| options->own_microtask_queue |
| ? options->own_microtask_queue.get() |
| : env->isolate()->GetCurrentContext()->GetMicrotaskQueue(); |
| |
| Local<Context> v8_context; |
| if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue) |
| .ToLocal(&v8_context))) { |
| // Allocation failure, maximum call stack size reached, termination, etc. |
| return {}; |
| } |
| return New(v8_context, env, sandbox_obj, options); |
| } |
| |
| void ContextifyContext::Trace(cppgc::Visitor* visitor) const { |
| CppgcMixin::Trace(visitor); |
| visitor->Trace(context_); |
| } |
| |
| ContextifyContext::ContextifyContext(Environment* env, |
| Local<Object> wrapper, |
| Local<Context> v8_context, |
| ContextOptions* options) |
| : microtask_queue_(options->own_microtask_queue |
| ? options->own_microtask_queue.release() |
| : nullptr) { |
| CppgcMixin::Wrap(this, env, wrapper); |
| |
| context_.Reset(env->isolate(), v8_context); |
| // This should only be done after the initial initializations of the context |
| // global object is finished. |
| DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData( |
| ContextEmbedderIndex::kContextifyContext, |
| EmbedderDataTag::kPerContextData)); |
| v8_context->SetAlignedPointerInEmbedderData( |
| ContextEmbedderIndex::kContextifyContext, |
| this, |
| EmbedderDataTag::kPerContextData); |
| } |
| |
| void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { |
| DCHECK(isolate_data->contextify_wrapper_template().IsEmpty()); |
| Local<FunctionTemplate> global_func_template = |
| FunctionTemplate::New(isolate_data->isolate()); |
| Local<ObjectTemplate> global_object_template = |
| global_func_template->InstanceTemplate(); |
| |
| NamedPropertyHandlerConfiguration config( |
| PropertyGetterCallback, |
| PropertySetterCallback, |
| PropertyQueryCallback, |
| PropertyDeleterCallback, |
| PropertyEnumeratorCallback, |
| PropertyDefinerCallback, |
| PropertyDescriptorCallback, |
| {}, |
| PropertyHandlerFlags::kHasNoSideEffect); |
| |
| IndexedPropertyHandlerConfiguration indexed_config( |
| IndexedPropertyGetterCallback, |
| IndexedPropertySetterCallback, |
| IndexedPropertyQueryCallback, |
| IndexedPropertyDeleterCallback, |
| IndexedPropertyEnumeratorCallback, |
| IndexedPropertyDefinerCallback, |
| IndexedPropertyDescriptorCallback, |
| {}, |
| PropertyHandlerFlags::kHasNoSideEffect); |
| |
| global_object_template->SetHandler(config); |
| global_object_template->SetHandler(indexed_config); |
| isolate_data->set_contextify_global_template(global_object_template); |
| |
| Local<FunctionTemplate> wrapper_func_template = |
| BaseObject::MakeLazilyInitializedJSTemplate(isolate_data); |
| Local<ObjectTemplate> wrapper_object_template = |
| wrapper_func_template->InstanceTemplate(); |
| isolate_data->set_contextify_wrapper_template(wrapper_object_template); |
| } |
| |
| MaybeLocal<Context> ContextifyContext::CreateV8Context( |
| Isolate* isolate, |
| Local<ObjectTemplate> object_template, |
| const SnapshotData* snapshot_data, |
| MicrotaskQueue* queue) { |
| EscapableHandleScope scope(isolate); |
| |
| Local<Context> ctx; |
| if (object_template.IsEmpty() || snapshot_data == nullptr) { |
| ctx = Context::New( |
| isolate, |
| nullptr, // extensions |
| object_template, |
| {}, // global object |
| v8::DeserializeInternalFieldsCallback(), // deserialization callback |
| queue); |
| if (ctx.IsEmpty() || InitializeBaseContextForSnapshot(ctx).IsNothing()) { |
| return MaybeLocal<Context>(); |
| } |
| } else if (!Context::FromSnapshot( |
| isolate, |
| SnapshotData::kNodeVMContextIndex, |
| v8::DeserializeInternalFieldsCallback(), // deserialization |
| // callback |
| nullptr, // extensions |
| {}, // global object |
| queue) |
| .ToLocal(&ctx)) { |
| return MaybeLocal<Context>(); |
| } |
| |
| return scope.Escape(ctx); |
| } |
| |
| ContextifyContext* ContextifyContext::New(Local<Context> v8_context, |
| Environment* env, |
| Local<Object> sandbox_obj, |
| ContextOptions* options) { |
| HandleScope scope(env->isolate()); |
| CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla); |
| // This only initializes part of the context. The primordials are |
| // only initialized when needed because even deserializing them slows |
| // things down significantly and they are only needed in rare occasions |
| // in the vm contexts. |
| if (InitializeContextRuntime(v8_context).IsNothing()) { |
| return {}; |
| } |
| |
| Local<Context> main_context = env->context(); |
| Local<Object> new_context_global = v8_context->Global(); |
| v8_context->SetSecurityToken(main_context->GetSecurityToken()); |
| |
| // We need to tie the lifetime of the sandbox object with the lifetime of |
| // newly created context. We do this by making them hold references to each |
| // other. The context can directly hold a reference to the sandbox as an |
| // embedder data field. The sandbox uses a private symbol to hold a reference |
| // to the ContextifyContext wrapper which in turn internally references |
| // the context from its constructor. |
| if (sandbox_obj.IsEmpty()) { |
| v8_context->SetEmbedderData(ContextEmbedderIndex::kSandboxObject, |
| v8::Undefined(env->isolate())); |
| } else { |
| v8_context->SetEmbedderData(ContextEmbedderIndex::kSandboxObject, |
| sandbox_obj); |
| } |
| |
| // Delegate the code generation validation to |
| // node::ModifyCodeGenerationFromStrings. |
| v8_context->AllowCodeGenerationFromStrings(false); |
| v8_context->SetEmbedderData( |
| ContextEmbedderIndex::kAllowCodeGenerationFromStrings, |
| options->allow_code_gen_strings); |
| v8_context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, |
| options->allow_code_gen_wasm); |
| |
| Utf8Value name_val(env->isolate(), options->name); |
| ContextInfo info(name_val.ToString()); |
| if (!options->origin.IsEmpty()) { |
| Utf8Value origin_val(env->isolate(), options->origin); |
| info.origin = origin_val.ToString(); |
| } |
| |
| ContextifyContext* result; |
| Local<Object> wrapper; |
| { |
| Context::Scope context_scope(v8_context); |
| if (!sandbox_obj.IsEmpty()) { |
| Local<String> ctor_name = sandbox_obj->GetConstructorName(); |
| if (!ctor_name->Equals(v8_context, env->object_string()) |
| .FromMaybe(false) && |
| new_context_global |
| ->DefineOwnProperty( |
| v8_context, |
| v8::Symbol::GetToStringTag(env->isolate()), |
| ctor_name, |
| static_cast<v8::PropertyAttribute>(v8::DontEnum)) |
| .IsNothing()) { |
| return {}; |
| } |
| } |
| |
| // Assign host_defined_options_id to the global object so that in the |
| // callback of ImportModuleDynamically, we can get the |
| // host_defined_options_id from the v8::Context without accessing the |
| // wrapper object. |
| if (new_context_global |
| ->SetPrivate(v8_context, |
| env->host_defined_option_symbol(), |
| options->host_defined_options_id) |
| .IsNothing()) { |
| return {}; |
| } |
| |
| env->AssignToContext(v8_context, nullptr, info); |
| |
| if (!env->contextify_wrapper_template() |
| ->NewInstance(v8_context) |
| .ToLocal(&wrapper)) { |
| return {}; |
| } |
| DCHECK_NOT_NULL(env->isolate()->GetCppHeap()); |
| result = cppgc::MakeGarbageCollected<ContextifyContext>( |
| env->cppgc_allocation_handle(), env, wrapper, v8_context, options); |
| } |
| |
| Local<Object> wrapper_holder = |
| sandbox_obj.IsEmpty() ? new_context_global : sandbox_obj; |
| if (!wrapper_holder.IsEmpty() && |
| wrapper_holder |
| ->SetPrivate( |
| v8_context, env->contextify_context_private_symbol(), wrapper) |
| .IsNothing()) { |
| return {}; |
| } |
| |
| // Assign host_defined_options_id to the sandbox object or the global object |
| // (for vanilla contexts) so that module callbacks like |
| // importModuleDynamically can be registered once back to the JS land. |
| if (!sandbox_obj.IsEmpty() && |
| sandbox_obj |
| ->SetPrivate(v8_context, |
| env->host_defined_option_symbol(), |
| options->host_defined_options_id) |
| .IsNothing()) { |
| return {}; |
| } |
| return result; |
| } |
| |
| void ContextifyContext::CreatePerIsolateProperties( |
| IsolateData* isolate_data, Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| SetMethod(isolate, target, "makeContext", MakeContext); |
| } |
| |
| void ContextifyContext::RegisterExternalReferences( |
| ExternalReferenceRegistry* registry) { |
| registry->Register(MakeContext); |
| registry->Register(PropertyQueryCallback); |
| registry->Register(PropertyGetterCallback); |
| registry->Register(PropertySetterCallback); |
| registry->Register(PropertyDescriptorCallback); |
| registry->Register(PropertyDeleterCallback); |
| registry->Register(PropertyEnumeratorCallback); |
| registry->Register(PropertyDefinerCallback); |
| registry->Register(IndexedPropertyQueryCallback); |
| registry->Register(IndexedPropertyGetterCallback); |
| registry->Register(IndexedPropertySetterCallback); |
| registry->Register(IndexedPropertyDescriptorCallback); |
| registry->Register(IndexedPropertyDeleterCallback); |
| registry->Register(IndexedPropertyDefinerCallback); |
| registry->Register(IndexedPropertyEnumeratorCallback); |
| } |
| |
| // makeContext(sandbox, name, origin, strings, wasm); |
| void ContextifyContext::MakeContext(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ContextOptions options; |
| |
| CHECK_EQ(args.Length(), 7); |
| Local<Object> sandbox; |
| if (args[0]->IsObject()) { |
| sandbox = args[0].As<Object>(); |
| // Don't allow contextifying a sandbox multiple times. |
| CHECK(!sandbox |
| ->HasPrivate(env->context(), |
| env->contextify_context_private_symbol()) |
| .FromJust()); |
| } else { |
| CHECK(args[0]->IsSymbol()); |
| options.vanilla = true; |
| } |
| |
| CHECK(args[1]->IsString()); |
| options.name = args[1].As<String>(); |
| |
| CHECK(args[2]->IsString() || args[2]->IsUndefined()); |
| if (args[2]->IsString()) { |
| options.origin = args[2].As<String>(); |
| } |
| |
| CHECK(args[3]->IsBoolean()); |
| options.allow_code_gen_strings = args[3].As<Boolean>(); |
| |
| CHECK(args[4]->IsBoolean()); |
| options.allow_code_gen_wasm = args[4].As<Boolean>(); |
| |
| if (args[5]->IsBoolean() && args[5]->BooleanValue(env->isolate())) { |
| options.own_microtask_queue = |
| MicrotaskQueue::New(env->isolate(), MicrotasksPolicy::kExplicit); |
| } |
| |
| CHECK(args[6]->IsSymbol()); |
| options.host_defined_options_id = args[6].As<Symbol>(); |
| |
| TryCatchScope try_catch(env); |
| ContextifyContext* context_ptr = |
| ContextifyContext::New(env, sandbox, &options); |
| |
| if (try_catch.HasCaught()) { |
| if (!try_catch.HasTerminated()) |
| try_catch.ReThrow(); |
| return; |
| } |
| |
| if (sandbox.IsEmpty()) { |
| args.GetReturnValue().Set(context_ptr->context()->Global()); |
| } else { |
| args.GetReturnValue().Set(sandbox); |
| } |
| } |
| |
| // static |
| ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox( |
| Environment* env, const Local<Object>& wrapper_holder) { |
| Local<Value> contextify; |
| if (wrapper_holder |
| ->GetPrivate(env->context(), env->contextify_context_private_symbol()) |
| .ToLocal(&contextify) && |
| contextify->IsObject()) { |
| return Unwrap<ContextifyContext>(contextify.As<Object>()); |
| } |
| return nullptr; |
| } |
| |
| template <typename T> |
| ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo<T>& args) { |
| // TODO(joyeecheung): it should be fine to simply use |
| // args.GetIsolate()->GetCurrentContext() and take the pointer at |
| // ContextEmbedderIndex::kContextifyContext, as V8 is supposed to |
| // push the creation context before invoking these callbacks. |
| return Get(args.HolderV2()); |
| } |
| |
| ContextifyContext* ContextifyContext::Get(Local<Object> object) { |
| Local<Context> context; |
| if (!object->GetCreationContext().ToLocal(&context)) { |
| return nullptr; |
| } |
| if (!ContextEmbedderTag::IsNodeContext(context)) { |
| return nullptr; |
| } |
| return static_cast<ContextifyContext*>( |
| context->GetAlignedPointerFromEmbedderData( |
| ContextEmbedderIndex::kContextifyContext, |
| EmbedderDataTag::kPerContextData)); |
| } |
| |
| bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) { |
| return ctx == nullptr || ctx->context_.IsEmpty(); |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertyQueryCallback( |
| Local<Name> property, const PropertyCallbackInfo<Integer>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Context> context = ctx->context(); |
| Local<Object> sandbox = ctx->sandbox(); |
| |
| PropertyAttribute attr; |
| |
| Maybe<bool> maybe_has = sandbox->HasRealNamedProperty(context, property); |
| if (maybe_has.IsNothing()) { |
| return Intercepted::kNo; |
| } else if (maybe_has.FromJust()) { |
| Maybe<PropertyAttribute> maybe_attr = |
| sandbox->GetRealNamedPropertyAttributes(context, property); |
| if (!maybe_attr.To(&attr)) { |
| return Intercepted::kNo; |
| } |
| args.GetReturnValue().Set(attr); |
| return Intercepted::kYes; |
| } else { |
| maybe_has = ctx->global_proxy()->HasRealNamedProperty(context, property); |
| if (maybe_has.IsNothing()) { |
| return Intercepted::kNo; |
| } else if (maybe_has.FromJust()) { |
| Maybe<PropertyAttribute> maybe_attr = |
| ctx->global_proxy()->GetRealNamedPropertyAttributes(context, |
| property); |
| if (!maybe_attr.To(&attr)) { |
| return Intercepted::kNo; |
| } |
| args.GetReturnValue().Set(attr); |
| return Intercepted::kYes; |
| } |
| } |
| |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertyGetterCallback( |
| Local<Name> property, const PropertyCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Context> context = ctx->context(); |
| Local<Object> sandbox = ctx->sandbox(); |
| |
| TryCatchScope try_catch(env); |
| MaybeLocal<Value> maybe_rv = |
| sandbox->GetRealNamedProperty(context, property); |
| if (maybe_rv.IsEmpty()) { |
| maybe_rv = |
| ctx->global_proxy()->GetRealNamedProperty(context, property); |
| } |
| |
| Local<Value> rv; |
| if (maybe_rv.ToLocal(&rv)) { |
| if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| try_catch.ReThrow(); |
| } |
| if (rv == sandbox) |
| rv = ctx->global_proxy(); |
| |
| args.GetReturnValue().Set(rv); |
| return Intercepted::kYes; |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertySetterCallback( |
| Local<Name> property, |
| Local<Value> value, |
| const PropertyCallbackInfo<void>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Context> context = ctx->context(); |
| PropertyAttribute attributes = PropertyAttribute::None; |
| bool is_declared_on_global_proxy = ctx->global_proxy() |
| ->GetRealNamedPropertyAttributes(context, property) |
| .To(&attributes); |
| bool read_only = |
| static_cast<int>(attributes) & |
| static_cast<int>(PropertyAttribute::ReadOnly); |
| |
| bool is_declared_on_sandbox = ctx->sandbox() |
| ->GetRealNamedPropertyAttributes(context, property) |
| .To(&attributes); |
| read_only = read_only || |
| (static_cast<int>(attributes) & |
| static_cast<int>(PropertyAttribute::ReadOnly)); |
| |
| if (read_only) { |
| return Intercepted::kNo; |
| } |
| |
| // V8 comment: As long as the context is not detached the contextual accesses |
| // are the same as regular accesses to `context->Global()`s data property. |
| // The only difference is that after detaching `args.Holder()` will |
| // become a new identity and will no longer be equal to `context->Global()`. |
| // TODO(Node.js): revise the code below as the "contextual"-ness of the |
| // store is not actually relevant here. Also, new variable declaration is |
| // reported by V8 via PropertyDefinerCallback. |
| bool is_declared = is_declared_on_global_proxy || is_declared_on_sandbox; |
| |
| /* |
| // true for x = 5 |
| // false for this.x = 5 |
| // false for Object.defineProperty(this, 'foo', ...) |
| // false for vmResult.x = 5 where vmResult = vm.runInContext(); |
| |
| bool is_contextual_store = ctx->global_proxy() != args.This(); |
| |
| // Indicator to not return before setting (undeclared) function declarations |
| // on the sandbox in strict mode, i.e. args.ShouldThrowOnError() = true. |
| // True for 'function f() {}', 'this.f = function() {}', |
| // 'var f = function()'. |
| // In effect only for 'function f() {}' because |
| // var f = function(), is_declared = true |
| // this.f = function() {}, is_contextual_store = false. |
| bool is_function = value->IsFunction(); |
| |
| bool is_declared = is_declared_on_global_proxy || is_declared_on_sandbox; |
| if (!is_declared && args.ShouldThrowOnError() && is_contextual_store && |
| !is_function) { |
| return Intercepted::kNo; |
| } |
| */ |
| if (!is_declared && property->IsSymbol()) { |
| return Intercepted::kNo; |
| } |
| if (ctx->sandbox()->Set(context, property, value).IsNothing()) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Value> desc; |
| if (is_declared_on_sandbox && |
| ctx->sandbox() |
| ->GetOwnPropertyDescriptor(context, property) |
| .ToLocal(&desc) && |
| !desc->IsUndefined()) { |
| Environment* env = Environment::GetCurrent(context); |
| Local<Object> desc_obj = desc.As<Object>(); |
| |
| // We have to specify the return value for any contextual or get/set |
| // property |
| if (desc_obj->HasOwnProperty(context, env->get_string()).FromMaybe(false) || |
| desc_obj->HasOwnProperty(context, env->set_string()).FromMaybe(false)) { |
| return Intercepted::kYes; |
| } |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertyDescriptorCallback( |
| Local<Name> property, const PropertyCallbackInfo<Value>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Context> context = ctx->context(); |
| |
| Local<Object> sandbox = ctx->sandbox(); |
| |
| if (sandbox->HasOwnProperty(context, property).FromMaybe(false)) { |
| Local<Value> desc; |
| if (sandbox->GetOwnPropertyDescriptor(context, property).ToLocal(&desc)) { |
| args.GetReturnValue().Set(desc); |
| return Intercepted::kYes; |
| } |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertyDefinerCallback( |
| Local<Name> property, |
| const PropertyDescriptor& desc, |
| const PropertyCallbackInfo<void>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Context> context = ctx->context(); |
| Isolate* isolate = Isolate::GetCurrent(); |
| |
| PropertyAttribute attributes = PropertyAttribute::None; |
| bool is_declared = |
| ctx->global_proxy()->GetRealNamedPropertyAttributes(context, |
| property) |
| .To(&attributes); |
| bool read_only = |
| static_cast<int>(attributes) & |
| static_cast<int>(PropertyAttribute::ReadOnly); |
| bool dont_delete = static_cast<int>(attributes) & |
| static_cast<int>(PropertyAttribute::DontDelete); |
| |
| // If the property is set on the global as neither writable nor |
| // configurable, don't change it on the global or sandbox. |
| if (is_declared && read_only && dont_delete) { |
| return Intercepted::kNo; |
| } |
| |
| Local<Object> sandbox = ctx->sandbox(); |
| |
| auto define_prop_on_sandbox = |
| [&] (PropertyDescriptor* desc_for_sandbox) { |
| if (desc.has_enumerable()) { |
| desc_for_sandbox->set_enumerable(desc.enumerable()); |
| } |
| if (desc.has_configurable()) { |
| desc_for_sandbox->set_configurable(desc.configurable()); |
| } |
| // Set the property on the sandbox. |
| USE(sandbox->DefineProperty(context, property, *desc_for_sandbox)); |
| }; |
| |
| if (desc.has_get() || desc.has_set()) { |
| PropertyDescriptor desc_for_sandbox( |
| desc.has_get() ? desc.get() : Undefined(isolate).As<Value>(), |
| desc.has_set() ? desc.set() : Undefined(isolate).As<Value>()); |
| |
| define_prop_on_sandbox(&desc_for_sandbox); |
| // TODO(https://github.com/nodejs/node/issues/52634): this should return |
| // kYes to behave according to the expected semantics. |
| return Intercepted::kNo; |
| } else { |
| Local<Value> value = |
| desc.has_value() ? desc.value() : Undefined(isolate).As<Value>(); |
| |
| if (desc.has_writable()) { |
| PropertyDescriptor desc_for_sandbox(value, desc.writable()); |
| define_prop_on_sandbox(&desc_for_sandbox); |
| } else { |
| PropertyDescriptor desc_for_sandbox(value); |
| define_prop_on_sandbox(&desc_for_sandbox); |
| } |
| // TODO(https://github.com/nodejs/node/issues/52634): this should return |
| // kYes to behave according to the expected semantics. |
| return Intercepted::kNo; |
| } |
| } |
| |
| // static |
| Intercepted ContextifyContext::PropertyDeleterCallback( |
| Local<Name> property, const PropertyCallbackInfo<Boolean>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), property); |
| |
| if (success.FromMaybe(false)) { |
| return Intercepted::kNo; |
| } |
| |
| // Delete failed on the sandbox, intercept and do not delete on |
| // the global object. |
| args.GetReturnValue().Set(false); |
| return Intercepted::kYes; |
| } |
| |
| // static |
| void ContextifyContext::PropertyEnumeratorCallback( |
| const PropertyCallbackInfo<Array>& args) { |
| // Named enumerator will be invoked on Object.keys, |
| // Object.getOwnPropertyNames, Object.getOwnPropertySymbols, |
| // Object.getOwnPropertyDescriptors, for...in, etc. operations. |
| // Named enumerator should return all own non-indices property names, |
| // including string properties and symbol properties. V8 will filter the |
| // result array to match the expected symbol-only, enumerable-only with |
| // NamedPropertyQueryCallback. |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) return; |
| |
| Local<Array> properties; |
| // Only get own named properties, exclude indices. |
| if (!ctx->sandbox() |
| ->GetPropertyNames( |
| ctx->context(), |
| KeyCollectionMode::kOwnOnly, |
| static_cast<PropertyFilter>(PropertyFilter::ALL_PROPERTIES), |
| IndexFilter::kSkipIndices) |
| .ToLocal(&properties)) |
| return; |
| |
| args.GetReturnValue().Set(properties); |
| } |
| |
| // static |
| void ContextifyContext::IndexedPropertyEnumeratorCallback( |
| const PropertyCallbackInfo<Array>& args) { |
| // Indexed enumerator will be invoked on Object.keys, |
| // Object.getOwnPropertyNames, Object.getOwnPropertyDescriptors, for...in, |
| // etc. operations. Indexed enumerator should return all own non-indices index |
| // properties. V8 will filter the result array to match the expected |
| // enumerable-only with IndexedPropertyQueryCallback. |
| |
| Isolate* isolate = args.GetIsolate(); |
| HandleScope scope(isolate); |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| Local<Context> context = ctx->context(); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) return; |
| |
| Local<Array> properties; |
| |
| // Only get own index properties. |
| if (!ctx->sandbox() |
| ->GetPropertyNames( |
| context, |
| KeyCollectionMode::kOwnOnly, |
| static_cast<PropertyFilter>(PropertyFilter::SKIP_SYMBOLS), |
| IndexFilter::kIncludeIndices) |
| .ToLocal(&properties)) |
| return; |
| |
| std::vector<v8::Global<Value>> properties_vec; |
| if (FromV8Array(context, properties, &properties_vec).IsNothing()) { |
| return; |
| } |
| |
| // Filter out non-number property names. |
| LocalVector<Value> indices(isolate); |
| for (uint32_t i = 0; i < properties->Length(); i++) { |
| Local<Value> prop = properties_vec[i].Get(isolate); |
| if (!prop->IsNumber()) { |
| continue; |
| } |
| indices.push_back(prop); |
| } |
| |
| args.GetReturnValue().Set( |
| Array::New(args.GetIsolate(), indices.data(), indices.size())); |
| } |
| |
| // static |
| Intercepted ContextifyContext::IndexedPropertyQueryCallback( |
| uint32_t index, const PropertyCallbackInfo<Integer>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<String> name; |
| if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { |
| return ContextifyContext::PropertyQueryCallback(name, args); |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::IndexedPropertyGetterCallback( |
| uint32_t index, const PropertyCallbackInfo<Value>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<String> name; |
| if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { |
| return ContextifyContext::PropertyGetterCallback(name, args); |
| } |
| return Intercepted::kNo; |
| } |
| |
| Intercepted ContextifyContext::IndexedPropertySetterCallback( |
| uint32_t index, |
| Local<Value> value, |
| const PropertyCallbackInfo<void>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<String> name; |
| if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { |
| return ContextifyContext::PropertySetterCallback(name, value, args); |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::IndexedPropertyDescriptorCallback( |
| uint32_t index, const PropertyCallbackInfo<Value>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<String> name; |
| if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { |
| return ContextifyContext::PropertyDescriptorCallback(name, args); |
| } |
| return Intercepted::kNo; |
| } |
| |
| Intercepted ContextifyContext::IndexedPropertyDefinerCallback( |
| uint32_t index, |
| const PropertyDescriptor& desc, |
| const PropertyCallbackInfo<void>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Local<String> name; |
| if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { |
| return ContextifyContext::PropertyDefinerCallback(name, desc, args); |
| } |
| return Intercepted::kNo; |
| } |
| |
| // static |
| Intercepted ContextifyContext::IndexedPropertyDeleterCallback( |
| uint32_t index, const PropertyCallbackInfo<Boolean>& args) { |
| ContextifyContext* ctx = ContextifyContext::Get(args); |
| |
| // Still initializing |
| if (IsStillInitializing(ctx)) { |
| return Intercepted::kNo; |
| } |
| |
| Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), index); |
| |
| if (success.FromMaybe(false)) { |
| return Intercepted::kNo; |
| } |
| |
| // Delete failed on the sandbox, intercept and do not delete on |
| // the global object. |
| args.GetReturnValue().Set(false); |
| return Intercepted::kYes; |
| } |
| |
| void ContextifyScript::CreatePerIsolateProperties( |
| IsolateData* isolate_data, Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| Local<String> class_name = FIXED_ONE_BYTE_STRING(isolate, "ContextifyScript"); |
| |
| Local<FunctionTemplate> script_tmpl = NewFunctionTemplate(isolate, New); |
| script_tmpl->InstanceTemplate()->SetInternalFieldCount( |
| ContextifyScript::kInternalFieldCount); |
| script_tmpl->SetClassName(class_name); |
| SetProtoMethod(isolate, script_tmpl, "createCachedData", CreateCachedData); |
| SetProtoMethod(isolate, script_tmpl, "runInContext", RunInContext); |
| |
| target->Set(isolate, "ContextifyScript", script_tmpl); |
| isolate_data->set_script_context_constructor_template(script_tmpl); |
| } |
| |
| void ContextifyScript::RegisterExternalReferences( |
| ExternalReferenceRegistry* registry) { |
| registry->Register(New); |
| registry->Register(CreateCachedData); |
| registry->Register(RunInContext); |
| } |
| |
| ContextifyScript* ContextifyScript::New(Environment* env, |
| Local<Object> object) { |
| DCHECK_NOT_NULL(env->isolate()->GetCppHeap()); |
| return cppgc::MakeGarbageCollected<ContextifyScript>( |
| env->cppgc_allocation_handle(), env, object); |
| } |
| |
| void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| |
| CHECK(args.IsConstructCall()); |
| |
| const int argc = args.Length(); |
| CHECK_GE(argc, 2); |
| |
| CHECK(args[0]->IsString()); |
| Local<String> code = args[0].As<String>(); |
| |
| CHECK(args[1]->IsString()); |
| Local<String> filename = args[1].As<String>(); |
| |
| int line_offset = 0; |
| int column_offset = 0; |
| Local<ArrayBufferView> cached_data_buf; |
| bool produce_cached_data = false; |
| Local<Context> parsing_context = context; |
| |
| Local<Symbol> id_symbol; |
| if (argc > 2) { |
| // new ContextifyScript(code, filename, lineOffset, columnOffset, |
| // cachedData, produceCachedData, parsingContext, |
| // hostDefinedOptionId) |
| CHECK_EQ(argc, 8); |
| CHECK(args[2]->IsNumber()); |
| line_offset = args[2].As<Int32>()->Value(); |
| CHECK(args[3]->IsNumber()); |
| column_offset = args[3].As<Int32>()->Value(); |
| if (!args[4]->IsUndefined()) { |
| CHECK(args[4]->IsArrayBufferView()); |
| cached_data_buf = args[4].As<ArrayBufferView>(); |
| } |
| 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<Object>()); |
| CHECK_NOT_NULL(sandbox); |
| parsing_context = sandbox->context(); |
| } |
| CHECK(args[7]->IsSymbol()); |
| id_symbol = args[7].As<Symbol>(); |
| } |
| |
| ContextifyScript* contextify_script = New(env, args.This()); |
| |
| if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| TRACING_CATEGORY_NODE2(vm, script)) != 0) { |
| Utf8Value fn(isolate, filename); |
| TRACE_EVENT_BEGIN1(TRACING_CATEGORY_NODE2(vm, script), |
| "ContextifyScript::New", |
| "filename", |
| TRACE_STR_COPY(*fn)); |
| } |
| |
| ScriptCompiler::CachedData* cached_data = nullptr; |
| if (!cached_data_buf.IsEmpty()) { |
| uint8_t* data = static_cast<uint8_t*>(cached_data_buf->Buffer()->Data()); |
| cached_data = new ScriptCompiler::CachedData( |
| data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); |
| } |
| |
| Local<PrimitiveArray> host_defined_options = |
| PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); |
| host_defined_options->Set( |
| isolate, loader::HostDefinedOptions::kID, id_symbol); |
| |
| ScriptOrigin origin(filename, |
| line_offset, // line offset |
| column_offset, // column offset |
| true, // is cross origin |
| -1, // script id |
| Local<Value>(), // source map URL |
| false, // is opaque (?) |
| false, // is WASM |
| false, // is ES Module |
| host_defined_options); |
| ScriptCompiler::Source source(code, origin, cached_data); |
| ScriptCompiler::CompileOptions compile_options = |
| ScriptCompiler::kNoCompileOptions; |
| |
| if (source.GetCachedData() != nullptr) |
| compile_options = ScriptCompiler::kConsumeCodeCache; |
| |
| TryCatchScope try_catch(env); |
| ShouldNotAbortOnUncaughtScope no_abort_scope(env); |
| Context::Scope scope(parsing_context); |
| |
| MaybeLocal<UnboundScript> maybe_v8_script = |
| ScriptCompiler::CompileUnboundScript(isolate, &source, compile_options); |
| |
| Local<UnboundScript> v8_script; |
| if (!maybe_v8_script.ToLocal(&v8_script)) { |
| errors::DecorateErrorStack(env, try_catch); |
| no_abort_scope.Close(); |
| if (!try_catch.HasTerminated()) |
| try_catch.ReThrow(); |
| TRACE_EVENT_END0(TRACING_CATEGORY_NODE2(vm, script), |
| "ContextifyScript::New"); |
| return; |
| } |
| |
| contextify_script->set_unbound_script(v8_script); |
| |
| std::unique_ptr<ScriptCompiler::CachedData> new_cached_data; |
| if (produce_cached_data) { |
| new_cached_data.reset(ScriptCompiler::CreateCodeCache(v8_script)); |
| } |
| |
| auto self = args.This(); |
| |
| if (contextify_script->object() |
| ->SetPrivate(context, env->host_defined_option_symbol(), id_symbol) |
| .IsNothing()) { |
| return; |
| } |
| if (StoreCodeCacheResult(env, |
| self, |
| compile_options, |
| source, |
| produce_cached_data, |
| std::move(new_cached_data)) |
| .IsNothing()) { |
| return; |
| } |
| if (self->Set(env->context(), |
| env->source_url_string(), |
| v8_script->GetSourceURL()) |
| .IsNothing()) { |
| return; |
| } |
| if (self->Set(env->context(), |
| env->source_map_url_string(), |
| v8_script->GetSourceMappingURL()) |
| .IsNothing()) { |
| return; |
| } |
| |
| TRACE_EVENT_END0(TRACING_CATEGORY_NODE2(vm, script), "ContextifyScript::New"); |
| } |
| |
| Maybe<void> StoreCodeCacheResult( |
| Environment* env, |
| Local<Object> target, |
| ScriptCompiler::CompileOptions compile_options, |
| const v8::ScriptCompiler::Source& source, |
| bool produce_cached_data, |
| std::unique_ptr<ScriptCompiler::CachedData> new_cached_data) { |
| Local<Context> context; |
| if (!target->GetCreationContext().ToLocal(&context)) { |
| return Nothing<void>(); |
| } |
| if (compile_options == ScriptCompiler::kConsumeCodeCache) { |
| if (target |
| ->Set( |
| context, |
| env->cached_data_rejected_string(), |
| Boolean::New(env->isolate(), source.GetCachedData()->rejected)) |
| .IsNothing()) { |
| return Nothing<void>(); |
| } |
| } |
| if (produce_cached_data) { |
| bool cached_data_produced = new_cached_data != nullptr; |
| if (cached_data_produced) { |
| Local<Object> buf; |
| if (!Buffer::Copy(env, |
| reinterpret_cast<const char*>(new_cached_data->data), |
| new_cached_data->length) |
| .ToLocal(&buf) || |
| target->Set(context, env->cached_data_string(), buf).IsNothing() || |
| target |
| ->Set(context, |
| env->cached_data_produced_string(), |
| Boolean::New(env->isolate(), cached_data_produced)) |
| .IsNothing()) { |
| return Nothing<void>(); |
| } |
| } |
| } |
| return JustVoid(); |
| } |
| |
| bool ContextifyScript::InstanceOf(Environment* env, |
| const Local<Value>& value) { |
| return !value.IsEmpty() && |
| env->script_context_constructor_template()->HasInstance(value); |
| } |
| |
| void ContextifyScript::CreateCachedData( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ContextifyScript* wrapped_script; |
| ASSIGN_OR_RETURN_UNWRAP_CPPGC(&wrapped_script, args.This()); |
| std::unique_ptr<ScriptCompiler::CachedData> cached_data( |
| ScriptCompiler::CreateCodeCache(wrapped_script->unbound_script())); |
| |
| auto maybeRet = ([&] { |
| if (!cached_data) { |
| return Buffer::New(env, 0); |
| } |
| return Buffer::Copy(env, |
| reinterpret_cast<const char*>(cached_data->data), |
| cached_data->length); |
| })(); |
| |
| Local<Object> ret; |
| if (maybeRet.ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| |
| void ContextifyScript::RunInContext(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ContextifyScript* wrapped_script; |
| ASSIGN_OR_RETURN_UNWRAP_CPPGC(&wrapped_script, args.This()); |
| |
| CHECK_EQ(args.Length(), 5); |
| CHECK(args[0]->IsObject() || args[0]->IsNull()); |
| |
| Local<Context> context; |
| v8::MicrotaskQueue* microtask_queue = nullptr; |
| |
| if (args[0]->IsObject()) { |
| Local<Object> sandbox = args[0].As<Object>(); |
| // Get the context from the sandbox |
| ContextifyContext* contextify_context = |
| ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); |
| CHECK_NOT_NULL(contextify_context); |
| CHECK_EQ(contextify_context->env(), env); |
| |
| context = contextify_context->context(); |
| if (context.IsEmpty()) return; |
| |
| microtask_queue = contextify_context->microtask_queue(); |
| } else { |
| context = env->context(); |
| } |
| |
| TRACE_EVENT0(TRACING_CATEGORY_NODE2(vm, script), "RunInContext"); |
| |
| CHECK(args[1]->IsNumber()); |
| int64_t timeout; |
| if (!args[1]->IntegerValue(env->context()).To(&timeout)) { |
| return; |
| } |
| |
| CHECK(args[2]->IsBoolean()); |
| bool display_errors = args[2]->IsTrue(); |
| |
| CHECK(args[3]->IsBoolean()); |
| bool break_on_sigint = args[3]->IsTrue(); |
| |
| CHECK(args[4]->IsBoolean()); |
| bool break_on_first_line = args[4]->IsTrue(); |
| |
| // Do the eval within the context |
| EvalMachine(context, |
| env, |
| timeout, |
| display_errors, |
| break_on_sigint, |
| break_on_first_line, |
| microtask_queue, |
| args); |
| } |
| |
| bool ContextifyScript::EvalMachine(Local<Context> context, |
| Environment* env, |
| const int64_t timeout, |
| const bool display_errors, |
| const bool break_on_sigint, |
| const bool break_on_first_line, |
| MicrotaskQueue* mtask_queue, |
| const FunctionCallbackInfo<Value>& args) { |
| Context::Scope context_scope(context); |
| |
| if (!env->can_call_into_js()) |
| return false; |
| if (!ContextifyScript::InstanceOf(env, args.This())) { |
| THROW_ERR_INVALID_THIS( |
| env, |
| "Script methods can only be called on script instances."); |
| return false; |
| } |
| |
| TryCatchScope try_catch(env); |
| ContextifyScript* wrapped_script; |
| ASSIGN_OR_RETURN_UNWRAP_CPPGC(&wrapped_script, args.This(), false); |
| Local<Script> script = |
| wrapped_script->unbound_script()->BindToCurrentContext(); |
| |
| #if HAVE_INSPECTOR |
| if (break_on_first_line) { |
| if (!env->permission()->is_granted(env, |
| permission::PermissionScope::kInspector, |
| "PauseOnNextJavascriptStatement")) |
| [[unlikely]] { |
| node::permission::Permission::ThrowAccessDenied( |
| env, |
| permission::PermissionScope::kInspector, |
| "PauseOnNextJavascriptStatement"); |
| if (display_errors) { |
| // We should decorate non-termination exceptions |
| errors::DecorateErrorStack(env, try_catch); |
| } |
| try_catch.ReThrow(); |
| return false; |
| } |
| env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start"); |
| } |
| #endif |
| |
| MaybeLocal<Value> result; |
| bool timed_out = false; |
| bool received_signal = false; |
| { |
| auto wd = timeout != -1 ? std::make_optional<Watchdog>( |
| env->isolate(), timeout, &timed_out) |
| : std::nullopt; |
| auto swd = break_on_sigint ? std::make_optional<SigintWatchdog>( |
| env->isolate(), &received_signal) |
| : std::nullopt; |
| |
| result = script->Run(context); |
| if (!result.IsEmpty() && mtask_queue != nullptr) |
| mtask_queue->PerformCheckpoint(env->isolate()); |
| } |
| |
| // Convert the termination exception into a regular exception. |
| if (timed_out || received_signal) { |
| if (!env->is_main_thread() && env->is_stopping()) |
| return false; |
| env->isolate()->CancelTerminateExecution(); |
| // It is possible that execution was terminated by another timeout in |
| // which this timeout is nested, so check whether one of the watchdogs |
| // from this invocation is responsible for termination. |
| if (timed_out) { |
| node::THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, timeout); |
| } else if (received_signal) { |
| node::THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(env); |
| } |
| } |
| |
| if (try_catch.HasCaught()) { |
| if (!timed_out && !received_signal && display_errors) { |
| // We should decorate non-termination exceptions |
| errors::DecorateErrorStack(env, try_catch); |
| } |
| |
| // If there was an exception thrown during script execution, re-throw it. |
| // If one of the above checks threw, re-throw the exception instead of |
| // letting try_catch catch it. |
| // If execution has been terminated, but not by one of the watchdogs from |
| // this invocation, this will re-throw a `null` value. |
| if (!try_catch.HasTerminated()) |
| try_catch.ReThrow(); |
| |
| return false; |
| } |
| |
| // We checked for res being empty previously so this is a bit redundant |
| // but still safer than using ToLocalChecked. |
| Local<Value> res; |
| if (!result.ToLocal(&res)) return false; |
| |
| args.GetReturnValue().Set(res); |
| return true; |
| } |
| |
| Local<UnboundScript> ContextifyScript::unbound_script() const { |
| return script_.Get(env()->isolate()); |
| } |
| |
| void ContextifyScript::set_unbound_script(Local<UnboundScript> script) { |
| script_.Reset(env()->isolate(), script); |
| } |
| |
| void ContextifyScript::Trace(cppgc::Visitor* visitor) const { |
| CppgcMixin::Trace(visitor); |
| visitor->Trace(script_); |
| } |
| |
| ContextifyScript::ContextifyScript(Environment* env, Local<Object> object) { |
| CppgcMixin::Wrap(this, env, object); |
| } |
| |
| ContextifyScript::~ContextifyScript() {} |
| |
| void ContextifyFunction::RegisterExternalReferences( |
| ExternalReferenceRegistry* registry) { |
| registry->Register(CompileFunction); |
| } |
| |
| void ContextifyFunction::CreatePerIsolateProperties( |
| IsolateData* isolate_data, Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| |
| SetMethod(isolate, target, "compileFunction", CompileFunction); |
| } |
| |
| void ContextifyFunction::CompileFunction( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| |
| // Argument 1: source code |
| CHECK(args[0]->IsString()); |
| Local<String> code = args[0].As<String>(); |
| |
| // Argument 2: filename |
| CHECK(args[1]->IsString()); |
| Local<String> filename = args[1].As<String>(); |
| |
| // Argument 3: line offset |
| CHECK(args[2]->IsNumber()); |
| int line_offset = args[2].As<Int32>()->Value(); |
| |
| // Argument 4: column offset |
| CHECK(args[3]->IsNumber()); |
| int column_offset = args[3].As<Int32>()->Value(); |
| |
| // Argument 5: cached data (optional) |
| Local<ArrayBufferView> cached_data_buf; |
| if (!args[4]->IsUndefined()) { |
| CHECK(args[4]->IsArrayBufferView()); |
| cached_data_buf = args[4].As<ArrayBufferView>(); |
| } |
| |
| // Argument 6: produce cache data |
| CHECK(args[5]->IsBoolean()); |
| bool produce_cached_data = args[5]->IsTrue(); |
| |
| // Argument 7: parsing context (optional) |
| Local<Context> parsing_context; |
| if (!args[6]->IsUndefined()) { |
| CHECK(args[6]->IsObject()); |
| ContextifyContext* sandbox = |
| ContextifyContext::ContextFromContextifiedSandbox( |
| env, args[6].As<Object>()); |
| CHECK_NOT_NULL(sandbox); |
| parsing_context = sandbox->context(); |
| } else { |
| parsing_context = context; |
| } |
| |
| // Argument 8: context extensions (optional) |
| Local<Array> context_extensions_buf; |
| if (!args[7]->IsUndefined()) { |
| CHECK(args[7]->IsArray()); |
| context_extensions_buf = args[7].As<Array>(); |
| } |
| |
| // Argument 9: params for the function (optional) |
| Local<Array> params_buf; |
| if (!args[8]->IsUndefined()) { |
| CHECK(args[8]->IsArray()); |
| params_buf = args[8].As<Array>(); |
| } |
| |
| // Argument 10: host-defined option symbol |
| CHECK(args[9]->IsSymbol()); |
| Local<Symbol> id_symbol = args[9].As<Symbol>(); |
| |
| // Read cache from cached data buffer |
| ScriptCompiler::CachedData* cached_data = nullptr; |
| if (!cached_data_buf.IsEmpty()) { |
| uint8_t* data = static_cast<uint8_t*>(cached_data_buf->Buffer()->Data()); |
| cached_data = new ScriptCompiler::CachedData( |
| data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); |
| } |
| |
| Local<PrimitiveArray> host_defined_options = |
| loader::ModuleWrap::GetHostDefinedOptions(isolate, id_symbol); |
| |
| ScriptOrigin origin(filename, |
| line_offset, // line offset |
| column_offset, // column offset |
| true, // is cross origin |
| -1, // script id |
| Local<Value>(), // source map URL |
| false, // is opaque (?) |
| false, // is WASM |
| false, // is ES Module |
| host_defined_options); |
| ScriptCompiler::Source source(code, origin, cached_data); |
| |
| ScriptCompiler::CompileOptions options; |
| if (source.GetCachedData() != nullptr) { |
| options = ScriptCompiler::kConsumeCodeCache; |
| } else { |
| options = ScriptCompiler::kNoCompileOptions; |
| } |
| |
| Context::Scope scope(parsing_context); |
| |
| // Read context extensions from buffer |
| LocalVector<Object> context_extensions(isolate); |
| if (!context_extensions_buf.IsEmpty()) { |
| for (uint32_t n = 0; n < context_extensions_buf->Length(); n++) { |
| Local<Value> val; |
| if (!context_extensions_buf->Get(context, n).ToLocal(&val)) return; |
| CHECK(val->IsObject()); |
| context_extensions.push_back(val.As<Object>()); |
| } |
| } |
| |
| // Read params from params buffer |
| LocalVector<String> params(isolate); |
| if (!params_buf.IsEmpty()) { |
| for (uint32_t n = 0; n < params_buf->Length(); n++) { |
| Local<Value> val; |
| if (!params_buf->Get(context, n).ToLocal(&val)) return; |
| CHECK(val->IsString()); |
| params.push_back(val.As<String>()); |
| } |
| } |
| |
| TryCatchScope try_catch(env); |
| MaybeLocal<Object> maybe_result = |
| CompileFunctionAndCacheResult(env, |
| parsing_context, |
| &source, |
| params, |
| context_extensions, |
| options, |
| produce_cached_data, |
| id_symbol, |
| try_catch); |
| Local<Object> result; |
| if (!maybe_result.ToLocal(&result)) { |
| CHECK(try_catch.HasCaught()); |
| try_catch.ReThrow(); |
| return; |
| } |
| args.GetReturnValue().Set(result); |
| } |
| |
| static LocalVector<String> GetCJSParameters(IsolateData* data) { |
| LocalVector<String> result(data->isolate(), |
| { |
| data->exports_string(), |
| data->require_string(), |
| data->module_string(), |
| data->__filename_string(), |
| data->__dirname_string(), |
| }); |
| return result; |
| } |
| |
| MaybeLocal<Object> ContextifyFunction::CompileFunctionAndCacheResult( |
| Environment* env, |
| Local<Context> parsing_context, |
| ScriptCompiler::Source* source, |
| LocalVector<String> params, |
| LocalVector<Object> context_extensions, |
| ScriptCompiler::CompileOptions options, |
| bool produce_cached_data, |
| Local<Symbol> id_symbol, |
| const TryCatchScope& try_catch) { |
| MaybeLocal<Function> maybe_fn = ScriptCompiler::CompileFunction( |
| parsing_context, |
| source, |
| params.size(), |
| params.data(), |
| context_extensions.size(), |
| context_extensions.data(), |
| options, |
| v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason); |
| |
| Local<Function> fn; |
| if (!maybe_fn.ToLocal(&fn)) { |
| CHECK(try_catch.HasCaught()); |
| if (!try_catch.HasTerminated()) { |
| errors::DecorateErrorStack(env, try_catch); |
| } |
| return {}; |
| } |
| |
| Local<Context> context = env->context(); |
| if (fn->SetPrivate(context, env->host_defined_option_symbol(), id_symbol) |
| .IsNothing()) { |
| return {}; |
| } |
| |
| auto tmpl = env->compiled_function_template(); |
| if (tmpl.IsEmpty()) { |
| static constexpr std::string_view names[] = { |
| "function", |
| "sourceURL", |
| "sourceMapURL", |
| "cachedDataRejected", |
| "cachedDataProduced", |
| "cachedData", |
| }; |
| tmpl = DictionaryTemplate::New(env->isolate(), names); |
| env->set_compiled_function_template(tmpl); |
| } |
| |
| auto scriptOrigin = fn->GetScriptOrigin(); |
| MaybeLocal<Value> values[] = { |
| fn, |
| // ScriptOrigin::ResourceName() returns SourceURL magic comment content if |
| // present. |
| scriptOrigin.ResourceName(), |
| scriptOrigin.SourceMapUrl(), |
| // These are conditionally filled in by StoreCodeCacheResult below. |
| Undefined(env->isolate()), // cachedDataRejected |
| Undefined(env->isolate()), // cachedDataProduced |
| Undefined(env->isolate()), // cachedData |
| }; |
| |
| Local<Object> result; |
| if (!NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&result)) { |
| return {}; |
| } |
| |
| std::unique_ptr<ScriptCompiler::CachedData> new_cached_data; |
| if (produce_cached_data) { |
| new_cached_data.reset(ScriptCompiler::CreateCodeCacheForFunction(fn)); |
| } |
| if (StoreCodeCacheResult(env, |
| result, |
| options, |
| *source, |
| produce_cached_data, |
| std::move(new_cached_data)) |
| .IsNothing()) { |
| return {}; |
| } |
| |
| return result; |
| } |
| |
| // When compiling as CommonJS source code that contains ESM syntax, the |
| // following error messages are returned: |
| // - `import` statements: "Cannot use import statement outside a module" |
| // - `export` statements: "Unexpected token 'export'" |
| // - `import.meta` references: "Cannot use 'import.meta' outside a module" |
| // Dynamic `import()` is permitted in CommonJS, so it does not error. |
| // While top-level `await` is not permitted in CommonJS, it returns the same |
| // error message as when `await` is used in a sync function, so we don't use it |
| // as a disambiguation. |
| static const auto esm_syntax_error_messages = std::array<std::string_view, 3>{ |
| "Cannot use import statement outside a module", // `import` statements |
| "Unexpected token 'export'", // `export` statements |
| "Cannot use 'import.meta' outside a module"}; // `import.meta` references |
| |
| // Another class of error messages that we need to check for are syntax errors |
| // where the syntax throws when parsed as CommonJS but succeeds when parsed as |
| // ESM. So far, the cases we've found are: |
| // - CommonJS module variables (`module`, `exports`, `require`, `__filename`, |
| // `__dirname`): if the user writes code such as `const module =` in the top |
| // level of a CommonJS module, it will throw a syntax error; but the same |
| // code is valid in ESM. |
| // - Top-level `await`: if the user writes `await` at the top level of a |
| // CommonJS module, it will throw a syntax error; but the same code is valid |
| // in ESM. |
| static const auto throws_only_in_cjs_error_messages = |
| std::array<std::string_view, 6>{ |
| "Identifier 'module' has already been declared", |
| "Identifier 'exports' has already been declared", |
| "Identifier 'require' has already been declared", |
| "Identifier '__filename' has already been declared", |
| "Identifier '__dirname' has already been declared", |
| "await is only valid in async functions and " |
| "the top level bodies of modules"}; |
| |
| static const auto maybe_top_level_await_errors = |
| std::array<std::string_view, 2>{ |
| "missing ) after argument list", // example: `func(await 1);` |
| "SyntaxError: Unexpected" // example: `if(await 1)` |
| }; |
| |
| // If cached_data is provided, it would be used for the compilation and |
| // the on-disk compilation cache from NODE_COMPILE_CACHE (if configured) |
| // would be ignored. |
| static MaybeLocal<Function> CompileFunctionForCJSLoader( |
| Environment* env, |
| Local<Context> context, |
| Local<String> code, |
| Local<String> filename, |
| bool* cache_rejected, |
| bool is_cjs_scope, |
| ScriptCompiler::CachedData* cached_data) { |
| Isolate* isolate = Isolate::GetCurrent(); |
| EscapableHandleScope scope(isolate); |
| |
| Local<Symbol> symbol = env->vm_dynamic_import_default_internal(); |
| Local<PrimitiveArray> hdo = |
| loader::ModuleWrap::GetHostDefinedOptions(isolate, symbol); |
| ScriptOrigin origin(filename, |
| 0, // line offset |
| 0, // column offset |
| true, // is cross origin |
| -1, // script id |
| Local<Value>(), // source map URL |
| false, // is opaque |
| false, // is WASM |
| false, // is ES Module |
| hdo); |
| |
| CompileCacheEntry* cache_entry = nullptr; |
| if (cached_data == nullptr && env->use_compile_cache()) { |
| cache_entry = env->compile_cache_handler()->GetOrInsert( |
| code, filename, CachedCodeType::kCommonJS); |
| } |
| if (cache_entry != nullptr && cache_entry->cache != nullptr) { |
| // source will take ownership of cached_data. |
| cached_data = cache_entry->CopyCache(); |
| } |
| |
| ScriptCompiler::Source source(code, origin, cached_data); |
| ScriptCompiler::CompileOptions options; |
| if (cached_data == nullptr) { |
| options = ScriptCompiler::kNoCompileOptions; |
| } else { |
| options = ScriptCompiler::kConsumeCodeCache; |
| } |
| |
| LocalVector<String> params(isolate); |
| if (is_cjs_scope) { |
| params = GetCJSParameters(env->isolate_data()); |
| } |
| MaybeLocal<Function> maybe_fn = ScriptCompiler::CompileFunction( |
| context, |
| &source, |
| params.size(), |
| params.data(), |
| 0, /* context extensions size */ |
| nullptr, /* context extensions data */ |
| // TODO(joyeecheung): allow optional eager compilation. |
| options); |
| |
| Local<Function> fn; |
| if (!maybe_fn.ToLocal(&fn)) { |
| return scope.EscapeMaybe(MaybeLocal<Function>()); |
| } |
| |
| if (options == ScriptCompiler::kConsumeCodeCache) { |
| *cache_rejected = source.GetCachedData()->rejected; |
| } |
| if (cache_entry != nullptr) { |
| env->compile_cache_handler()->MaybeSave(cache_entry, fn, *cache_rejected); |
| } |
| return scope.Escape(fn); |
| } |
| |
| static std::string GetRequireEsmWarning(Local<String> filename) { |
| Isolate* isolate = Isolate::GetCurrent(); |
| Utf8Value filename_utf8(isolate, filename); |
| |
| std::string warning_message = |
| "Failed to load the ES module: " + filename_utf8.ToString() + |
| ". Make sure to set \"type\": \"module\" in the nearest package.json " |
| "file " |
| "or use the .mjs extension."; |
| return warning_message; |
| } |
| |
| static bool warned_about_require_esm = false; |
| |
| static bool ShouldRetryAsESM(Realm* realm, |
| Local<String> message, |
| Local<String> code, |
| Local<String> resource_name); |
| |
| static void CompileFunctionForCJSLoader( |
| const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsString()); |
| CHECK(args[1]->IsString()); |
| CHECK(args[2]->IsBoolean()); |
| CHECK(args[3]->IsBoolean()); |
| Local<String> code = args[0].As<String>(); |
| Local<String> filename = args[1].As<String>(); |
| bool is_sea_main = args[2].As<Boolean>()->Value(); |
| bool should_detect_module = args[3].As<Boolean>()->Value(); |
| |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| Realm* realm = Realm::GetCurrent(context); |
| Environment* env = realm->env(); |
| |
| bool cache_rejected = false; |
| Local<Function> fn; |
| Local<Value> cjs_exception; |
| Local<Message> cjs_message; |
| |
| ScriptCompiler::CachedData* cached_data = nullptr; |
| #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION |
| if (is_sea_main) { |
| sea::SeaResource sea = sea::FindSingleExecutableResource(); |
| // Use the "main" field in SEA config for the filename. |
| Local<Value> filename_from_sea; |
| if (!ToV8Value(context, sea.code_path).ToLocal(&filename_from_sea)) { |
| return; |
| } |
| filename = filename_from_sea.As<String>(); |
| if (sea.use_code_cache()) { |
| std::string_view data = sea.code_cache.value(); |
| cached_data = new ScriptCompiler::CachedData( |
| reinterpret_cast<const uint8_t*>(data.data()), |
| static_cast<int>(data.size()), |
| v8::ScriptCompiler::CachedData::BufferNotOwned); |
| } |
| } |
| #endif |
| |
| { |
| ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); |
| TryCatchScope try_catch(env); |
| if (!CompileFunctionForCJSLoader( |
| env, context, code, filename, &cache_rejected, true, cached_data) |
| .ToLocal(&fn)) { |
| CHECK(try_catch.HasCaught()); |
| CHECK(!try_catch.HasTerminated()); |
| cjs_exception = try_catch.Exception(); |
| cjs_message = try_catch.Message(); |
| errors::DecorateErrorStack(env, cjs_exception, cjs_message); |
| } |
| } |
| |
| bool can_parse_as_esm = false; |
| if (!cjs_exception.IsEmpty()) { |
| // Use the URL to match what would be used in the origin if it's going to |
| // be reparsed as ESM. |
| Utf8Value filename_utf8(isolate, filename); |
| std::string url = url::FromFilePath(filename_utf8.ToStringView()); |
| Local<Value> url_value; |
| if (!ToV8Value(context, url).ToLocal(&url_value)) { |
| return; |
| } |
| can_parse_as_esm = ShouldRetryAsESM( |
| realm, cjs_message->Get(), code, url_value.As<String>()); |
| if (!can_parse_as_esm) { |
| // The syntax error is not related to ESM, throw the original error. |
| isolate->ThrowException(cjs_exception); |
| return; |
| } |
| |
| if (!should_detect_module) { |
| bool should_throw = true; |
| if (!warned_about_require_esm) { |
| // This needs to call process.emit('warning') in JS which can throw if |
| // the user listener throws. In that case, don't try to throw the syntax |
| // error. |
| std::string warning_message = GetRequireEsmWarning(filename); |
| should_throw = ProcessEmitWarningSync(env, warning_message).IsJust(); |
| } |
| if (should_throw) { |
| isolate->ThrowException(cjs_exception); |
| } |
| return; |
| } |
| } |
| |
| auto tmpl = env->compiled_function_cjs_template(); |
| if (tmpl.IsEmpty()) { |
| static constexpr std::string_view names[] = { |
| "cachedDataRejected", |
| "sourceMapURL", |
| "sourceURL", |
| "function", |
| "canParseAsESM", |
| }; |
| tmpl = DictionaryTemplate::New(isolate, names); |
| env->set_compiled_function_cjs_template(tmpl); |
| } |
| |
| Local<Value> undefined = v8::Undefined(isolate); |
| |
| MaybeLocal<Value> values[] = { |
| Boolean::New(isolate, cache_rejected), |
| fn.IsEmpty() ? undefined : fn->GetScriptOrigin().SourceMapUrl(), |
| // ScriptOrigin::ResourceName() returns SourceURL magic comment content if |
| // present. |
| fn.IsEmpty() ? undefined : fn->GetScriptOrigin().ResourceName(), |
| fn.IsEmpty() ? undefined : fn.As<Value>(), |
| Boolean::New(isolate, can_parse_as_esm), |
| }; |
| Local<Object> result; |
| if (NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&result)) { |
| args.GetReturnValue().Set(result); |
| } |
| } |
| |
| bool ShouldRetryAsESM(Realm* realm, |
| Local<String> message, |
| Local<String> code, |
| Local<String> resource_name) { |
| Isolate* isolate = realm->isolate(); |
| |
| Utf8Value message_value(isolate, message); |
| auto message_view = message_value.ToStringView(); |
| |
| // These indicates that the file contains syntaxes that are only valid in |
| // ESM. So it must be true. |
| for (const auto& error_message : esm_syntax_error_messages) { |
| if (message_view.find(error_message) != std::string_view::npos) { |
| return true; |
| } |
| } |
| |
| // Check if the error message is allowed in ESM but not in CommonJS. If it |
| // is the case, let's check if file can be compiled as ESM. |
| bool maybe_valid_in_esm = false; |
| for (const auto& error_message : throws_only_in_cjs_error_messages) { |
| if (message_view.find(error_message) != std::string_view::npos) { |
| maybe_valid_in_esm = true; |
| break; |
| } |
| } |
| |
| for (const auto& error_message : maybe_top_level_await_errors) { |
| if (message_view.find(error_message) != std::string_view::npos) { |
| // If the error message is related to top-level await, we can try to |
| // compile it as ESM. |
| maybe_valid_in_esm = true; |
| break; |
| } |
| } |
| |
| if (!maybe_valid_in_esm) { |
| return false; |
| } |
| |
| bool cache_rejected = false; |
| TryCatchScope try_catch(realm->env()); |
| ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); |
| Local<v8::Module> module; |
| Local<PrimitiveArray> hdo = loader::ModuleWrap::GetHostDefinedOptions( |
| isolate, realm->isolate_data()->source_text_module_default_hdo()); |
| if (loader::ModuleWrap::CompileSourceTextModule( |
| realm, code, resource_name, 0, 0, hdo, std::nullopt, &cache_rejected) |
| .ToLocal(&module)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void ContainsModuleSyntax(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| Realm* realm = Realm::GetCurrent(context); |
| Environment* env = realm->env(); |
| |
| CHECK_GE(args.Length(), 2); |
| |
| // Argument 1: source code |
| CHECK(args[0]->IsString()); |
| Local<String> code = args[0].As<String>(); |
| |
| // Argument 2: filename |
| CHECK(args[1]->IsString()); |
| Local<String> filename = args[1].As<String>(); |
| |
| // Argument 3: resource name (URL for ES module). |
| Local<String> resource_name = filename; |
| if (args[2]->IsString()) { |
| resource_name = args[2].As<String>(); |
| } |
| // Argument 4: flag to indicate if CJS variables should not be in scope |
| // (they should be for normal CommonJS modules, but not for the |
| // CommonJS eval scope). |
| bool cjs_var = !args[3]->IsString(); |
| |
| bool cache_rejected = false; |
| Local<String> message; |
| { |
| Local<Function> fn; |
| TryCatchScope try_catch(env); |
| ShouldNotAbortOnUncaughtScope no_abort_scope(env); |
| if (CompileFunctionForCJSLoader( |
| env, context, code, filename, &cache_rejected, cjs_var, nullptr) |
| .ToLocal(&fn)) { |
| args.GetReturnValue().Set(false); |
| return; |
| } |
| CHECK(try_catch.HasCaught()); |
| message = try_catch.Message()->Get(); |
| } |
| |
| bool result = ShouldRetryAsESM(realm, message, code, resource_name); |
| args.GetReturnValue().Set(result); |
| } |
| |
| static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) { |
| int ret = SigintWatchdogHelper::GetInstance()->Start(); |
| args.GetReturnValue().Set(ret == 0); |
| } |
| |
| static void StopSigintWatchdog(const FunctionCallbackInfo<Value>& args) { |
| bool had_pending_signals = SigintWatchdogHelper::GetInstance()->Stop(); |
| args.GetReturnValue().Set(had_pending_signals); |
| } |
| |
| static void WatchdogHasPendingSigint(const FunctionCallbackInfo<Value>& args) { |
| bool ret = SigintWatchdogHelper::GetInstance()->HasPendingSignal(); |
| args.GetReturnValue().Set(ret); |
| } |
| |
| static void MeasureMemory(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args[0]->IsInt32()); |
| CHECK(args[1]->IsInt32()); |
| int32_t mode = args[0].As<v8::Int32>()->Value(); |
| int32_t execution = args[1].As<v8::Int32>()->Value(); |
| Isolate* isolate = args.GetIsolate(); |
| |
| Local<Context> current_context = isolate->GetCurrentContext(); |
| Local<Promise::Resolver> resolver; |
| if (!Promise::Resolver::New(current_context).ToLocal(&resolver)) return; |
| std::unique_ptr<v8::MeasureMemoryDelegate> delegate = |
| v8::MeasureMemoryDelegate::Default( |
| isolate, |
| current_context, |
| resolver, |
| static_cast<v8::MeasureMemoryMode>(mode)); |
| isolate->MeasureMemory(std::move(delegate), |
| static_cast<v8::MeasureMemoryExecution>(execution)); |
| Local<Promise> promise = resolver->GetPromise(); |
| |
| args.GetReturnValue().Set(promise); |
| } |
| |
| void CreatePerIsolateProperties(IsolateData* isolate_data, |
| Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| |
| ContextifyContext::CreatePerIsolateProperties(isolate_data, target); |
| ContextifyScript::CreatePerIsolateProperties(isolate_data, target); |
| ContextifyFunction::CreatePerIsolateProperties(isolate_data, target); |
| |
| SetMethod(isolate, target, "startSigintWatchdog", StartSigintWatchdog); |
| SetMethod(isolate, target, "stopSigintWatchdog", StopSigintWatchdog); |
| // Used in tests. |
| SetMethodNoSideEffect( |
| isolate, target, "watchdogHasPendingSigint", WatchdogHasPendingSigint); |
| |
| SetMethod(isolate, target, "measureMemory", MeasureMemory); |
| SetMethod(isolate, |
| target, |
| "compileFunctionForCJSLoader", |
| CompileFunctionForCJSLoader); |
| |
| SetMethod(isolate, target, "containsModuleSyntax", ContainsModuleSyntax); |
| } |
| |
| static void CreatePerContextProperties(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = env->isolate(); |
| |
| Local<Object> constants = Object::New(env->isolate()); |
| Local<Object> measure_memory = Object::New(env->isolate()); |
| Local<Object> memory_execution = Object::New(env->isolate()); |
| |
| { |
| Local<Object> memory_mode = Object::New(env->isolate()); |
| MeasureMemoryMode SUMMARY = MeasureMemoryMode::kSummary; |
| MeasureMemoryMode DETAILED = MeasureMemoryMode::kDetailed; |
| NODE_DEFINE_CONSTANT(memory_mode, SUMMARY); |
| NODE_DEFINE_CONSTANT(memory_mode, DETAILED); |
| READONLY_PROPERTY(measure_memory, "mode", memory_mode); |
| } |
| |
| { |
| MeasureMemoryExecution DEFAULT = MeasureMemoryExecution::kDefault; |
| MeasureMemoryExecution EAGER = MeasureMemoryExecution::kEager; |
| NODE_DEFINE_CONSTANT(memory_execution, DEFAULT); |
| NODE_DEFINE_CONSTANT(memory_execution, EAGER); |
| READONLY_PROPERTY(measure_memory, "execution", memory_execution); |
| } |
| |
| READONLY_PROPERTY(constants, "measureMemory", measure_memory); |
| |
| target->Set(context, env->constants_string(), constants).Check(); |
| } |
| |
| void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
| ContextifyContext::RegisterExternalReferences(registry); |
| ContextifyScript::RegisterExternalReferences(registry); |
| ContextifyFunction::RegisterExternalReferences(registry); |
| |
| registry->Register(CompileFunctionForCJSLoader); |
| registry->Register(StartSigintWatchdog); |
| registry->Register(StopSigintWatchdog); |
| registry->Register(WatchdogHasPendingSigint); |
| registry->Register(MeasureMemory); |
| registry->Register(ContainsModuleSyntax); |
| } |
| } // namespace contextify |
| } // namespace node |
| |
| NODE_BINDING_CONTEXT_AWARE_INTERNAL( |
| contextify, node::contextify::CreatePerContextProperties) |
| NODE_BINDING_PER_ISOLATE_INIT(contextify, |
| node::contextify::CreatePerIsolateProperties) |
| NODE_BINDING_EXTERNAL_REFERENCE(contextify, |
| node::contextify::RegisterExternalReferences) |