#include "node.h" #include "node_context_data.h" #include "node_errors.h" #include "node_internals.h" #include "node_native_module_env.h" #include "node_platform.h" #include "node_v8_platform-inl.h" #include "uv.h" namespace node { using errors::TryCatchScope; using v8::Array; using v8::Context; using v8::EscapableHandleScope; using v8::Function; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::MicrotasksPolicy; using v8::Null; using v8::Object; using v8::ObjectTemplate; using v8::Private; using v8::String; using v8::Value; static bool AllowWasmCodeGenerationCallback(Local context, Local) { Local wasm_code_gen = context->GetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration); return wasm_code_gen->IsUndefined() || wasm_code_gen->IsTrue(); } static bool ShouldAbortOnUncaughtException(Isolate* isolate) { DebugSealHandleScope scope(isolate); Environment* env = Environment::GetCurrent(isolate); return env != nullptr && (env->is_main_thread() || !env->is_stopping()) && env->should_abort_on_uncaught_toggle()[0] && !env->inside_should_not_abort_on_uncaught_scope(); } static MaybeLocal PrepareStackTraceCallback(Local context, Local exception, Local trace) { Environment* env = Environment::GetCurrent(context); if (env == nullptr) { MaybeLocal s = exception->ToString(context); return s.IsEmpty() ? MaybeLocal() : MaybeLocal(s.ToLocalChecked()); } Local prepare = env->prepare_stack_trace_callback(); if (prepare.IsEmpty()) { MaybeLocal s = exception->ToString(context); return s.IsEmpty() ? MaybeLocal() : MaybeLocal(s.ToLocalChecked()); } Local args[] = { context->Global(), exception, trace, }; // This TryCatch + Rethrow is required by V8 due to details around exception // handling there. For C++ callbacks, V8 expects a scheduled exception (which // is what ReThrow gives us). Just returning the empty MaybeLocal would leave // us with a pending exception. TryCatchScope try_catch(env); MaybeLocal result = prepare->Call( context, Undefined(env->isolate()), arraysize(args), args); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } return result; } void* NodeArrayBufferAllocator::Allocate(size_t size) { if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) return UncheckedCalloc(size); else return UncheckedMalloc(size); } DebuggingArrayBufferAllocator::~DebuggingArrayBufferAllocator() { CHECK(allocations_.empty()); } void* DebuggingArrayBufferAllocator::Allocate(size_t size) { Mutex::ScopedLock lock(mutex_); void* data = NodeArrayBufferAllocator::Allocate(size); RegisterPointerInternal(data, size); return data; } void* DebuggingArrayBufferAllocator::AllocateUninitialized(size_t size) { Mutex::ScopedLock lock(mutex_); void* data = NodeArrayBufferAllocator::AllocateUninitialized(size); RegisterPointerInternal(data, size); return data; } void DebuggingArrayBufferAllocator::Free(void* data, size_t size) { Mutex::ScopedLock lock(mutex_); UnregisterPointerInternal(data, size); NodeArrayBufferAllocator::Free(data, size); } void* DebuggingArrayBufferAllocator::Reallocate(void* data, size_t old_size, size_t size) { Mutex::ScopedLock lock(mutex_); void* ret = NodeArrayBufferAllocator::Reallocate(data, old_size, size); if (ret == nullptr) { if (size == 0) // i.e. equivalent to free(). UnregisterPointerInternal(data, old_size); return nullptr; } if (data != nullptr) { auto it = allocations_.find(data); CHECK_NE(it, allocations_.end()); allocations_.erase(it); } RegisterPointerInternal(ret, size); return ret; } void DebuggingArrayBufferAllocator::RegisterPointer(void* data, size_t size) { Mutex::ScopedLock lock(mutex_); RegisterPointerInternal(data, size); } void DebuggingArrayBufferAllocator::UnregisterPointer(void* data, size_t size) { Mutex::ScopedLock lock(mutex_); UnregisterPointerInternal(data, size); } void DebuggingArrayBufferAllocator::UnregisterPointerInternal(void* data, size_t size) { if (data == nullptr) return; auto it = allocations_.find(data); CHECK_NE(it, allocations_.end()); if (size > 0) { // We allow allocations with size 1 for 0-length buffers to avoid having // to deal with nullptr values. CHECK_EQ(it->second, size); } allocations_.erase(it); } void DebuggingArrayBufferAllocator::RegisterPointerInternal(void* data, size_t size) { if (data == nullptr) return; CHECK_EQ(allocations_.count(data), 0); allocations_[data] = size; } std::unique_ptr ArrayBufferAllocator::Create(bool debug) { if (debug || per_process::cli_options->debug_arraybuffer_allocations) return std::make_unique(); else return std::make_unique(); } ArrayBufferAllocator* CreateArrayBufferAllocator() { return ArrayBufferAllocator::Create().release(); } void FreeArrayBufferAllocator(ArrayBufferAllocator* allocator) { delete allocator; } void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) { const uint64_t constrained_memory = uv_get_constrained_memory(); const uint64_t total_memory = constrained_memory > 0 ? std::min(uv_get_total_memory(), constrained_memory) : uv_get_total_memory(); if (total_memory > 0) { // V8 defaults to 700MB or 1.4GB on 32 and 64 bit platforms respectively. // This default is based on browser use-cases. Tell V8 to configure the // heap based on the actual physical memory. params->constraints.ConfigureDefaults(total_memory, 0); } } void SetIsolateUpForNode(v8::Isolate* isolate, IsolateSettingCategories cat) { switch (cat) { case IsolateSettingCategories::kErrorHandlers: isolate->AddMessageListenerWithErrorLevel( errors::PerIsolateMessageListener, Isolate::MessageErrorLevel::kMessageError | Isolate::MessageErrorLevel::kMessageWarning); isolate->SetAbortOnUncaughtExceptionCallback( ShouldAbortOnUncaughtException); isolate->SetFatalErrorHandler(OnFatalError); isolate->SetPrepareStackTraceCallback(PrepareStackTraceCallback); break; case IsolateSettingCategories::kMisc: isolate->SetMicrotasksPolicy(MicrotasksPolicy::kExplicit); isolate->SetAllowWasmCodeGenerationCallback( AllowWasmCodeGenerationCallback); isolate->SetPromiseRejectCallback(task_queue::PromiseRejectCallback); v8::CpuProfiler::UseDetailedSourcePositionsForProfiling(isolate); break; default: UNREACHABLE(); break; } } void SetIsolateUpForNode(v8::Isolate* isolate) { SetIsolateUpForNode(isolate, IsolateSettingCategories::kErrorHandlers); SetIsolateUpForNode(isolate, IsolateSettingCategories::kMisc); } Isolate* NewIsolate(ArrayBufferAllocator* allocator, uv_loop_t* event_loop) { return NewIsolate(allocator, event_loop, GetMainThreadMultiIsolatePlatform()); } // TODO(joyeecheung): we may want to expose this, but then we need to be // careful about what we override in the params. Isolate* NewIsolate(Isolate::CreateParams* params, uv_loop_t* event_loop, MultiIsolatePlatform* platform) { Isolate* isolate = Isolate::Allocate(); if (isolate == nullptr) return nullptr; // Register the isolate on the platform before the isolate gets initialized, // so that the isolate can access the platform during initialization. platform->RegisterIsolate(isolate, event_loop); SetIsolateCreateParamsForNode(params); Isolate::Initialize(isolate, *params); SetIsolateUpForNode(isolate); return isolate; } Isolate* NewIsolate(ArrayBufferAllocator* allocator, uv_loop_t* event_loop, MultiIsolatePlatform* platform) { Isolate::CreateParams params; if (allocator != nullptr) params.array_buffer_allocator = allocator; return NewIsolate(¶ms, event_loop, platform); } IsolateData* CreateIsolateData(Isolate* isolate, uv_loop_t* loop, MultiIsolatePlatform* platform, ArrayBufferAllocator* allocator) { return new IsolateData(isolate, loop, platform, allocator); } void FreeIsolateData(IsolateData* isolate_data) { delete isolate_data; } Environment* CreateEnvironment(IsolateData* isolate_data, Local context, int argc, const char* const* argv, int exec_argc, const char* const* exec_argv) { Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); Context::Scope context_scope(context); // TODO(addaleax): This is a much better place for parsing per-Environment // options than the global parse call. std::vector args(argv, argv + argc); std::vector exec_args(exec_argv, exec_argv + exec_argc); // TODO(addaleax): Provide more sensible flags, in an embedder-accessible way. Environment* env = new Environment( isolate_data, context, args, exec_args, static_cast(Environment::kIsMainThread | Environment::kOwnsProcessState | Environment::kOwnsInspector)); env->InitializeLibuv(per_process::v8_is_profiling); if (env->RunBootstrapping().IsEmpty()) { return nullptr; } std::vector> parameters = { env->require_string(), FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; std::vector> arguments = { env->native_module_require(), env->NewFunctionTemplate(MarkBootstrapComplete) ->GetFunction(env->context()) .ToLocalChecked()}; if (ExecuteBootstrapper( env, "internal/bootstrap/environment", ¶meters, &arguments) .IsEmpty()) { return nullptr; } return env; } void FreeEnvironment(Environment* env) { env->RunCleanup(); delete env; } Environment* GetCurrentEnvironment(Local context) { return Environment::GetCurrent(context); } MultiIsolatePlatform* GetMainThreadMultiIsolatePlatform() { return per_process::v8_platform.Platform(); } MultiIsolatePlatform* CreatePlatform( int thread_pool_size, node::tracing::TracingController* tracing_controller) { return new NodePlatform(thread_pool_size, tracing_controller); } MultiIsolatePlatform* InitializeV8Platform(int thread_pool_size) { per_process::v8_platform.Initialize(thread_pool_size); return per_process::v8_platform.Platform(); } void FreePlatform(MultiIsolatePlatform* platform) { delete platform; } MaybeLocal GetPerContextExports(Local context) { Isolate* isolate = context->GetIsolate(); EscapableHandleScope handle_scope(isolate); Local global = context->Global(); Local key = Private::ForApi(isolate, FIXED_ONE_BYTE_STRING(isolate, "node:per_context_binding_exports")); Local existing_value; if (!global->GetPrivate(context, key).ToLocal(&existing_value)) return MaybeLocal(); if (existing_value->IsObject()) return handle_scope.Escape(existing_value.As()); Local exports = Object::New(isolate); if (context->Global()->SetPrivate(context, key, exports).IsNothing()) return MaybeLocal(); return handle_scope.Escape(exports); } Local NewContext(Isolate* isolate, Local object_template) { auto context = Context::New(isolate, nullptr, object_template); if (context.IsEmpty()) return context; if (!InitializeContext(context)) { return Local(); } InitializeContextRuntime(context); return context; } // This runs at runtime, regardless of whether the context // is created from a snapshot. void InitializeContextRuntime(Local context) { Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); // Delete `Intl.v8BreakIterator` // https://github.com/nodejs/node/issues/14909 Local intl_string = FIXED_ONE_BYTE_STRING(isolate, "Intl"); Local break_iter_string = FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator"); Local intl_v; if (context->Global()->Get(context, intl_string).ToLocal(&intl_v) && intl_v->IsObject()) { Local intl = intl_v.As(); intl->Delete(context, break_iter_string).FromJust(); } // Delete `Atomics.wake` // https://github.com/nodejs/node/issues/21219 Local atomics_string = FIXED_ONE_BYTE_STRING(isolate, "Atomics"); Local wake_string = FIXED_ONE_BYTE_STRING(isolate, "wake"); Local atomics_v; if (context->Global()->Get(context, atomics_string).ToLocal(&atomics_v) && atomics_v->IsObject()) { Local atomics = atomics_v.As(); atomics->Delete(context, wake_string).FromJust(); } } bool InitializeContext(Local context) { Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, True(isolate)); { // Run per-context JS files. Context::Scope context_scope(context); Local exports; Local primordials_string = FIXED_ONE_BYTE_STRING(isolate, "primordials"); Local global_string = FIXED_ONE_BYTE_STRING(isolate, "global"); Local exports_string = FIXED_ONE_BYTE_STRING(isolate, "exports"); // Create primordials first and make it available to per-context scripts. Local primordials = Object::New(isolate); if (!primordials->SetPrototype(context, Null(isolate)).FromJust() || !GetPerContextExports(context).ToLocal(&exports) || !exports->Set(context, primordials_string, primordials).FromJust()) { return false; } static const char* context_files[] = {"internal/per_context/primordials", "internal/per_context/domexception", nullptr}; for (const char** module = context_files; *module != nullptr; module++) { std::vector> parameters = { global_string, exports_string, primordials_string}; Local arguments[] = {context->Global(), exports, primordials}; MaybeLocal maybe_fn = native_module::NativeModuleEnv::LookupAndCompile( context, *module, ¶meters, nullptr); if (maybe_fn.IsEmpty()) { return false; } Local fn = maybe_fn.ToLocalChecked(); MaybeLocal result = fn->Call(context, Undefined(isolate), arraysize(arguments), arguments); // Execution failed during context creation. // TODO(joyeecheung): deprecate this signature and return a MaybeLocal. if (result.IsEmpty()) { return false; } } } return true; } uv_loop_t* GetCurrentEventLoop(Isolate* isolate) { HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); if (context.IsEmpty()) return nullptr; Environment* env = Environment::GetCurrent(context); if (env == nullptr) return nullptr; return env->event_loop(); } } // namespace node