Skip to content

Commit f071028

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 <joyeec9h3@gmail.com> PR-URL: #43521 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com>
1 parent b8290ff commit f071028

8 files changed

+66
-23
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
@@ -1738,6 +1738,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
17381738
Local<Object> holder,
17391739
int index,
17401740
InternalFieldInfo* info) {
1741+
DCHECK_EQ(index, BaseObject::kEmbedderType);
17411742
DeserializeRequest request{cb, {isolate(), holder}, index, info};
17421743
deserialize_requests_.push_back(std::move(request));
17431744
}
@@ -2017,7 +2018,9 @@ void Environment::RunWeakRefCleanup() {
20172018
BaseObject::BaseObject(Environment* env, Local<Object> object)
20182019
: persistent_handle_(env->isolate(), object), env_(env) {
20192020
CHECK_EQ(false, object.IsEmpty());
2020-
CHECK_GT(object->InternalFieldCount(), 0);
2021+
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
2022+
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2023+
&kNodeEmbedderId);
20212024
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
20222025
static_cast<void*>(this));
20232026
env->AddCleanupHook(DeleteMe, static_cast<void*>(this));
@@ -2068,10 +2071,19 @@ void BaseObject::MakeWeak() {
20682071
WeakCallbackType::kParameter);
20692072
}
20702073

2074+
// This just has to be different from the Chromium ones:
2075+
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
2076+
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
2077+
// misinterpret the data stored in the embedder fields and try to garbage
2078+
// collect them.
2079+
uint16_t kNodeEmbedderId = 0x90de;
2080+
20712081
void BaseObject::LazilyInitializedJSTemplateConstructor(
20722082
const FunctionCallbackInfo<Value>& args) {
20732083
DCHECK(args.IsConstructCall());
2074-
DCHECK_GT(args.This()->InternalFieldCount(), 0);
2084+
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
2085+
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2086+
&kNodeEmbedderId);
20752087
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
20762088
}
20772089

src/node_blob.cc

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

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

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
@@ -532,7 +532,7 @@ void BindingData::PrepareForSerialization(Local<Context> context,
532532
}
533533

534534
InternalFieldInfo* BindingData::Serialize(int index) {
535-
DCHECK_EQ(index, BaseObject::kSlot);
535+
DCHECK_EQ(index, BaseObject::kEmbedderType);
536536
InternalFieldInfo* info = InternalFieldInfo::New(type());
537537
return info;
538538
}
@@ -541,7 +541,7 @@ void BindingData::Deserialize(Local<Context> context,
541541
Local<Object> holder,
542542
int index,
543543
InternalFieldInfo* info) {
544-
DCHECK_EQ(index, BaseObject::kSlot);
544+
DCHECK_EQ(index, BaseObject::kEmbedderType);
545545
v8::HandleScope scope(context->GetIsolate());
546546
Environment* env = Environment::GetCurrent(context);
547547
// Recreate the buffer in the constructor.

src/node_snapshotable.cc

+40-6
Original file line numberDiff line numberDiff line change
@@ -1119,10 +1119,20 @@ void DeserializeNodeInternalFields(Local<Object> holder,
11191119
static_cast<int>(index),
11201120
(*holder),
11211121
static_cast<int>(payload.raw_size));
1122+
1123+
if (payload.raw_size == 0) {
1124+
holder->SetAlignedPointerInInternalField(index, nullptr);
1125+
return;
1126+
}
1127+
1128+
DCHECK_EQ(index, BaseObject::kEmbedderType);
1129+
11221130
Environment* env_ptr = static_cast<Environment*>(env);
11231131
const InternalFieldInfo* info =
11241132
reinterpret_cast<const InternalFieldInfo*>(payload.data);
1125-
1133+
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
1134+
// beginning of every InternalFieldInfo to ensure that we don't
1135+
// step on payloads that were not serialized by Node.js.
11261136
switch (info->type) {
11271137
#define V(PropertyName, NativeTypeName) \
11281138
case EmbedderObjectType::k_##PropertyName: { \
@@ -1143,21 +1153,44 @@ void DeserializeNodeInternalFields(Local<Object> holder,
11431153
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
11441154
int index,
11451155
void* env) {
1146-
void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
1147-
if (ptr == nullptr) {
1156+
// We only do one serialization for the kEmbedderType slot, the result
1157+
// contains everything necessary for deserializing the entire object,
1158+
// including the fields whose index is bigger than kEmbedderType
1159+
// (most importantly, BaseObject::kSlot).
1160+
// For Node.js this design is enough for all the native binding that are
1161+
// serializable.
1162+
if (index != BaseObject::kEmbedderType) {
1163+
return StartupData{nullptr, 0};
1164+
}
1165+
1166+
void* type_ptr = holder->GetAlignedPointerFromInternalField(index);
1167+
if (type_ptr == nullptr) {
1168+
return StartupData{nullptr, 0};
1169+
}
1170+
1171+
uint16_t type = *(static_cast<uint16_t*>(type_ptr));
1172+
per_process::Debug(DebugCategory::MKSNAPSHOT, "type = 0x%x\n", type);
1173+
if (type != kNodeEmbedderId) {
11481174
return StartupData{nullptr, 0};
11491175
}
1176+
11501177
per_process::Debug(DebugCategory::MKSNAPSHOT,
11511178
"Serialize internal field, index=%d, holder=%p\n",
11521179
static_cast<int>(index),
11531180
*holder);
1154-
DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
1155-
SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
1181+
1182+
void* binding_ptr =
1183+
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
1184+
per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr);
1185+
DCHECK(static_cast<BaseObject*>(binding_ptr)->is_snapshotable());
1186+
SnapshotableObject* obj = static_cast<SnapshotableObject*>(binding_ptr);
1187+
11561188
per_process::Debug(DebugCategory::MKSNAPSHOT,
11571189
"Object %p is %s, ",
11581190
*holder,
11591191
obj->GetTypeNameChars());
11601192
InternalFieldInfo* info = obj->Serialize(index);
1193+
11611194
per_process::Debug(DebugCategory::MKSNAPSHOT,
11621195
"payload size=%d\n",
11631196
static_cast<int>(info->length));
@@ -1172,8 +1205,9 @@ void SerializeBindingData(Environment* env,
11721205
env->ForEachBindingData([&](FastStringKey key,
11731206
BaseObjectPtr<BaseObject> binding) {
11741207
per_process::Debug(DebugCategory::MKSNAPSHOT,
1175-
"Serialize binding %i, %p, type=%s\n",
1208+
"Serialize binding %i (%p), object=%p, type=%s\n",
11761209
static_cast<int>(i),
1210+
binding.get(),
11771211
*(binding->object()),
11781212
key.c_str());
11791213

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
@@ -124,15 +124,15 @@ void BindingData::Deserialize(Local<Context> context,
124124
Local<Object> holder,
125125
int index,
126126
InternalFieldInfo* info) {
127-
DCHECK_EQ(index, BaseObject::kSlot);
127+
DCHECK_EQ(index, BaseObject::kEmbedderType);
128128
HandleScope scope(context->GetIsolate());
129129
Environment* env = Environment::GetCurrent(context);
130130
BindingData* binding = env->AddBindingData<BindingData>(context, holder);
131131
CHECK_NOT_NULL(binding);
132132
}
133133

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

0 commit comments

Comments
 (0)