#include
#if HAVE_OPENSSL
#include "crypto/crypto_util.h"
#endif // HAVE_OPENSSL
#include "env.h"
#include "env_properties.h"
#include "node.h"
#include "node_builtins.h"
#include "node_context_data.h"
#include "node_debug.h"
#include "node_errors.h"
#include "node_exit_code.h"
#include "node_internals.h"
#include "node_options-inl.h"
#include "node_platform.h"
#include "node_realm-inl.h"
#include "node_shadow_realm.h"
#include "node_snapshot_builder.h"
#include "node_v8_platform-inl.h"
#include "node_wasm_web_api.h"
#include "uv.h"
#ifdef NODE_ENABLE_VTUNE_PROFILING
#include "../deps/v8/third_party/vtune/v8-vtune.h"
#endif
#if HAVE_INSPECTOR
#include "inspector/worker_inspector.h" // ParentInspectorHandle
#endif
#include "v8-cppgc.h"
namespace node {
using errors::TryCatchScope;
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::CppHeap;
using v8::CppHeapCreateParams;
using v8::EscapableHandleScope;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::IsolateGroup;
using v8::Just;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Null;
using v8::Object;
using v8::ObjectTemplate;
using v8::Private;
using v8::PropertyDescriptor;
using v8::SealHandleScope;
using v8::String;
using v8::Value;
bool AllowWasmCodeGenerationCallback(Local context,
Local) {
Local wasm_code_gen =
context->GetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration);
return wasm_code_gen->IsUndefined() || wasm_code_gen->IsTrue();
}
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
DebugSealHandleScope scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
return env != nullptr &&
(env->is_main_thread() || !env->is_stopping()) &&
env->abort_on_uncaught_exception() &&
env->should_abort_on_uncaught_toggle()[0] &&
!env->inside_should_not_abort_on_uncaught_scope();
}
MaybeLocal PrepareStackTraceCallback(Local context,
Local exception,
Local trace) {
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) {
return exception->ToString(context).FromMaybe(Local());
}
Realm* realm = Realm::GetCurrent(context);
Local prepare;
if (realm != nullptr) {
// If we are in a Realm, call the realm specific prepareStackTrace callback
// to avoid passing the JS objects (the exception and trace) across the
// realm boundary with the `Error.prepareStackTrace` override.
prepare = realm->prepare_stack_trace_callback();
} else {
// The context is created with ContextifyContext, call the principal
// realm's prepareStackTrace callback.
prepare = env->principal_realm()->prepare_stack_trace_callback();
}
if (prepare.IsEmpty()) {
return exception->ToString(context).FromMaybe(Local());
}
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) {
void* ret;
COUNT_GENERIC_USAGE("NodeArrayBufferAllocator.Allocate.ZeroFilled");
ret = allocator_->Allocate(size);
if (ret != nullptr) [[likely]] {
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
}
return ret;
}
void* NodeArrayBufferAllocator::AllocateUninitialized(size_t size) {
COUNT_GENERIC_USAGE("NodeArrayBufferAllocator.Allocate.Uninitialized");
void* ret = allocator_->AllocateUninitialized(size);
if (ret != nullptr) [[likely]] {
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
}
return ret;
}
void NodeArrayBufferAllocator::Free(void* data, size_t size) {
total_mem_usage_.fetch_sub(size, std::memory_order_relaxed);
allocator_->Free(data, 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::RegisterPointer(void* data, size_t size) {
Mutex::ScopedLock lock(mutex_);
NodeArrayBufferAllocator::RegisterPointer(data, size);
RegisterPointerInternal(data, size);
}
void DebuggingArrayBufferAllocator::UnregisterPointer(void* data, size_t size) {
Mutex::ScopedLock lock(mutex_);
NodeArrayBufferAllocator::UnregisterPointer(data, size);
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 &&
params->constraints.max_old_generation_size_in_bytes() == 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);
}
#ifdef NODE_ENABLE_VTUNE_PROFILING
params->code_event_handler = vTune::GetVtuneCodeEventHandler();
#endif
}
void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
if (s.flags & MESSAGE_LISTENER_WITH_ERROR_LEVEL)
isolate->AddMessageListenerWithErrorLevel(
errors::PerIsolateMessageListener,
Isolate::MessageErrorLevel::kMessageError |
Isolate::MessageErrorLevel::kMessageWarning);
auto* abort_callback = s.should_abort_on_uncaught_exception_callback ?
s.should_abort_on_uncaught_exception_callback :
ShouldAbortOnUncaughtException;
isolate->SetAbortOnUncaughtExceptionCallback(abort_callback);
auto* fatal_error_cb = s.fatal_error_callback ?
s.fatal_error_callback : OnFatalError;
isolate->SetFatalErrorHandler(fatal_error_cb);
auto* oom_error_cb =
s.oom_error_callback ? s.oom_error_callback : OOMErrorHandler;
isolate->SetOOMErrorHandler(oom_error_cb);
if ((s.flags & SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK) == 0) {
auto* prepare_stack_trace_cb = s.prepare_stack_trace_callback ?
s.prepare_stack_trace_callback : PrepareStackTraceCallback;
isolate->SetPrepareStackTraceCallback(prepare_stack_trace_cb);
}
}
void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
isolate->SetMicrotasksPolicy(s.policy);
auto* allow_wasm_codegen_cb = s.allow_wasm_code_generation_callback ?
s.allow_wasm_code_generation_callback : AllowWasmCodeGenerationCallback;
isolate->SetAllowWasmCodeGenerationCallback(allow_wasm_codegen_cb);
auto* modify_code_generation_from_strings_callback =
ModifyCodeGenerationFromStrings;
if (s.modify_code_generation_from_strings_callback != nullptr) {
modify_code_generation_from_strings_callback =
s.modify_code_generation_from_strings_callback;
}
isolate->SetModifyCodeGenerationFromStringsCallback(
modify_code_generation_from_strings_callback);
isolate->SetWasmStreamingCallback(wasm_web_api::StartStreamingCompilation);
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
if (per_process::cli_options->get_per_isolate_options()
->experimental_shadow_realm) {
isolate->SetHostCreateShadowRealmContextCallback(
shadow_realm::HostCreateShadowRealmContextCallback);
}
if ((s.flags & SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK) == 0) {
auto* promise_reject_cb = s.promise_reject_callback ?
s.promise_reject_callback : PromiseRejectCallback;
isolate->SetPromiseRejectCallback(promise_reject_cb);
}
if (s.flags & DETAILED_SOURCE_POSITIONS_FOR_PROFILING)
v8::CpuProfiler::UseDetailedSourcePositionsForProfiling(isolate);
}
void SetIsolateUpForNode(v8::Isolate* isolate,
const IsolateSettings& settings) {
Isolate::Scope isolate_scope(isolate);
SetIsolateErrorHandlers(isolate, settings);
SetIsolateMiscHandlers(isolate, settings);
}
void SetIsolateUpForNode(v8::Isolate* isolate) {
IsolateSettings settings;
SetIsolateUpForNode(isolate, settings);
}
//
IsolateGroup GetOrCreateIsolateGroup() {
// When pointer compression is disabled, we cannot create new groups,
// in which case we'll always return the default.
if (IsolateGroup::CanCreateNewGroups()) {
return IsolateGroup::Create();
}
return IsolateGroup::GetDefault();
}
// 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,
const SnapshotData* snapshot_data,
const IsolateSettings& settings) {
IsolateGroup group = GetOrCreateIsolateGroup();
Isolate* isolate = Isolate::Allocate(group);
if (isolate == nullptr) return nullptr;
if (snapshot_data != nullptr) {
SnapshotBuilder::InitializeIsolateParams(snapshot_data, params);
}
{
// Because it uses a shared readonly-heap, V8 requires all snapshots used
// for creating Isolates to be identical. This isn't really memory-safe
// but also otherwise just doesn't work, and the only real alternative
// is disabling shared-readonly-heap mode altogether.
static Isolate::CreateParams first_params = *params;
params->snapshot_blob = first_params.snapshot_blob;
params->external_references = first_params.external_references;
}
// 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);
// Ensure that there is always a CppHeap.
if (settings.cpp_heap == nullptr) {
params->cpp_heap =
CppHeap::Create(platform, CppHeapCreateParams{{}}).release();
} else {
params->cpp_heap = settings.cpp_heap;
}
SetIsolateCreateParamsForNode(params);
Isolate::Initialize(isolate, *params);
Isolate::Scope isolate_scope(isolate);
if (snapshot_data == nullptr) {
// If in deserialize mode, delay until after the deserialization is
// complete.
SetIsolateUpForNode(isolate, settings);
} else {
SetIsolateMiscHandlers(isolate, settings);
}
return isolate;
}
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator != nullptr) params.array_buffer_allocator = allocator;
return NewIsolate(¶ms,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
Isolate* NewIsolate(std::shared_ptr allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const EmbedderSnapshotData* snapshot_data,
const IsolateSettings& settings) {
Isolate::CreateParams params;
if (allocator) params.array_buffer_allocator_shared = allocator;
return NewIsolate(¶ms,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}
IsolateData* CreateIsolateData(
Isolate* isolate,
uv_loop_t* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator,
const EmbedderSnapshotData* embedder_snapshot_data) {
return IsolateData::CreateIsolateData(
isolate, loop, platform, allocator, embedder_snapshot_data);
}
void FreeIsolateData(IsolateData* isolate_data) {
delete isolate_data;
}
// Hide the internal handle class from the public API.
#if HAVE_INSPECTOR
struct InspectorParentHandleImpl : public InspectorParentHandle {
std::unique_ptr<:parentinspectorhandle> impl;
explicit InspectorParentHandleImpl(
std::unique_ptr<:parentinspectorhandle>&& impl)
: impl(std::move(impl)) {}
};
#endif
Environment* CreateEnvironment(
IsolateData* isolate_data,
Local context,
const std::vector<:string>& args,
const std::vector<:string>& exec_args,
EnvironmentFlags::Flags flags,
ThreadId thread_id,
std::unique_ptr inspector_parent_handle) {
return CreateEnvironment(isolate_data,
context,
args,
exec_args,
flags,
thread_id,
std::move(inspector_parent_handle),
{});
}
Environment* CreateEnvironment(
IsolateData* isolate_data,
Local context,
const std::vector<:string>& args,
const std::vector<:string>& exec_args,
EnvironmentFlags::Flags flags,
ThreadId thread_id,
std::unique_ptr inspector_parent_handle,
std::string_view thread_name) {
Isolate* isolate = isolate_data->isolate();
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
const bool use_snapshot = context.IsEmpty();
const EnvSerializeInfo* env_snapshot_info = nullptr;
if (use_snapshot) {
CHECK_NOT_NULL(isolate_data->snapshot_data());
env_snapshot_info = &isolate_data->snapshot_data()->env_info;
}
// TODO(addaleax): This is a much better place for parsing per-Environment
// options than the global parse call.
Environment* env = new Environment(isolate_data,
isolate,
args,
exec_args,
env_snapshot_info,
flags,
thread_id,
thread_name);
CHECK_NOT_NULL(env);
if (use_snapshot) {
context = Context::FromSnapshot(isolate,
SnapshotData::kNodeMainContextIndex,
v8::DeserializeInternalFieldsCallback(
DeserializeNodeInternalFields, env),
nullptr,
MaybeLocal(),
nullptr,
v8::DeserializeContextDataCallback(
DeserializeNodeContextData, env))
.ToLocalChecked();
CHECK(!context.IsEmpty());
Context::Scope context_scope(context);
if (InitializeContextRuntime(context).IsNothing()) {
FreeEnvironment(env);
return nullptr;
}
SetIsolateErrorHandlers(isolate, {});
}
Context::Scope context_scope(context);
env->InitializeMainContext(context, env_snapshot_info);
#if HAVE_INSPECTOR
if (env->should_create_inspector()) {
if (inspector_parent_handle) {
env->InitializeInspector(std::move(
static_cast(inspector_parent_handle.get())
->impl));
} else {
env->InitializeInspector({});
}
}
#endif
if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) {
FreeEnvironment(env);
return nullptr;
}
return env;
}
void FreeEnvironment(Environment* env) {
Isolate* isolate = env->isolate();
Isolate::DisallowJavascriptExecutionScope disallow_js(isolate,
Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
{
HandleScope handle_scope(isolate); // For env->context().
Context::Scope context_scope(env->context());
SealHandleScope seal_handle_scope(isolate);
// Set the flag in accordance with the DisallowJavascriptExecutionScope
// above.
env->set_can_call_into_js(false);
env->set_stopping(true);
env->stop_sub_worker_contexts();
env->RunCleanup();
RunAtExit(env);
}
delete env;
}
NODE_EXTERN std::unique_ptr GetInspectorParentHandle(
Environment* env,
ThreadId thread_id,
const char* url) {
return GetInspectorParentHandle(env, thread_id, url, "");
}
NODE_EXTERN std::unique_ptr GetInspectorParentHandle(
Environment* env, ThreadId thread_id, const char* url, const char* name) {
if (url == nullptr) url = "";
if (name == nullptr) name = "";
std::string_view url_view(url);
std::string_view name_view(name);
return GetInspectorParentHandle(env, thread_id, url_view, name_view);
}
NODE_EXTERN std::unique_ptr GetInspectorParentHandle(
Environment* env,
ThreadId thread_id,
std::string_view url,
std::string_view name) {
CHECK_NOT_NULL(env);
CHECK_NE(thread_id.id, static_cast(-1));
if (!env->should_create_inspector()) {
return nullptr;
}
#if HAVE_INSPECTOR
return std::make_unique(
env->inspector_agent()->GetParentHandle(thread_id.id, url, name));
#else
return {};
#endif
}
MaybeLocal LoadEnvironment(Environment* env,
StartExecutionCallbackWithModule cb,
EmbedderPreloadCallback preload) {
env->InitializeLibuv();
env->InitializeDiagnostics();
if (preload) {
env->set_embedder_preload(std::move(preload));
}
env->InitializeCompileCache();
return StartExecution(env, cb);
}
struct StartExecutionCallbackInfoWithModule::Impl {
Environment* env = nullptr;
Local