Skip to content

Commit 39cf8b4

Browse files
joyeecheungdanielleadams
authored andcommitted
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. PR-URL: #44796 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 2d2e71c commit 39cf8b4

File tree

4 files changed

+134
-100
lines changed

4 files changed

+134
-100
lines changed

src/env.cc

+1-2
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,7 @@ void IsolateData::CreateProperties() {
451451
templ->Inherit(BaseObject::GetConstructorTemplate(this));
452452
set_binding_data_ctor_template(templ);
453453

454-
set_contextify_global_template(
455-
contextify::ContextifyContext::CreateGlobalTemplate(isolate_));
454+
contextify::ContextifyContext::InitializeGlobalTemplates(this);
456455
}
457456

458457
IsolateData::IsolateData(Isolate* isolate,

src/env_properties.h

+1
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

+113-84
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ using v8::ArrayBufferView;
4242
using v8::Boolean;
4343
using v8::Context;
4444
using v8::EscapableHandleScope;
45-
using v8::External;
4645
using v8::Function;
4746
using v8::FunctionCallbackInfo;
4847
using v8::FunctionTemplate;
@@ -108,62 +107,77 @@ Local<Name> Uint32ToName(Local<Context> context, uint32_t index) {
108107

109108
} // anonymous namespace
110109

111-
ContextifyContext::ContextifyContext(
110+
BaseObjectPtr<ContextifyContext> ContextifyContext::New(
112111
Environment* env,
113112
Local<Object> sandbox_obj,
114-
const ContextOptions& options)
115-
: env_(env),
116-
microtask_queue_wrap_(options.microtask_queue_wrap) {
113+
const ContextOptions& options) {
114+
HandleScope scope(env->isolate());
115+
InitializeGlobalTemplates(env->isolate_data());
117116
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-
}
117+
DCHECK(!object_template.IsEmpty());
122118
bool use_node_snapshot = per_process::cli_options->node_snapshot;
123119
const SnapshotData* snapshot_data =
124120
use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr;
125121

126122
MicrotaskQueue* queue =
127-
microtask_queue()
128-
? microtask_queue().get()
123+
options.microtask_queue_wrap
124+
? options.microtask_queue_wrap->microtask_queue().get()
129125
: env->isolate()->GetCurrentContext()->GetMicrotaskQueue();
130126

131127
Local<Context> v8_context;
132128
if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue)
133-
.ToLocal(&v8_context)) ||
134-
!InitializeContext(v8_context, env, sandbox_obj, options)) {
129+
.ToLocal(&v8_context))) {
135130
// Allocation failure, maximum call stack size reached, termination, etc.
136-
return;
131+
return BaseObjectPtr<ContextifyContext>();
137132
}
133+
return New(v8_context, env, sandbox_obj, options);
134+
}
138135

139-
context_.Reset(env->isolate(), v8_context);
140-
context_.SetWeak(this, WeakCallback, WeakCallbackType::kParameter);
141-
env->AddCleanupHook(CleanupHook, this);
136+
void ContextifyContext::MemoryInfo(MemoryTracker* tracker) const {
137+
if (microtask_queue_wrap_) {
138+
tracker->TrackField("microtask_queue_wrap",
139+
microtask_queue_wrap_->object());
140+
}
142141
}
143142

143+
ContextifyContext::ContextifyContext(Environment* env,
144+
Local<Object> wrapper,
145+
Local<Context> v8_context,
146+
const ContextOptions& options)
147+
: BaseObject(env, wrapper),
148+
microtask_queue_wrap_(options.microtask_queue_wrap) {
149+
context_.Reset(env->isolate(), v8_context);
150+
// This should only be done after the initial initializations of the context
151+
// global object is finished.
152+
DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData(
153+
ContextEmbedderIndex::kContextifyContext));
154+
v8_context->SetAlignedPointerInEmbedderData(
155+
ContextEmbedderIndex::kContextifyContext, this);
156+
// It's okay to make this reference weak - V8 would create an internal
157+
// reference to this context via the constructor of the wrapper.
158+
// As long as the wrapper is alive, it's constructor is alive, and so
159+
// is the context.
160+
context_.SetWeak();
161+
}
144162

