#include "env.h"
#include "async_wrap.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "diagnosticfilename-inl.h"
#include "memory_tracker-inl.h"
#include "module_wrap.h"
#include "node_buffer.h"
#include "node_context_data.h"
#include "node_contextify.h"
#include "node_errors.h"
#include "node_internals.h"
#include "node_options-inl.h"
#include "node_process-inl.h"
#include "node_shadow_realm.h"
#include "node_snapshotable.h"
#include "node_v8_platform-inl.h"
#include "node_worker.h"
#include "req_wrap-inl.h"
#include "stream_base.h"
#include "tracing/agent.h"
#include "tracing/traced_value.h"
#include "util-inl.h"
#include "v8-cppgc.h"
#include "v8-profiler.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace node {
using errors::TryCatchScope;
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::CppHeap;
using v8::CppHeapCreateParams;
using v8::EmbedderGraph;
using v8::EscapableHandleScope;
using v8::Function;
using v8::HandleScope;
using v8::HeapProfiler;
using v8::HeapSpaceStatistics;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Private;
using v8::Promise;
using v8::PromiseHookType;
using v8::Script;
using v8::SnapshotCreator;
using v8::StackTrace;
using v8::String;
using v8::Symbol;
using v8::TracingController;
using v8::TryCatch;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
using v8::WrapperDescriptor;
using worker::Worker;
int const ContextEmbedderTag::kNodeContextTag = 0x6e6f64;
void* const ContextEmbedderTag::kNodeContextTagPtr = const_cast(
static_cast(&ContextEmbedderTag::kNodeContextTag));
void AsyncHooks::ResetPromiseHooks(Local init,
Local before,
Local after,
Local resolve) {
js_promise_hooks_[0].Reset(env()->isolate(), init);
js_promise_hooks_[1].Reset(env()->isolate(), before);
js_promise_hooks_[2].Reset(env()->isolate(), after);
js_promise_hooks_[3].Reset(env()->isolate(), resolve);
}
void Environment::ResetPromiseHooks(Local init,
Local before,
Local after,
Local resolve) {
async_hooks()->ResetPromiseHooks(init, before, after, resolve);
for (auto it = contexts_.begin(); it != contexts_.end(); it++) {
if (it->IsEmpty()) {
contexts_.erase(it--);
continue;
}
PersistentToLocal::Weak(isolate_, *it)
->SetPromiseHooks(init, before, after, resolve);
}
}
// Remember to keep this code aligned with pushAsyncContext() in JS.
void AsyncHooks::push_async_context(double async_id,
double trigger_async_id,
Local resource) {
// Since async_hooks is experimental, do only perform the check
// when async_hooks is enabled.
if (fields_[kCheck] > 0) {
CHECK_GE(async_id, -1);
CHECK_GE(trigger_async_id, -1);
}
uint32_t offset = fields_[kStackLength];
if (offset * 2 >= async_ids_stack_.Length()) grow_async_ids_stack();
async_ids_stack_[2 * offset] = async_id_fields_[kExecutionAsyncId];
async_ids_stack_[2 * offset + 1] = async_id_fields_[kTriggerAsyncId];
fields_[kStackLength] += 1;
async_id_fields_[kExecutionAsyncId] = async_id;
async_id_fields_[kTriggerAsyncId] = trigger_async_id;
#ifdef DEBUG
for (uint32_t i = offset; i < native_execution_async_resources_.size(); i++)
CHECK(native_execution_async_resources_[i].IsEmpty());
#endif
// When this call comes from JS (as a way of increasing the stack size),
// `resource` will be empty, because JS caches these values anyway.
if (!resource.IsEmpty()) {
native_execution_async_resources_.resize(offset + 1);
// Caveat: This is a v8::Local<> assignment, we do not keep a v8::Global<>!
native_execution_async_resources_[offset] = resource;
}
}
// Remember to keep this code aligned with popAsyncContext() in JS.
bool AsyncHooks::pop_async_context(double async_id) {
// In case of an exception then this may have already been reset, if the
// stack was multiple MakeCallback()'s deep.
if (UNLIKELY(fields_[kStackLength] == 0)) return false;
// Ask for the async_id to be restored as a check that the stack
// hasn't been corrupted.
if (UNLIKELY(fields_[kCheck] > 0 &&
async_id_fields_[kExecutionAsyncId] != async_id)) {
FailWithCorruptedAsyncStack(async_id);
}
uint32_t offset = fields_[kStackLength] - 1;
async_id_fields_[kExecutionAsyncId] = async_ids_stack_[2 * offset];
async_id_fields_[kTriggerAsyncId] = async_ids_stack_[2 * offset + 1];
fields_[kStackLength] = offset;
if (LIKELY(offset < native_execution_async_resources_.size() &&
!native_execution_async_resources_[offset].IsEmpty())) {
#ifdef DEBUG
for (uint32_t i = offset + 1; i < native_execution_async_resources_.size();
i++) {
CHECK(native_execution_async_resources_[i].IsEmpty());
}
#endif
native_execution_async_resources_.resize(offset);
if (native_execution_async_resources_.size() <
native_execution_async_resources_.capacity() / 2 &&
native_execution_async_resources_.size() > 16) {
native_execution_async_resources_.shrink_to_fit();
}
}
if (UNLIKELY(js_execution_async_resources()->Length() > offset)) {
HandleScope handle_scope(env()->isolate());
USE(js_execution_async_resources()->Set(
env()->context(),
env()->length_string(),
Integer::NewFromUnsigned(env()->isolate(), offset)));
}
return fields_[kStackLength] > 0;
}
void AsyncHooks::clear_async_id_stack() {
if (!js_execution_async_resources_.IsEmpty() && env()->can_call_into_js()) {
Isolate* isolate = env()->isolate();
HandleScope handle_scope(isolate);
USE(PersistentToLocal::Strong(js_execution_async_resources_)
->Set(env()->context(),
env()->length_string(),
Integer::NewFromUnsigned(isolate, 0)));
}
native_execution_async_resources_.clear();
native_execution_async_resources_.shrink_to_fit();
async_id_fields_[kExecutionAsyncId] = 0;
async_id_fields_[kTriggerAsyncId] = 0;
fields_[kStackLength] = 0;
}
void AsyncHooks::InstallPromiseHooks(Local ctx) {
ctx->SetPromiseHooks(js_promise_hooks_[0].IsEmpty()
? Local()
: PersistentToLocal::Strong(js_promise_hooks_[0]),
js_promise_hooks_[1].IsEmpty()
? Local()
: PersistentToLocal::Strong(js_promise_hooks_[1]),
js_promise_hooks_[2].IsEmpty()
? Local()
: PersistentToLocal::Strong(js_promise_hooks_[2]),
js_promise_hooks_[3].IsEmpty()
? Local()
: PersistentToLocal::Strong(js_promise_hooks_[3]));
}
void Environment::TrackContext(Local context) {
size_t id = contexts_.size();
contexts_.resize(id + 1);
contexts_[id].Reset(isolate_, context);
contexts_[id].SetWeak();
}
void Environment::UntrackContext(Local context) {
HandleScope handle_scope(isolate_);
contexts_.erase(std::remove_if(contexts_.begin(),
contexts_.end(),
[&](auto&& el) { return el.IsEmpty(); }),
contexts_.end());
for (auto it = contexts_.begin(); it != contexts_.end(); it++) {
Local saved_context = PersistentToLocal::Weak(isolate_, *it);
if (saved_context == context) {
it->Reset();
contexts_.erase(it);
break;
}
}
}
void Environment::TrackShadowRealm(shadow_realm::ShadowRealm* realm) {
shadow_realms_.insert(realm);
}
void Environment::UntrackShadowRealm(shadow_realm::ShadowRealm* realm) {
shadow_realms_.erase(realm);
}
AsyncHooks::DefaultTriggerAsyncIdScope::DefaultTriggerAsyncIdScope(
Environment* env, double default_trigger_async_id)
: async_hooks_(env->async_hooks()) {
if (env->async_hooks()->fields()[AsyncHooks::kCheck] > 0) {
CHECK_GE(default_trigger_async_id, 0);
}
old_default_trigger_async_id_ =
async_hooks_->async_id_fields()[AsyncHooks::kDefaultTriggerAsyncId];
async_hooks_->async_id_fields()[AsyncHooks::kDefaultTriggerAsyncId] =
default_trigger_async_id;
}
AsyncHooks::DefaultTriggerAsyncIdScope::~DefaultTriggerAsyncIdScope() {
async_hooks_->async_id_fields()[AsyncHooks::kDefaultTriggerAsyncId] =
old_default_trigger_async_id_;
}
AsyncHooks::DefaultTriggerAsyncIdScope::DefaultTriggerAsyncIdScope(
AsyncWrap* async_wrap)
: DefaultTriggerAsyncIdScope(async_wrap->env(),
async_wrap->get_async_id()) {}
std::ostream& operator<<(std::ostream& output,
const std::vector& v) {
output << "{ ";
for (const SnapshotIndex i : v) {
output << i << ", ";
}
output << " }";
return output;
}
std::ostream& operator<<(std::ostream& output,
const IsolateDataSerializeInfo& i) {
output << "{\n"
<< "// -- primitive begins --\n"
<< i.primitive_values << ",\n"
<< "// -- primitive ends --\n"
<< "// -- template_values begins --\n"
<< i.template_values << ",\n"
<< "// -- template_values ends --\n"
<< "}";
return output;
}
std::ostream& operator<<(std::ostream& output, const SnapshotFlags& flags) {
output << "static_cast(" << static_cast(flags)
<< ")";
return output;
}
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) {
output << "{\n"
<< " "
<< (i.type == SnapshotMetadata::Type::kDefault
? "SnapshotMetadata::Type::kDefault"
: "SnapshotMetadata::Type::kFullyCustomized")
<< ", // type\n"
<< " \"" << i.node_version << "\", // node_version\n"
<< " \"" << i.node_arch << "\", // node_arch\n"
<< " \"" << i.node_platform << "\", // node_platform\n"
<< " " << i.v8_cache_version_tag << ", // v8_cache_version_tag\n"
<< " " << i.flags << ", // flags\n"
<< "}";
return output;
}
IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) {
Isolate* isolate = creator->GetIsolate();
IsolateDataSerializeInfo info;
HandleScope handle_scope(isolate);
// XXX(joyeecheung): technically speaking, the indexes here should be
// consecutive and we could just return a range instead of an array,
// but that's not part of the V8 API contract so we use an array
// just to be safe.
#define VP(PropertyName, StringValue) V(Private, PropertyName)
#define VY(PropertyName, StringValue) V(Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(String, PropertyName)
#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName)
#define V(TypeName, PropertyName) \
info.primitive_values.push_back( \
creator->AddData(PropertyName##_.Get(isolate)));
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_SYMBOL_PROPERTIES(VY)
PER_ISOLATE_STRING_PROPERTIES(VS)
PER_REALM_STRONG_PERSISTENT_VALUES(VR)
#undef V
#undef VR
#undef VY
#undef VS
#undef VP
for (size_t i = 0; i < AsyncWrap::PROVIDERS_LENGTH; i++)
info.primitive_values.push_back(creator->AddData(async_wrap_provider(i)));
uint32_t id = 0;
#define VM(PropertyName) V(PropertyName##_binding_template, ObjectTemplate)
#define V(PropertyName, TypeName) \
do { \
Local field = PropertyName(); \
if (!field.IsEmpty()) { \
size_t index = creator->AddData(field); \
info.template_values.push_back({#PropertyName, id, index}); \
} \
id++; \
} while (0);
PER_ISOLATE_TEMPLATE_PROPERTIES(V)
NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM)
#undef V
return info;
}
void IsolateData::DeserializeProperties(const IsolateDataSerializeInfo* info) {
size_t i = 0;
v8::Isolate::Scope isolate_scope(isolate_);
HandleScope handle_scope(isolate_);
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
fprintf(stderr, "deserializing IsolateDataSerializeInfo...\n");
std::cerr << *info << "\n";
}
#define VP(PropertyName, StringValue) V(Private, PropertyName)
#define VY(PropertyName, StringValue) V(Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(String, PropertyName)
#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName)
#define V(TypeName, PropertyName) \
do { \
MaybeLocal maybe_field = \
isolate_->GetDataFromSnapshotOnce( \
info->primitive_values[i++]); \
Local field; \
if (!maybe_field.ToLocal(&field)) { \
fprintf(stderr, "Failed to deserialize " #PropertyName "\n"); \
} \
PropertyName##_.Set(isolate_, field); \
} while (0);
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
PER_ISOLATE_SYMBOL_PROPERTIES(VY)
PER_ISOLATE_STRING_PROPERTIES(VS)
PER_REALM_STRONG_PERSISTENT_VALUES(VR)
#undef V
#undef VR
#undef VY
#undef VS
#undef VP
for (size_t j = 0; j < AsyncWrap::PROVIDERS_LENGTH; j++) {
MaybeLocal maybe_field =
isolate_->GetDataFromSnapshotOnce(info->primitive_values[i++]);
Local field;
if (!maybe_field.ToLocal(&field)) {
fprintf(stderr, "Failed to deserialize AsyncWrap provider %zu\n", j);
}
async_wrap_providers_[j].Set(isolate_, field);
}
const std::vector& values = info->template_values;
i = 0; // index to the array
uint32_t id = 0;
#define VM(PropertyName) V(PropertyName##_binding_template, ObjectTemplate)
#define V(PropertyName, TypeName) \
do { \
if (values.size() > i && id == values[i].id) { \
const PropInfo& d = values[i]; \
DCHECK_EQ(d.name, #PropertyName); \
MaybeLocal maybe_field = \
isolate_->GetDataFromSnapshotOnce(d.index); \
Local field; \
if (!maybe_field.ToLocal(&field)) { \
fprintf(stderr, \
"Failed to deserialize isolate data template " #PropertyName \
"\n"); \
} \
set_##PropertyName(field); \
i++; \
} \
id++; \
} while (0);
PER_ISOLATE_TEMPLATE_PROPERTIES(V);
NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM);
#undef V
}
void IsolateData::CreateProperties() {
// Create string and private symbol properties as internalized one byte
// strings after the platform is properly initialized.
//
// Internalized because it makes property lookups a little faster and
// because the string is created in the old space straight away. It's going
// to end up in the old space sooner or later anyway but now it doesn't go
// through v8::Eternal's new space handling first.
//
// One byte because our strings are ASCII and we can safely skip V8's UTF-8
// decoding step.
v8::Isolate::Scope isolate_scope(isolate_);
HandleScope handle_scope(isolate_);
#define V(PropertyName, StringValue) \
PropertyName##_.Set( \
isolate_, \
Private::New(isolate_, \
String::NewFromOneByte( \
isolate_, \
reinterpret_cast(StringValue), \
NewStringType::kInternalized, \
sizeof(StringValue) - 1) \
.ToLocalChecked()));
PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V)
#undef V
#define V(PropertyName, TypeName) \
per_realm_##PropertyName##_.Set( \
isolate_, \
Private::New( \
isolate_, \
String::NewFromOneByte( \
isolate_, \
reinterpret_cast("per_realm_" #PropertyName), \
NewStringType::kInternalized, \
sizeof("per_realm_" #PropertyName) - 1) \
.ToLocalChecked()));
PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V
#define V(PropertyName, StringValue) \
PropertyName##_.Set( \
isolate_, \
Symbol::New(isolate_, \
String::NewFromOneByte( \
isolate_, \
reinterpret_cast(StringValue), \
NewStringType::kInternalized, \
sizeof(StringValue) - 1) \
.ToLocalChecked()));
PER_ISOLATE_SYMBOL_PROPERTIES(V)
#undef V
#define V(PropertyName, StringValue) \
PropertyName##_.Set( \
isolate_, \
String::NewFromOneByte(isolate_, \
reinterpret_cast(StringValue), \
NewStringType::kInternalized, \
sizeof(StringValue) - 1) \
.ToLocalChecked());
PER_ISOLATE_STRING_PROPERTIES(V)
#undef V
// Create all the provider strings that will be passed to JS. Place them in
// an array so the array index matches the PROVIDER id offset. This way the
// strings can be retrieved quickly.
#define V(Provider) \
async_wrap_providers_[AsyncWrap::PROVIDER_ ## Provider].Set( \
isolate_, \
String::NewFromOneByte( \
isolate_, \
reinterpret_cast(#Provider), \
NewStringType::kInternalized, \
sizeof(#Provider) - 1).ToLocalChecked());
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
Local templ = ObjectTemplate::New(isolate());
templ->SetInternalFieldCount(BaseObject::kInternalFieldCount);
set_binding_data_default_template(templ);
binding::CreateInternalBindingTemplates(this);
contextify::ContextifyContext::InitializeGlobalTemplates(this);
CreateEnvProxyTemplate(this);
}
constexpr uint16_t kDefaultCppGCEmebdderID = 0x90de;
Mutex IsolateData::isolate_data_mutex_;
std::unordered_map>
IsolateData::wrapper_data_map_;
IsolateData::IsolateData(Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* node_allocator,
const SnapshotData* snapshot_data)
: isolate_(isolate),
event_loop_(event_loop),
node_allocator_(node_allocator == nullptr ? nullptr
: node_allocator->GetImpl()),
platform_(platform),
snapshot_data_(snapshot_data) {
options_.reset(
new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
v8::CppHeap* cpp_heap = isolate->GetCppHeap();
uint16_t cppgc_id = kDefaultCppGCEmebdderID;
if (cpp_heap != nullptr) {
// The general convention of the wrappable layout for cppgc in the
// ecosystem is:
// [ 0 ] -> embedder id
// [ 1 ] -> wrappable instance
// If the Isolate includes a CppHeap attached by another embedder,
// And if they also use the field 0 for the ID, we DCHECK that
// the layout matches our layout, and record the embedder ID for cppgc
// to avoid accidentally enabling cppgc on non-cppgc-managed wrappers .
v8::WrapperDescriptor descriptor = cpp_heap->wrapper_descriptor();
if (descriptor.wrappable_type_index == BaseObject::kEmbedderType) {
cppgc_id = descriptor.embedder_id_for_garbage_collected;
DCHECK_EQ(descriptor.wrappable_instance_index, BaseObject::kSlot);
}
// If the CppHeap uses the slot we use to put non-cppgc-traced BaseObject
// for embedder ID, V8 could accidentally enable cppgc on them. So
// safe guard against this.
DCHECK_NE(descriptor.wrappable_type_index, BaseObject::kSlot);
} else {
cpp_heap_ = CppHeap::Create(
platform,
CppHeapCreateParams{
{},
WrapperDescriptor(
BaseObject::kEmbedderType, BaseObject::kSlot, cppgc_id)});
isolate->AttachCppHeap(cpp_heap_.get());
}
// We do not care about overflow since we just want this to be different
// from the cppgc id.
uint16_t non_cppgc_id = cppgc_id + 1;
{
// GC could still be run after the IsolateData is destroyed, so we store
// the ids in a static map to ensure pointers to them are still valid
// then. In practice there should be very few variants of the cppgc id
// in one process so the size of this map should be very small.
node::Mutex::ScopedLock lock(isolate_data_mutex_);
auto it = wrapper_data_map_.find(cppgc_id);
if (it == wrapper_data_map_.end()) {
auto pair = wrapper_data_map_.emplace(
cppgc_id, new PerIsolateWrapperData{cppgc_id, non_cppgc_id});
it = pair.first;
}
wrapper_data_ = it->second.get();
}
if (snapshot_data == nullptr) {
CreateProperties();
} else {
DeserializeProperties(&snapshot_data->isolate_data_info);
}
}
IsolateData::~IsolateData() {
if (cpp_heap_ != nullptr) {
// The CppHeap must be detached before being terminated.
isolate_->DetachCppHeap();
cpp_heap_->Terminate();
}
}
// Public API
void SetCppgcReference(Isolate* isolate,
Local object,
void* wrappable) {
IsolateData::SetCppgcReference(isolate, object, wrappable);
}
void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
#define V(PropertyName, StringValue) \
tracker->TrackField(#PropertyName, PropertyName());
PER_ISOLATE_SYMBOL_PROPERTIES(V)
PER_ISOLATE_STRING_PROPERTIES(V)
#undef V
tracker->TrackField("async_wrap_providers", async_wrap_providers_);
if (node_allocator_ != nullptr) {
tracker->TrackFieldWithSize(
"node_allocator", sizeof(*node_allocator_), "NodeArrayBufferAllocator");
}
tracker->TrackFieldWithSize(
"platform", sizeof(*platform_), "MultiIsolatePlatform");
// TODO(joyeecheung): implement MemoryRetainer in the option classes.
}
void TrackingTraceStateObserver::UpdateTraceCategoryState() {
if (!env_->owns_process_state() || !env_->can_call_into_js()) {
// Ideally, we’d have a consistent story that treats all threads/Environment
// instances equally here. However, tracing is essentially global, and this
// callback is called from whichever thread calls `StartTracing()` or
// `StopTracing()`. The only way to do this in a threadsafe fashion
// seems to be only tracking this from the main thread, and only allowing
// these state modifications from the main thread.
return;
}
if (env_->principal_realm() == nullptr) {
return;
}
bool async_hooks_enabled = (*(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACING_CATEGORY_NODE1(async_hooks)))) != 0;
Isolate* isolate = env_->isolate();
HandleScope handle_scope(isolate);
Local cb = env_->trace_category_state_function();
if (cb.IsEmpty())
return;
TryCatchScope try_catch(env_);
try_catch.SetVerbose(true);
Local args[] = {Boolean::New(isolate, async_hooks_enabled)};
USE(cb->Call(env_->context(), Undefined(isolate), arraysize(args), args));
}
void Environment::AssignToContext(Local context,
Realm* realm,
const ContextInfo& info) {
context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kEnvironment,
this);
context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kRealm, realm);
// ContextifyContexts will update this to a pointer to the native object.
context->SetAlignedPointerInEmbedderData(
ContextEmbedderIndex::kContextifyContext, nullptr);
// This must not be done before other context fields are initialized.
ContextEmbedderTag::TagNodeContext(context);
#if HAVE_INSPECTOR
inspector_agent()->ContextCreated(context, info);
#endif // HAVE_INSPECTOR
this->async_hooks()->InstallPromiseHooks(context);
TrackContext(context);
}
void Environment::UnassignFromContext(Local context) {
if (!context.IsEmpty()) {
context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kEnvironment,
nullptr);
context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kRealm,
nullptr);
context->SetAlignedPointerInEmbedderData(
ContextEmbedderIndex::kContextifyContext, nullptr);
}
UntrackContext(context);
}
void Environment::TryLoadAddon(
const char* filename,
int flags,
const std::function& was_loaded) {
loaded_addons_.emplace_back(filename, flags);
if (!was_loaded(&loaded_addons_.back())) {
loaded_addons_.pop_back();
}
}
std::string Environment::GetCwd(const std::string& exec_path) {
char cwd[PATH_MAX_BYTES];
size_t size = PATH_MAX_BYTES;
const int err = uv_cwd(cwd, &size);
if (err == 0) {
CHECK_GT(size, 0);
return cwd;
}
// This can fail if the cwd is deleted. In that case, fall back to
// exec_path.
return exec_path.substr(0, exec_path.find_last_of(kPathSeparator));
}
void Environment::add_refs(int64_t diff) {
task_queues_async_refs_ += diff;
CHECK_GE(task_queues_async_refs_, 0);
if (task_queues_async_refs_ == 0)
uv_unref(reinterpret_cast(&task_queues_async_));
else
uv_ref(reinterpret_cast(&task_queues_async_));
}
uv_buf_t Environment::allocate_managed_buffer(const size_t suggested_size) {
NoArrayBufferZeroFillScope no_zero_fill_scope(isolate_data());
std::unique_ptr bs =
v8::ArrayBuffer::NewBackingStore(isolate(), suggested_size);
uv_buf_t buf = uv_buf_init(static_cast(bs->Data()), bs->ByteLength());
released_allocated_buffers_.emplace(buf.base, std::move(bs));
return buf;
}
std::unique_ptr Environment::release_managed_buffer(
const uv_buf_t& buf) {
std::unique_ptr bs;
if (buf.base != nullptr) {
auto it = released_allocated_buffers_.find(buf.base);
CHECK_NE(it, released_allocated_buffers_.end());
bs = std::move(it->second);
released_allocated_buffers_.erase(it);
}
return bs;
}
std::string Environment::GetExecPath(const std::vector& argv) {
char exec_path_buf[2 * PATH_MAX];
size_t exec_path_len = sizeof(exec_path_buf);
std::string exec_path;
if (uv_exepath(exec_path_buf, &exec_path_len) == 0) {
exec_path = std::string(exec_path_buf, exec_path_len);
} else if (argv.size() > 0) {
exec_path = argv[0];
}
// On OpenBSD process.execPath will be relative unless we
// get the full path before process.execPath is used.
#if defined(__OpenBSD__)
uv_fs_t req;
req.ptr = nullptr;
if (0 ==
uv_fs_realpath(nullptr, &req, exec_path.c_str(), nullptr)) {
CHECK_NOT_NULL(req.ptr);
exec_path = std::string(static_cast(req.ptr));
}
uv_fs_req_cleanup(&req);
#endif
return exec_path;
}
Environment::Environment(IsolateData* isolate_data,
Isolate* isolate,
const std::vector& args,
const std::vector& exec_args,
const EnvSerializeInfo* env_info,
EnvironmentFlags::Flags flags,
ThreadId thread_id)
: isolate_(isolate),
isolate_data_(isolate_data),
async_hooks_(isolate, MAYBE_FIELD_PTR(env_info, async_hooks)),
immediate_info_(isolate, MAYBE_FIELD_PTR(env_info, immediate_info)),
timeout_info_(isolate_, 1, MAYBE_FIELD_PTR(env_info, timeout_info)),
tick_info_(isolate, MAYBE_FIELD_PTR(env_info, tick_info)),
timer_base_(uv_now(isolate_data->event_loop())),
exec_argv_(exec_args),
argv_(args),
exec_path_(Environment::GetExecPath(args)),
exit_info_(
isolate_, kExitInfoFieldCount, MAYBE_FIELD_PTR(env_info, exit_info)),
should_abort_on_uncaught_toggle_(
isolate_,
1,
MAYBE_FIELD_PTR(env_info, should_abort_on_uncaught_toggle)),
stream_base_state_(isolate_,
StreamBase::kNumStreamBaseStateFields,
MAYBE_FIELD_PTR(env_info, stream_base_state)),
time_origin_(performance::performance_process_start),
time_origin_timestamp_(performance::performance_process_start_timestamp),
environment_start_(PERFORMANCE_NOW()),
flags_(flags),
thread_id_(thread_id.id == static_cast(-1)
? AllocateEnvironmentThreadId().id
: thread_id.id) {
constexpr bool is_shared_ro_heap =
#ifdef NODE_V8_SHARED_RO_HEAP
true;
#else
false;
#endif
if (is_shared_ro_heap && !is_main_thread()) {
// If this is a Worker thread and we are in shared-readonly-heap mode,
// we can always safely use the parent's Isolate's code cache.
CHECK_NOT_NULL(isolate_data->worker_context());
builtin_loader()->CopySourceAndCodeCacheReferenceFrom(
isolate_data->worker_context()->env()->builtin_loader());
} else if (isolate_data->snapshot_data() != nullptr) {
// ... otherwise, if a snapshot was provided, use its code cache.
size_t cache_size = isolate_data->snapshot_data()->code_cache.size();
per_process::Debug(DebugCategory::CODE_CACHE,
"snapshot contains %zu code cache\n",
cache_size);
if (cache_size > 0) {
builtin_loader()->RefreshCodeCache(
isolate_data->snapshot_data()->code_cache);
}
}
// We are supposed to call builtin_loader_.SetEagerCompile() in
// snapshot mode here because it's beneficial to compile built-ins
// loaded in the snapshot eagerly and include the code of inner functions
// that are likely to be used by user since they are part of the core
// startup. But this requires us to start the coverage collections
// before Environment/Context creation which is not currently possible.
// TODO(joyeecheung): refactor V8ProfilerConnection classes to parse
// JSON without v8 and lift this restriction.
// We'll be creating new objects so make sure we've entered the context.
HandleScope handle_scope(isolate);
// Set some flags if only kDefaultFlags was passed. This can make API version
// transitions easier for embedders.
if (flags_ & EnvironmentFlags::kDefaultFlags) {
flags_ = flags_ |
EnvironmentFlags::kOwnsProcessState |
EnvironmentFlags::kOwnsInspector;
}
set_env_vars(per_process::system_environment);
enabled_debug_list_.Parse(env_vars());
// We create new copies of the per-Environment option sets, so that it is
// easier to modify them after Environment creation. The defaults are
// part of the per-Isolate option set, for which in turn the defaults are
// part of the per-process option set.
options_ = std::make_shared(
*isolate_data->options()->per_env);
inspector_host_port_ = std::make_shared>(
options_->debug_options().host_port);
heap_snapshot_near_heap_limit_ =
static_cast(options_->heap_snapshot_near_heap_limit);
if (!(flags_ & EnvironmentFlags::kOwnsProcessState)) {
set_abort_on_uncaught_exception(false);
}
#if HAVE_INSPECTOR
// We can only create the inspector agent after having cloned the options.
inspector_agent_ = std::make_unique(this);
#endif
if (tracing::AgentWriterHandle* writer = GetTracingAgentWriter()) {
trace_state_observer_ = std::make_unique(this);
if (TracingController* tracing_controller = writer->GetTracingController())
tracing_controller->AddTraceStateObserver(trace_state_observer_.get());
}
destroy_async_id_list_.reserve(512);
performance_state_ = std::make_unique(
isolate,
time_origin_,
time_origin_timestamp_,
MAYBE_FIELD_PTR(env_info, performance_state));
if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACING_CATEGORY_NODE1(environment)) != 0) {
auto traced_value = tracing::TracedValue::Create();
traced_value->BeginArray("args");
for (const std::string& arg : args) traced_value->AppendString(arg);
traced_value->EndArray();
traced_value->BeginArray("exec_args");
for (const std::string& arg : exec_args) traced_value->AppendString(arg);
traced_value->EndArray();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(TRACING_CATEGORY_NODE1(environment),
"Environment",
this,
"args",
std::move(traced_value));
}
if (options_->experimental_permission) {
permission()->EnablePermissions();
// The process shouldn't be able to neither
// spawn/worker nor use addons or enable inspector
// unless explicitly allowed by the user
if (!options_->allow_addons) {
options_->allow_native_addons = false;
}
flags_ = flags_ | EnvironmentFlags::kNoCreateInspector;
permission()->Apply(this, {"*"}, permission::PermissionScope::kInspector);
if (!options_->allow_child_process) {
permission()->Apply(
this, {"*"}, permission::PermissionScope::kChildProcess);
}
if (!options_->allow_worker_threads) {
permission()->Apply(
this, {"*"}, permission::PermissionScope::kWorkerThreads);
}
if (!options_->allow_fs_read.empty()) {
permission()->Apply(this,
options_->allow_fs_read,
permission::PermissionScope::kFileSystemRead);
}
if (!options_->allow_fs_write.empty()) {
permission()->Apply(this,
options_->allow_fs_write,
permission::PermissionScope::kFileSystemWrite);
}
}
}
void Environment::InitializeMainContext(Local context,
const EnvSerializeInfo* env_info) {
principal_realm_ = std::make_unique(
this, context, MAYBE_FIELD_PTR(env_info, principal_realm));
if (env_info != nullptr) {
DeserializeProperties(env_info);
}
if (!options_->force_async_hooks_checks) {
async_hooks_.no_force_checks();
}
// By default, always abort when --abort-on-uncaught-exception was passed.
should_abort_on_uncaught_toggle_[0] = 1;
// The process is not exiting by default.
set_exiting(false);
performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_ENVIRONMENT,
environment_start_);
performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_NODE_START,
per_process::node_start_time);
if (per_process::v8_initialized) {
performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_V8_START,
performance::performance_v8_start);
}
}
Environment::~Environment() {
HandleScope handle_scope(isolate());
Local ctx = context();
if (Environment** interrupt_data = interrupt_data_.load()) {
// There are pending RequestInterrupt() callbacks. Tell them not to run,
// then force V8 to run interrupts by compiling and running an empty script
// so as not to leak memory.
*interrupt_data = nullptr;
Isolate::AllowJavascriptExecutionScope allow_js_here(isolate());
TryCatch try_catch(isolate());
Context::Scope context_scope(ctx);
#ifdef DEBUG
bool consistency_check = false;
isolate()->RequestInterrupt([](Isolate*, void* data) {
*static_cast(data) = true;
}, &consistency_check);
#endif
Local