Skip to content

Commit 70662f4

Browse files
codebyterejoyeecheung
authored andcommitted
src: fix cppgc incompatibility in v8
This patch updates the layout of the BaseObjects to make sure that the first embedder field of them is a "type" pointer, the first 16 bits of which are the Node.js embedder ID, so that cppgc will always skip over them. In addition we now use this field to determine if the native object should be interpreted as a Node.js embedder object in the serialization and deserialization callbacks for the startup snapshot to improve the reliability. Co-authored-by: Joyee Cheung <[email protected]> PR-URL: #43521 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Darshan Sen <[email protected]>
1 parent 5c721a3 commit 70662f4

8 files changed

+61
-25
lines changed

src/base_object.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ namespace worker {
3838
class TransferData;
3939
}
4040

41+
extern uint16_t kNodeEmbedderId;
42+
4143
class BaseObject : public MemoryRetainer {
4244
public:
43-
enum InternalFields { kSlot, kInternalFieldCount };
45+
enum InternalFields { kEmbedderType, kSlot, kInternalFieldCount };
4446

45-
// Associates this object with `object`. It uses the 0th internal field for
47+
// Associates this object with `object`. It uses the 1st internal field for
4648
// that, and in particular aborts if there is no such field.
4749
BaseObject(Environment* env, v8::Local<v8::Object> object);
4850
~BaseObject() override;

src/env.cc

+14-2
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
18361836
Local<Object> holder,
18371837
int index,
18381838
InternalFieldInfo* info) {
1839+
DCHECK_EQ(index, BaseObject::kEmbedderType);
18391840
DeserializeRequest request{cb, {isolate(), holder}, index, info};
18401841
deserialize_requests_.push_back(std::move(request));
18411842
}
@@ -2129,7 +2130,9 @@ void Environment::RunWeakRefCleanup() {
21292130
BaseObject::BaseObject(Environment* env, Local<Object> object)
21302131
: persistent_handle_(env->isolate(), object), env_(env) {
21312132
CHECK_EQ(false, object.IsEmpty());
2132-
CHECK_GT(object->InternalFieldCount(), 0);
2133+
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
2134+
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2135+
&kNodeEmbedderId);
21332136
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
21342137
static_cast<void*>(this));
21352138
env->AddCleanupHook(DeleteMe, static_cast<void*>(this));
@@ -2180,10 +2183,19 @@ void BaseObject::MakeWeak() {
21802183
WeakCallbackType::kParameter);
21812184
}
21822185

2186+
// This just has to be different from the Chromium ones:
2187+
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
2188+
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
2189+
// misinterpret the data stored in the embedder fields and try to garbage
2190+
// collect them.
2191+
uint16_t kNodeEmbedderId = 0x90de;
2192+
21832193
void BaseObject::LazilyInitializedJSTemplateConstructor(
21842194
const FunctionCallbackInfo<Value>& args) {
21852195
DCHECK(args.IsConstructCall());
2186-
DCHECK_GT(args.This()->InternalFieldCount(), 0);
2196+
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
2197+
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2198+
&kNodeEmbedderId);
21872199
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
21882200
}
21892201

