Skip to content

Commit 71c4ed9

Browse files
committed
vm: make ContextifyContext a BaseObject
Instead of adding a reference to the ContextifyContext by using a v8::External, we make ContextifyContext a weak BaseObject that whose wrapper is referenced by the sandbox via a private symbol. This makes it easier to snapshot the contexts, in addition to reusing the BaseObject lifetime management for ContextifyContexts.
1 parent 14b1b0d commit 71c4ed9

File tree

4 files changed

+163
-132
lines changed

4 files changed

+163
-132
lines changed

src/env.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,7 @@ void IsolateData::CreateProperties() {
454454
templ->Inherit(BaseObject::GetConstructorTemplate(this));
455455
set_binding_data_ctor_template(templ);
456456

457-
set_contextify_global_template(
458-
contextify::ContextifyContext::CreateGlobalTemplate(isolate_));
457+
contextify::ContextifyContext::InitializeGlobalTemplates(this);
459458
}
460459

461460
IsolateData::IsolateData(Isolate* isolate,

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@
332332
V(blob_constructor_template, v8::FunctionTemplate) \
333333
V(blocklist_constructor_template, v8::FunctionTemplate) \
334334
V(contextify_global_template, v8::ObjectTemplate) \
335+
V(contextify_wrapper_template, v8::ObjectTemplate) \
335336
V(compiled_fn_entry_template, v8::ObjectTemplate) \
336337
V(dir_instance_template, v8::ObjectTemplate) \
337338
V(fd_constructor_template, v8::ObjectTemplate) \

src/node_contextify.cc

Lines changed: 144 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -108,62 +108,151 @@ Local<Name> Uint32ToName(Local<Context> context, uint32_t index) {
108108

109109
} // anonymous namespace
110110

111-
ContextifyContext::ContextifyContext(
111+
BaseObjectPtr<ContextifyContext> ContextifyContext::New(
112112
Environment* env,
113113
Local<Object> sandbox_obj,
114-
const ContextOptions& options)
115-
: env_(env),
116-
microtask_queue_wrap_(options.microtask_queue_wrap) {
114+
const ContextOptions& options) {
115+
HandleScope scope(env->isolate());
116+
InitializeGlobalTemplates(env->isolate_data());
117117
Local<ObjectTemplate> object_template = env->contextify_global_template();
118-
if (object_template.IsEmpty()) {
119-
object_template = CreateGlobalTemplate(env->isolate());
120-
env->set_contextify_global_template(object_template);
121-
}
118+
DCHECK(!object_template.IsEmpty());
122119
bool use_node_snapshot = per_process::cli_options->node_snapshot;
123120
const SnapshotData* snapshot_data =
124121
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr;
125122

126123
MicrotaskQueue* queue =
127-
microtask_queue()
128-
? microtask_queue().get()
124+
options.microtask_queue_wrap
125+
? options.microtask_queue_wrap->microtask_queue().get()
129126
: env->isolate()->GetCurrentContext()->GetMicrotaskQueue();
130127

131128
Local<Context> v8_context;
132129
if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue)
133-
.ToLocal(&v8_context)) ||
134-
!InitializeContext(v8_context, env, sandbox_obj, options)) {
130+
.ToLocal(&v8_context))) {
135131
// Allocation failure, maximum call stack size reached, termination, etc.
136-
return;
132+
return BaseObjectPtr<ContextifyContext>();
137133
}
138134