145163
ContextifyContext::~ContextifyContext() {
146-
env()->RemoveCleanupHook(CleanupHook, this);
147164
Isolate* isolate = env()->isolate();
148165
HandleScope scope(isolate);
149166

150167
env()->async_hooks()
151168
->RemoveContext(PersistentToLocal::Weak(isolate, context_));
169+
context_.Reset();
152170
}
153171

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();
172+
void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) {
173+
if (!isolate_data->contextify_global_template().IsEmpty()) {
174+
return;
175+
}
176+
DCHECK(isolate_data->contextify_wrapper_template().IsEmpty());
177+
Local<FunctionTemplate> global_func_template =
178+
FunctionTemplate::New(isolate_data->isolate());
179+
Local<ObjectTemplate> global_object_template =
180+
global_func_template->InstanceTemplate();
167181

168182
NamedPropertyHandlerConfiguration config(
169183
PropertyGetterCallback,
@@ -185,10 +199,15 @@ Local<ObjectTemplate> ContextifyContext::CreateGlobalTemplate(
185199
{},
186200
PropertyHandlerFlags::kHasNoSideEffect);
187201

188-
object_template->SetHandler(config);
189-
object_template->SetHandler(indexed_config);
202+
global_object_template->SetHandler(config);
203+
global_object_template->SetHandler(indexed_config);
204+
isolate_data->set_contextify_global_template(global_object_template);
190205

191-
return object_template;
206+
Local<FunctionTemplate> wrapper_func_template =
207+
BaseObject::MakeLazilyInitializedJSTemplate(isolate_data);
208+
Local<ObjectTemplate> wrapper_object_template =
209+
wrapper_func_template->InstanceTemplate();
210+
isolate_data->set_contextify_wrapper_template(wrapper_object_template);
192211
}
193212

194213
MaybeLocal<Context> ContextifyContext::CreateV8Context(
@@ -218,43 +237,45 @@ MaybeLocal<Context> ContextifyContext::CreateV8Context(
218237
.ToLocal(&ctx)) {
219238
return MaybeLocal<Context>();
220239
}
240+
221241
return scope.Escape(ctx);
222242
}
223243

224-
bool ContextifyContext::InitializeContext(Local<Context> ctx,
225-
Environment* env,
226-
Local<Object> sandbox_obj,
227-
const ContextOptions& options) {
244+
BaseObjectPtr<ContextifyContext> ContextifyContext::New(
245+
Local<Context> v8_context,
246+
Environment* env,
247+
Local<Object> sandbox_obj,
248+
const ContextOptions& options) {
228249
HandleScope scope(env->isolate());
229-
230250
// This only initializes part of the context. The primordials are
231251
// only initilaized when needed because even deserializing them slows
232252
// things down significantly and they are only needed in rare occasions
233253
// in the vm contexts.
234-
if (InitializeContextRuntime(ctx).IsNothing()) {
235-
return false;
254+
if (InitializeContextRuntime(v8_context).IsNothing()) {
255+
return BaseObjectPtr<ContextifyContext>();
236256
}
237257

238258
Local<Context> main_context = env->context();
239-
ctx->SetSecurityToken(main_context->GetSecurityToken());
259+
Local<Object> new_context_global = v8_context->Global();
260+
v8_context->SetSecurityToken(main_context->GetSecurityToken());
240261

241262
// We need to tie the lifetime of the sandbox object with the lifetime of
242263
// newly created context. We do this by making them hold references to each
243264
// 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());
265+
// embedder data field. The sandbox uses a private symbol to hold a reference
266+
// to the ContextifyContext wrapper which in turn internally references
267+
// the context from its constructor.
268+
v8_context->SetEmbedderData(ContextEmbedderIndex::kSandboxObject,
269+
sandbox_obj);
250270

251271
// Delegate the code generation validation to
252272
// 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);
273+
v8_context->AllowCodeGenerationFromStrings(false);
274+
v8_context->SetEmbedderData(
275+
ContextEmbedderIndex::kAllowCodeGenerationFromStrings,
276+
options.allow_code_gen_strings);
277+
v8_context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration,
278+
options.allow_code_gen_wasm);
258279

259280
Utf8Value name_val(env->isolate(), options.name);
260281
ContextInfo info(*name_val);
@@ -263,28 +284,43 @@ bool ContextifyContext::InitializeContext(Local<Context> ctx,
263284
info.origin = *origin_val;
264285
}
265286

287+
BaseObjectPtr<ContextifyContext> result;
288+
Local<Object> wrapper;
266289
{
267-
Context::Scope context_scope(ctx);
290+
Context::Scope context_scope(v8_context);
268291
Local<String> ctor_name = sandbox_obj->GetConstructorName();
269-
if (!ctor_name->Equals(ctx, env->object_string()).FromMaybe(false) &&
270-
ctx->Global()
292+
if (!ctor_name->Equals(v8_context, env->object_string()).FromMaybe(false) &&
293+
new_context_global
271294
->DefineOwnProperty(
272-
ctx,
295+
v8_context,
273296
v8::Symbol::GetToStringTag(env->isolate()),
274297
ctor_name,
275298
static_cast<v8::PropertyAttribute>(v8::DontEnum))
276299
.IsNothing()) {
277-
return false;
300+
return BaseObjectPtr<ContextifyContext>();
278301
}
302+
env->AssignToContext(v8_context, nullptr, info);
303+
304+
if (!env->contextify_wrapper_template()
305+
->NewInstance(v8_context)
306+
.ToLocal(&wrapper)) {
307+
return BaseObjectPtr<ContextifyContext>();
308+
}
309+
310+
result =
311+
MakeBaseObject<ContextifyContext>(env, wrapper, v8_context, options);
312+
// The only strong reference to the wrapper will come from the sandbox.
313+
result->MakeWeak();
279314
}
280315

281-
env->AssignToContext(ctx, nullptr, info);
316+
if (sandbox_obj
317+
->SetPrivate(
318+
v8_context, env->contextify_context_private_symbol(), wrapper)
319+
.IsNothing()) {
320+
return BaseObjectPtr<ContextifyContext>();
321+
}
282322

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;
323+
return result;
288324
}
289325

290326
void ContextifyContext::Init(Environment* env, Local<Object> target) {
@@ -350,22 +386,14 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo<Value>& args) {
350386
}
351387

352388
TryCatchScope try_catch(env);
353-
std::unique_ptr<ContextifyContext> context_ptr =
354-
std::make_unique<ContextifyContext>(env, sandbox, options);
389+
BaseObjectPtr<ContextifyContext> context_ptr =
390+
ContextifyContext::New(env, sandbox, options);
355391

356392
if (try_catch.HasCaught()) {
357393
if (!try_catch.HasTerminated())
358394
try_catch.ReThrow();
359395
return;
360396
}
361-
362-
Local<Context> new_context = context_ptr->context();
363-
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()));
369397
}
370398

371399

@@ -392,23 +420,24 @@ void ContextifyContext::WeakCallback(
392420
ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox(
393421
Environment* env,
394422
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());
423+
Local<Value> context_global;
424+
if (sandbox
425+
->GetPrivate(env->context(), env->contextify_context_private_symbol())
426+
.ToLocal(&context_global) &&
427+
context_global->IsObject()) {
428+
return Unwrap<ContextifyContext>(context_global.As<Object>());
403429
}
404430
return nullptr;
405431
}
406432

407-
// static
408433
template <typename T>
409434
ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo<T>& args) {
435+
return Get(args.This());
436+
}
437+
438+
ContextifyContext* ContextifyContext::Get(Local<Object> object) {
410439
Local<Context> context;
411-
if (!args.This()->GetCreationContext().ToLocal(&context)) {
440+
if (!object->GetCreationContext().ToLocal(&context)) {
412441
return nullptr;
413442
}
414443
if (!ContextEmbedderTag::IsNodeContext(context)) {

0 commit comments

Comments
 (0)