src/node_blob.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ void BlobBindingData::Deserialize(
464464
Local<Object> holder,
465465
int index,
466466
InternalFieldInfo* info) {
467-
DCHECK_EQ(index, BaseObject::kSlot);
467+
DCHECK_EQ(index, BaseObject::kEmbedderType);
468468
HandleScope scope(context->GetIsolate());
469469
Environment* env = Environment::GetCurrent(context);
470470
BlobBindingData* binding =
@@ -479,7 +479,7 @@ void BlobBindingData::PrepareForSerialization(
479479
}
480480

481481
InternalFieldInfo* BlobBindingData::Serialize(int index) {
482-
DCHECK_EQ(index, BaseObject::kSlot);
482+
DCHECK_EQ(index, BaseObject::kEmbedderType);
483483
InternalFieldInfo* info = InternalFieldInfo::New(type());
484484
return info;
485485
}

src/node_file.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -2567,7 +2567,7 @@ void BindingData::Deserialize(Local<Context> context,
25672567
Local<Object> holder,
25682568
int index,
25692569
InternalFieldInfo* info) {
2570-
DCHECK_EQ(index, BaseObject::kSlot);
2570+
DCHECK_EQ(index, BaseObject::kEmbedderType);
25712571
HandleScope scope(context->GetIsolate());
25722572
Environment* env = Environment::GetCurrent(context);
25732573
BindingData* binding = env->AddBindingData<BindingData>(context, holder);
@@ -2584,7 +2584,7 @@ void BindingData::PrepareForSerialization(Local<Context> context,
25842584
}
25852585

25862586
InternalFieldInfo* BindingData::Serialize(int index) {
2587-
DCHECK_EQ(index, BaseObject::kSlot);
2587+
DCHECK_EQ(index, BaseObject::kEmbedderType);
25882588
InternalFieldInfo* info = InternalFieldInfo::New(type());
25892589
return info;
25902590
}

src/node_process_methods.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ void BindingData::PrepareForSerialization(Local<Context> context,
533533
}
534534

535535
InternalFieldInfo* BindingData::Serialize(int index) {
536-
DCHECK_EQ(index, BaseObject::kSlot);
536+
DCHECK_EQ(index, BaseObject::kEmbedderType);
537537
InternalFieldInfo* info = InternalFieldInfo::New(type());
538538
return info;
539539
}
@@ -542,7 +542,7 @@ void BindingData::Deserialize(Local<Context> context,
542542
Local<Object> holder,
543543
int index,
544544
InternalFieldInfo* info) {
545-
DCHECK_EQ(index, BaseObject::kSlot);
545+
DCHECK_EQ(index, BaseObject::kEmbedderType);
546546
v8::HandleScope scope(context->GetIsolate());
547547
Environment* env = Environment::GetCurrent(context);
548548
// Recreate the buffer in the constructor.

src/node_snapshotable.cc

+35-8
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,14 @@ void DeserializeNodeInternalFields(Local<Object> holder,
290290
return;
291291
}
292292

293+
DCHECK_EQ(index, BaseObject::kEmbedderType);
294+
293295
Environment* env_ptr = static_cast<Environment*>(env);
294296
const InternalFieldInfo* info =
295297
reinterpret_cast<const InternalFieldInfo*>(payload.data);
296-
298+
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
299+
// beginning of every InternalFieldInfo to ensure that we don't
300+
// step on payloads that were not serialized by Node.js.
297301
switch (info->type) {
298302
#define V(PropertyName, NativeTypeName) \
299303
case EmbedderObjectType::k_##PropertyName: { \
@@ -314,22 +318,44 @@ void DeserializeNodeInternalFields(Local<Object> holder,
314318
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
315319
int index,
316320
void* env) {
321+
// We only do one serialization for the kEmbedderType slot, the result
322+
// contains everything necessary for deserializing the entire object,
323+
// including the fields whose index is bigger than kEmbedderType
324+
// (most importantly, BaseObject::kSlot).
325+
// For Node.js this design is enough for all the native binding that are
326+
// serializable.
327+
if (index != BaseObject::kEmbedderType) {
328+
return StartupData{nullptr, 0};
329+
}
330+
331+
void* type_ptr = holder->GetAlignedPointerFromInternalField(index);
332+
if (type_ptr == nullptr) {
333+
return StartupData{nullptr, 0};
334+
}
335+
336+
uint16_t type = *(static_cast<uint16_t*>(type_ptr));
337+
per_process::Debug(DebugCategory::MKSNAPSHOT, "type = 0x%x\n", type);
338+
if (type != kNodeEmbedderId) {
339+
return StartupData{nullptr, 0};
340+
}
341+
317342
per_process::Debug(DebugCategory::MKSNAPSHOT,
318343
"Serialize internal field, index=%d, holder=%p\n",
319344
static_cast<int>(index),
320345
*holder);
321-
void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
322-
if (ptr == nullptr) {
323-
return StartupData{nullptr, 0};
324-
}
325346

326-
DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
327-
SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
347+
void* binding_ptr =
348+
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
349+
per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr);
350+
DCHECK(static_cast<BaseObject*>(binding_ptr)->is_snapshotable());
351+
SnapshotableObject* obj = static_cast<SnapshotableObject*>(binding_ptr);
352+
328353
per_process::Debug(DebugCategory::MKSNAPSHOT,
329354
"Object %p is %s, ",
330355
*holder,
331356
obj->GetTypeNameChars());
332357
InternalFieldInfo* info = obj->Serialize(index);
358+
333359
per_process::Debug(DebugCategory::MKSNAPSHOT,
334360
"payload size=%d\n",
335361
static_cast<int>(info->length));
@@ -344,8 +370,9 @@ void SerializeBindingData(Environment* env,
344370
env->ForEachBindingData([&](FastStringKey key,
345371
BaseObjectPtr<BaseObject> binding) {
346372
per_process::Debug(DebugCategory::MKSNAPSHOT,
347-
"Serialize binding %i, %p, type=%s\n",
373+
"Serialize binding %i (%p), object=%p, type=%s\n",
348374
static_cast<int>(i),
375+
binding.get(),
349376
*(binding->object()),
350377
key.c_str());
351378

src/node_snapshotable.h

-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ enum class EmbedderObjectType : uint8_t {
3030
// When serializing an embedder object, we'll serialize the native states
3131
// into a chunk that can be mapped into a subclass of InternalFieldInfo,
3232
// and pass it into the V8 callback as the payload of StartupData.
33-
// TODO(joyeecheung): the classification of types seem to be wrong.
34-
// We'd need a type for each field of each class of native object.
35-
// Maybe it's fine - we'll just use the type to invoke BaseObject constructors
36-
// and specify that the BaseObject has only one field for us to serialize.
37-
// And for non-BaseObject embedder objects, we'll use field-wise types.
3833
// The memory chunk looks like this:
3934
//
4035
// [ type ] - EmbedderObjectType (a uint8_t)

src/node_v8.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,15 @@ void BindingData::Deserialize(Local<Context> context,
123123
Local<Object> holder,
124124
int index,
125125
InternalFieldInfo* info) {
126-
DCHECK_EQ(index, BaseObject::kSlot);
126+
DCHECK_EQ(index, BaseObject::kEmbedderType);
127127
HandleScope scope(context->GetIsolate());
128128
Environment* env = Environment::GetCurrent(context);
129129
BindingData* binding = env->AddBindingData<BindingData>(context, holder);
130130
CHECK_NOT_NULL(binding);
131131
}
132132

133133
InternalFieldInfo* BindingData::Serialize(int index) {
134-
DCHECK_EQ(index, BaseObject::kSlot);
134+
DCHECK_EQ(index, BaseObject::kEmbedderType);
135135
InternalFieldInfo* info = InternalFieldInfo::New(type());
136136
return info;
137137
}

0 commit comments

Comments
 (0)