139-
context_.Reset(env->isolate(), v8_context);
140-
context_.SetWeak(this, WeakCallback, WeakCallbackType::kParameter);
141-
env->AddCleanupHook(CleanupHook, this);
135+
// This only initializes part of the context. The primordials are
136+
// only initilaized when needed because even deserializing them slows
137+
// things down significantly and they are only needed in rare occasions
138+
// in the vm contexts.
139+
if (InitializeContextRuntime(v8_context).IsNothing()) {
140+
return BaseObjectPtr<ContextifyContext>();
141+
}
142+
143+
Local<Context> main_context = env->context();
144+
Local<Object> new_context_global = v8_context->Global();
145+
v8_context->SetSecurityToken(main_context->GetSecurityToken());
146+
147+
// We need to tie the lifetime of the sandbox object with the lifetime of
148+
// newly created context. We do this by making them hold references to each
149+
// other. The context can directly hold a reference to the sandbox as an
150+
// embedder data field. The sandbox uses a private symbol to hold a reference
151+
// to the ContextifyContext wrapper which in turn internally references
152+
// the context from its constructor.
153+
v8_context->SetEmbedderData(ContextEmbedderIndex::kSandboxObject,
154+
sandbox_obj);
155+
156+
// Delegate the code generation validation to
157+
// node::ModifyCodeGenerationFromStrings.
158+
v8_context->AllowCodeGenerationFromStrings(false);
159+
v8_context->SetEmbedderData(
160+
ContextEmbedderIndex::kAllowCodeGenerationFromStrings,
161+
options.allow_code_gen_strings);
162+
v8_context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration,
163+
options.allow_code_gen_wasm);
164+
165+
Utf8Value name_val(env->isolate(), options.name);
166+
ContextInfo info(*name_val);
167+
if (!options.origin.IsEmpty()) {
168+
Utf8Value origin_val(env->isolate(), options.origin);
169+
info.origin = *origin_val;
170+
}
171+
172+
BaseObjectPtr<ContextifyContext> result;
173+
Local<Object> wrapper;
174+
{
175+
Context::Scope context_scope(v8_context);
176+
Local<String> ctor_name = sandbox_obj->GetConstructorName();
177+
if (!ctor_name->Equals(v8_context, env->object_string()).FromMaybe(false) &&
178+
new_context_global
179+
->DefineOwnProperty(
180+
v8_context,
181+
v8::Symbol::GetToStringTag(env->isolate()),
182+
ctor_name,
183+
static_cast<v8::PropertyAttribute>(v8::DontEnum))
184+
.IsNothing()) {
185+
return BaseObjectPtr<ContextifyContext>();
186+
}
187+
env->AssignToContext(v8_context, nullptr, info);
188+
189+
if (!env->contextify_wrapper_template()
190+
->NewInstance(v8_context)
191+
.ToLocal(&wrapper)) {
192+
return BaseObjectPtr<ContextifyContext>();
193+
}
194+
195+
result =
196+
MakeBaseObject<ContextifyContext>(env, wrapper, v8_context, options);
197+
// The only strong reference to the wrapper will come from the sandbox.
198+
result->MakeWeak();
199+
}
200+
201+
if (sandbox_obj
202+
->SetPrivate(
203+
v8_context, env->contextify_context_private_symbol(), wrapper)
204+
.IsNothing()) {
205+
return BaseObjectPtr<ContextifyContext>();
206+
}
207+
208+
return result;
142209
}
143210

211+
void ContextifyContext::MemoryInfo(MemoryTracker* tracker) const {
212+
if (microtask_queue_wrap_) {
213+
tracker->TrackField("microtask_queue_wrap",
214+
microtask_queue_wrap_->object());
215+
}
216+
}
217+
218+
ContextifyContext::ContextifyContext(Environment* env,
219+
Local<Object> wrapper,
220+
Local<Context> v8_context,
221+
const ContextOptions& options)
222+
: BaseObject(env, wrapper),
223+
microtask_queue_wrap_(options.microtask_queue_wrap) {
224+
context_.Reset(env->isolate(), v8_context);
225+
// This should only be done after the initial initializations of the context
226+
// global object is finished.
227+
DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData(
228+
ContextEmbedderIndex::kContextifyContext));
229+
v8_context->SetAlignedPointerInEmbedderData(
230+
ContextEmbedderIndex::kContextifyContext, this);
231+
// It's okay to make this reference weak - V8 would create an internal
232+
// reference to this context via the constructor of the wrapper.
233+
// As long as the wrapper is alive, it's constructor is alive, and so
234+
// is the context.
235+
context_.SetWeak();
236+
}
144237

