// 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::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 Uint32ToName(Local context, uint32_t index) { return Uint32::New(context->GetIsolate(), index)->ToString(context); } } // anonymous namespace ContextifyContext* ContextifyContext::New(Environment* env, Local sandbox_obj, ContextOptions* options) { Local 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 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 wrapper, Local 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)); v8_context->SetAlignedPointerInEmbedderData( ContextEmbedderIndex::kContextifyContext, this); } void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { DCHECK(isolate_data->contextify_wrapper_template().IsEmpty()); Local global_func_template = FunctionTemplate::New(isolate_data->isolate()); Local 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 wrapper_func_template = BaseObject::MakeLazilyInitializedJSTemplate(isolate_data); Local wrapper_object_template = wrapper_func_template->InstanceTemplate(); isolate_data->set_contextify_wrapper_template(wrapper_object_template); } MaybeLocal ContextifyContext::CreateV8Context( Isolate* isolate, Local object_template, const SnapshotData* snapshot_data, MicrotaskQueue* queue) { EscapableHandleScope scope(isolate); Local 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(); } } else if (!Context::FromSnapshot( isolate, SnapshotData::kNodeVMContextIndex, v8::DeserializeInternalFieldsCallback(), // deserialization // callback nullptr, // extensions {}, // global object queue) .ToLocal(&ctx)) { return MaybeLocal(); } return scope.Escape(ctx); } ContextifyContext* ContextifyContext::New(Local v8_context, Environment* env, Local 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 main_context = env->context(); Local 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); if (!options->origin.IsEmpty()) { Utf8Value origin_val(env->isolate(), options->origin); info.origin = *origin_val; } ContextifyContext* result; Local wrapper; { Context::Scope context_scope(v8_context); if (!sandbox_obj.IsEmpty()) { Local 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::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( env->cppgc_allocation_handle(), env, wrapper, v8_context, options); } Local 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 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& args) { Environment* env = Environment::GetCurrent(args); ContextOptions options; CHECK_EQ(args.Length(), 7); Local sandbox; if (args[0]->IsObject()) { sandbox = args[0].As(); // 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(); CHECK(args[2]->IsString() || args[2]->IsUndefined()); if (args[2]->IsString()) { options.origin = args[2].As(); } CHECK(args[3]->IsBoolean()); options.allow_code_gen_strings = args[3].As(); CHECK(args[4]->IsBoolean()); options.allow_code_gen_wasm = args[4].As(); 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(); 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& wrapper_holder) { Local contextify; if (wrapper_holder ->GetPrivate(env->context(), env->contextify_context_private_symbol()) .ToLocal(&contextify) && contextify->IsObject()) { return Unwrap(contextify.As()); } return nullptr; } template ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo& 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.This()); } ContextifyContext* ContextifyContext::Get(Local object) { Local context; if (!object->GetCreationContext().ToLocal(&context)) { return nullptr; } if (!ContextEmbedderTag::IsNodeContext(context)) { return nullptr; } return static_cast( context->GetAlignedPointerFromEmbedderData( ContextEmbedderIndex::kContextifyContext)); } bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) { return ctx == nullptr || ctx->context_.IsEmpty(); } // static Intercepted ContextifyContext::PropertyQueryCallback( Local property, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local context = ctx->context(); Local sandbox = ctx->sandbox(); PropertyAttribute attr; Maybe maybe_has = sandbox->HasRealNamedProperty(context, property); if (maybe_has.IsNothing()) { return Intercepted::kNo; } else if (maybe_has.FromJust()) { Maybe 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 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 property, const PropertyCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local context = ctx->context(); Local sandbox = ctx->sandbox(); TryCatchScope try_catch(env); MaybeLocal maybe_rv = sandbox->GetRealNamedProperty(context, property); if (maybe_rv.IsEmpty()) { maybe_rv = ctx->global_proxy()->GetRealNamedProperty(context, property); } Local 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 property, Local value, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local 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(attributes) & static_cast(PropertyAttribute::ReadOnly); bool is_declared_on_sandbox = ctx->sandbox() ->GetRealNamedPropertyAttributes(context, property) .To(&attributes); read_only = read_only || (static_cast(attributes) & static_cast(PropertyAttribute::ReadOnly)); if (read_only) { return Intercepted::kNo; } // 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 desc; if (is_declared_on_sandbox && ctx->sandbox() ->GetOwnPropertyDescriptor(context, property) .ToLocal(&desc) && !desc->IsUndefined()) { Environment* env = Environment::GetCurrent(context); Local desc_obj = desc.As(); // 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 property, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local context = ctx->context(); Local sandbox = ctx->sandbox(); if (sandbox->HasOwnProperty(context, property).FromMaybe(false)) { Local desc; if (sandbox->GetOwnPropertyDescriptor(context, property).ToLocal(&desc)) { args.GetReturnValue().Set(desc); return Intercepted::kYes; } } return Intercepted::kNo; } // static Intercepted ContextifyContext::PropertyDefinerCallback( Local property, const PropertyDescriptor& desc, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local context = ctx->context(); Isolate* isolate = context->GetIsolate(); PropertyAttribute attributes = PropertyAttribute::None; bool is_declared = ctx->global_proxy()->GetRealNamedPropertyAttributes(context, property) .To(&attributes); bool read_only = static_cast(attributes) & static_cast(PropertyAttribute::ReadOnly); bool dont_delete = static_cast(attributes) & static_cast(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 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(), desc.has_set() ? desc.set() : Undefined(isolate).As()); 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 = desc.has_value() ? desc.value() : Undefined(isolate).As(); 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; } return Intercepted::kNo; } // static Intercepted ContextifyContext::PropertyDeleterCallback( Local property, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Maybe 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& 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 properties; // Only get own named properties, exclude indices. if (!ctx->sandbox() ->GetPropertyNames( ctx->context(), KeyCollectionMode::kOwnOnly, static_cast(PropertyFilter::ALL_PROPERTIES), IndexFilter::kSkipIndices) .ToLocal(&properties)) return; args.GetReturnValue().Set(properties); } // static void ContextifyContext::IndexedPropertyEnumeratorCallback( const PropertyCallbackInfo& 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 = ctx->context(); // Still initializing if (IsStillInitializing(ctx)) return; Local properties; // Only get own index properties. if (!ctx->sandbox() ->GetPropertyNames( context, KeyCollectionMode::kOwnOnly, static_cast(PropertyFilter::SKIP_SYMBOLS), IndexFilter::kIncludeIndices) .ToLocal(&properties)) return; std::vector> properties_vec; if (FromV8Array(context, properties, &properties_vec).IsNothing()) { return; } // Filter out non-number property names. LocalVector indices(isolate); for (uint32_t i = 0; i < properties->Length(); i++) { Local 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& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local 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& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local name; if (Uint32ToName(ctx->context(), index).ToLocal(&name)) { return ContextifyContext::PropertyGetterCallback(name, args); } return Intercepted::kNo; } Intercepted ContextifyContext::IndexedPropertySetterCallback( uint32_t index, Local value, const PropertyCallbackInfo& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local 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& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local 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& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Local 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& args) { ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing if (IsStillInitializing(ctx)) { return Intercepted::kNo; } Maybe 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 target) { Isolate* isolate = isolate_data->isolate(); Local class_name = FIXED_ONE_BYTE_STRING(isolate, "ContextifyScript"); Local 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) { DCHECK_NOT_NULL(env->isolate()->GetCppHeap()); return cppgc::MakeGarbageCollected( env->cppgc_allocation_handle(), env, object); } void ContextifyScript::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(); int line_offset = 0; int column_offset = 0; Local cached_data_buf; bool produce_cached_data = false; Local parsing_context = context; Local 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()->Value(); CHECK(args[3]->IsNumber()); column_offset = args[3].As()->Value(); if (!args[4]->IsUndefined()) { CHECK(args[4]->IsArrayBufferView()); 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(); } CHECK(args[7]->IsSymbol()); id_symbol = args[7].As(); } 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(cached_data_buf->Buffer()->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::kID, id_symbol); ScriptOrigin origin(filename, line_offset, // line offset column_offset, // column offset true, // is cross origin -1, // script id Local(), // 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 maybe_v8_script = ScriptCompiler::CompileUnboundScript(isolate, &source, compile_options); Local 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 new_cached_data; if (produce_cached_data) { new_cached_data.reset(ScriptCompiler::CreateCodeCache(v8_script)); } if (contextify_script->object() ->SetPrivate(context, env->host_defined_option_symbol(), id_symbol) .IsNothing()) { return; } if (StoreCodeCacheResult(env, args.This(), compile_options, source, produce_cached_data, std::move(new_cached_data)) .IsNothing()) { return; } if (args.This() ->Set(env->context(), env->source_url_string(), v8_script->GetSourceURL()) .IsNothing()) return; if (args.This() ->Set(env->context(), env->source_map_url_string(), v8_script->GetSourceMappingURL()) .IsNothing()) return; TRACE_EVENT_END0(TRACING_CATEGORY_NODE2(vm, script), "ContextifyScript::New"); } Maybe StoreCodeCacheResult( Environment* env, Local target, ScriptCompiler::CompileOptions compile_options, const v8::ScriptCompiler::Source& source, bool produce_cached_data, std::unique_ptr new_cached_data) { Local context; if (!target->GetCreationContext().ToLocal(&context)) { return Nothing(); } if (compile_options == ScriptCompiler::kConsumeCodeCache) { if (target ->Set( context, env->cached_data_rejected_string(), Boolean::New(env->isolate(), source.GetCachedData()->rejected)) .IsNothing()) { return Nothing(); } } if (produce_cached_data) { bool cached_data_produced = new_cached_data != nullptr; if (cached_data_produced) { Local buf; if (!Buffer::Copy(env, reinterpret_cast(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(); } } } return JustVoid(); } bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { return !value.IsEmpty() && env->script_context_constructor_template()->HasInstance(value); } void ContextifyScript::CreateCachedData( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ContextifyScript* wrapped_script; ASSIGN_OR_RETURN_UNWRAP_CPPGC(&wrapped_script, args.This()); std::unique_ptr cached_data( ScriptCompiler::CreateCodeCache(wrapped_script->unbound_script())); auto maybeRet = ([&] { if (!cached_data) { return Buffer::New(env, 0); } return Buffer::Copy(env, reinterpret_cast(cached_data->data), cached_data->length); })(); Local ret; if (maybeRet.ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void ContextifyScript::RunInContext(const FunctionCallbackInfo& 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; v8::MicrotaskQueue* microtask_queue = nullptr; if (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(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, 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& 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