145238
ContextifyContext::~ContextifyContext() {
146-
env()->RemoveCleanupHook(CleanupHook, this);
147239
Isolate* isolate = env()->isolate();
148240
HandleScope scope(isolate);
149241

150242
env()->async_hooks()
151243
->RemoveContext(PersistentToLocal::Weak(isolate, context_));
244+
context_.Reset();
152245
}
153246

154-
155-
void ContextifyContext::CleanupHook(void* arg) {
156-
ContextifyContext* self = static_cast<ContextifyContext*>(arg);
157-
self->context_.Reset();
158-
delete self;
159-
}
160-
161-
Local<ObjectTemplate> ContextifyContext::CreateGlobalTemplate(
162-
Isolate* isolate) {
163-
Local<FunctionTemplate> function_template = FunctionTemplate::New(isolate);
164-
165-
Local<ObjectTemplate> object_template =
166-
function_template->InstanceTemplate();
247+
void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) {
248+
if (!isolate_data->contextify_global_template().IsEmpty()) {
249+
return;
250+
}
251+
DCHECK(isolate_data->contextify_wrapper_template().IsEmpty());
252+
Local<FunctionTemplate> global_func_template =
253+
FunctionTemplate::New(isolate_data->isolate());
254+
Local<ObjectTemplate> global_object_template =
255+
global_func_template->InstanceTemplate();
167256

168257
NamedPropertyHandlerConfiguration config(
169258
PropertyGetterCallback,
@@ -185,10 +274,15 @@ Local<ObjectTemplate> ContextifyContext::CreateGlobalTemplate(
185274
{},
186275
PropertyHandlerFlags::kHasNoSideEffect);
187276

188-
object_template->SetHandler(config);
189-
object_template->SetHandler(indexed_config);
277+
global_object_template->SetHandler(config);
278+
global_object_template->SetHandler(indexed_config);
279+
isolate_data->set_contextify_global_template(global_object_template);
190280

191-
return object_template;
281+
Local<FunctionTemplate> wrapper_func_template =
282+
BaseObject::MakeLazilyInitializedJSTemplate(isolate_data);
283+
Local<ObjectTemplate> wrapper_object_template =
284+
wrapper_func_template->InstanceTemplate();
285+
isolate_data->set_contextify_wrapper_template(wrapper_object_template);
192286
}
193287

194288
MaybeLocal<Context> ContextifyContext::CreateV8Context(
@@ -218,73 +312,8 @@ MaybeLocal<Context> ContextifyContext::CreateV8Context(
218312
.ToLocal(&ctx)) {
219313
return MaybeLocal<Context>();
220314
}
221-
return scope.Escape(ctx);
222-
}
223-
224-
bool ContextifyContext::InitializeContext(Local<Context> ctx,
225-
Environment* env,
226-
Local<Object> sandbox_obj,
227-
const ContextOptions& options) {
228-
HandleScope scope(env->isolate());
229-
230-
// This only initializes part of the context. The primordials are
231-
// only initilaized when needed because even deserializing them slows
232-
// things down significantly and they are only needed in rare occasions
233-
// in the vm contexts.
234-
if (InitializeContextRuntime(ctx).IsNothing()) {
235-
return false;
236-
}
237-
238-
Local<Context> main_context = env->context();
239-
ctx->SetSecurityToken(main_context->GetSecurityToken());
240-
241-
// We need to tie the lifetime of the sandbox object with the lifetime of
242-
// newly created context. We do this by making them hold references to each
243-
// other. The context can directly hold a reference to the sandbox as an
244-
// embedder data field. However, we cannot hold a reference to a v8::Context
245-
// directly in an Object, we instead hold onto the new context's global
246-
// object instead (which then has a reference to the context).
247-
ctx->SetEmbedderData(ContextEmbedderIndex::kSandboxObject, sandbox_obj);
248-
sandbox_obj->SetPrivate(
249-
main_context, env->contextify_global_private_symbol(), ctx->Global());
250-
251-
// Delegate the code generation validation to
252-
// node::ModifyCodeGenerationFromStrings.
253-
ctx->AllowCodeGenerationFromStrings(false);
254-
ctx->SetEmbedderData(ContextEmbedderIndex::kAllowCodeGenerationFromStrings,
255-
options.allow_code_gen_strings);
256-
ctx->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration,
257-
options.allow_code_gen_wasm);
258-
259-
Utf8Value name_val(env->isolate(), options.name);
260-
ContextInfo info(*name_val);
261-
if (!options.origin.IsEmpty()) {
262-
Utf8Value origin_val(env->isolate(), options.origin);
263-
info.origin = *origin_val;
264-
}
265-
266-
{
267-
Context::Scope context_scope(ctx);
268-
Local<String> ctor_name = sandbox_obj->GetConstructorName();
269-
if (!ctor_name->Equals(ctx, env->object_string()).FromMaybe(false) &&
270-
ctx->Global()
271-
->DefineOwnProperty(
272-
ctx,
273-
v8::Symbol::GetToStringTag(env->isolate()),
274-
ctor_name,
275-
static_cast<v8::PropertyAttribute>(v8::DontEnum))
276-
.IsNothing()) {
277-
return false;
278-
}
279-
}
280-
281-
env->AssignToContext(ctx, nullptr, info);
282315

283-
// This should only be done after the initial initializations of the context
284-
// global object is finished.
285-
ctx->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kContextifyContext,
286-
this);
287-
return true;
316+
return scope.Escape(ctx);
288317
}
289318

290319
void ContextifyContext::Init(Environment* env, Local<Object> target) {
@@ -350,22 +379,20 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo<Value>& args) {
350379
}
351380

352381
TryCatchScope try_catch(env);
353-
std::unique_ptr<ContextifyContext> context_ptr =
354-
std::make_unique<ContextifyContext>(env, sandbox, options);
382+
BaseObjectPtr<ContextifyContext> context_ptr =
383+
ContextifyContext::New(env, sandbox, options);
355384

356385
if (try_catch.HasCaught()) {
357386
if (!try_catch.HasTerminated())
358387
try_catch.ReThrow();
359388
return;
360389
}
361390

391+
if (context_ptr.get() == nullptr) {
392+
return;
393+
}
362394
Local<Context> new_context = context_ptr->context();
363395
if (new_context.IsEmpty()) return;
364-
365-
sandbox->SetPrivate(
366-
env->context(),
367-
env->contextify_context_private_symbol(),
368-
External::New(env->isolate(), context_ptr.release()));
369396
}
370397

371398

@@ -392,23 +419,24 @@ void ContextifyContext::WeakCallback(
392419
ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox(
393420
Environment* env,
394421
const Local<Object>& sandbox) {
395-
MaybeLocal<Value> maybe_value =
396-
sandbox->GetPrivate(env->context(),
397-
env->contextify_context_private_symbol());
398-
Local<Value> context_external_v;
399-
if (maybe_value.ToLocal(&context_external_v) &&
400-
context_external_v->IsExternal()) {
401-
Local<External> context_external = context_external_v.As<External>();
402-
return static_cast<ContextifyContext*>(context_external->Value());
422+
Local<Value> context_global;
423+
if (sandbox
424+
->GetPrivate(env->context(), env->contextify_context_private_symbol())
425+
.ToLocal(&context_global) &&
426+
context_global->IsObject()) {
427+
return Unwrap<ContextifyContext>(context_global.As<Object>());
403428
}
404429
return nullptr;
405430
}
406431

407-
// static
408432
template <typename T>
409433
ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo<T>& args) {
434+
return Get(args.This());
435+
}
436+
437+
ContextifyContext* ContextifyContext::Get(Local<Object> object) {
410438
Local<Context> context;
411-
if (!args.This()->GetCreationContext().ToLocal(&context)) {
439+
if (!object->GetCreationContext().ToLocal(&context)) {
412440
return nullptr;
413441
}
414442
if (!ContextEmbedderTag::IsNodeContext(context)) {

0 commit comments

Comments
 (0)