Skip to content

Commit 418a35b

Browse files
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: nodejs#43521 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Darshan Sen <[email protected]>
1 parent 9cdbf6b commit 418a35b

8 files changed

+66
-23
lines changed

src/base_object.h

Lines changed: 4 additions & 2 deletions
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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
17561756
Local<Object> holder,
17571757
int index,
17581758
InternalFieldInfo* info) {
1759+
DCHECK_EQ(index, BaseObject::kEmbedderType);
17591760
DeserializeRequest request{cb, {isolate(), holder}, index, info};
17601761
deserialize_requests_.push_back(std::move(request));
17611762
}
@@ -2038,7 +2039,9 @@ void Environment::RunWeakRefCleanup() {
20382039
BaseObject::BaseObject(Environment* env, Local<Object> object)
20392040
: persistent_handle_(env->isolate(), object), env_(env) {
20402041
CHECK_EQ(false, object.IsEmpty());
2041-
CHECK_GT(object->InternalFieldCount(), 0);
2042+
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
2043+
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2044+
&kNodeEmbedderId);
20422045
object->SetAlignedPointerInInternalField(BaseObject::kSlot,
20432046
static_cast<void*>(this));
20442047
env->AddCleanupHook(DeleteMe, static_cast<void*>(this));
@@ -2089,10 +2092,19 @@ void BaseObject::MakeWeak() {
20892092
WeakCallbackType::kParameter);
20902093
}
20912094

2095+
// This just has to be different from the Chromium ones:
2096+
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
2097+
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
2098+
// misinterpret the data stored in the embedder fields and try to garbage
2099+
// collect them.
2100+
uint16_t kNodeEmbedderId = 0x90de;
2101+
20922102
void BaseObject::LazilyInitializedJSTemplateConstructor(
20932103
const FunctionCallbackInfo<Value>& args) {
20942104
DCHECK(args.IsConstructCall());
2095-
DCHECK_GT(args.This()->InternalFieldCount(), 0);
2105+
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
2106+
args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
2107+
&kNodeEmbedderId);
20962108
args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr);
20972109
}
20982110

src/node_blob.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ void BlobBindingData::Deserialize(
467467
Local<Object> holder,
468468
int index,
469469
InternalFieldInfo* info) {
470-
DCHECK_EQ(index, BaseObject::kSlot);
470+
DCHECK_EQ(index, BaseObject::kEmbedderType);
471471
HandleScope scope(context->GetIsolate());
472472
Environment* env = Environment::GetCurrent(context);
473473
BlobBindingData* binding =
@@ -482,7 +482,7 @@ void BlobBindingData::PrepareForSerialization(
482482
}
483483

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

src/node_file.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,7 +2401,7 @@ void BindingData::Deserialize(Local<Context> context,
24012401
Local<Object> holder,
24022402
int index,
24032403
InternalFieldInfo* info) {
2404-
DCHECK_EQ(index, BaseObject::kSlot);
2404+
DCHECK_EQ(index, BaseObject::kEmbedderType);
24052405
HandleScope scope(context->GetIsolate());
24062406
Environment* env = Environment::GetCurrent(context);
24072407
BindingData* binding = env->AddBindingData<BindingData>(context, holder);
@@ -2418,7 +2418,7 @@ void BindingData::PrepareForSerialization(Local<Context> context,
24182418
}
24192419

24202420
InternalFieldInfo* BindingData::Serialize(int index) {
2421-
DCHECK_EQ(index, BaseObject::kSlot);
2421+
DCHECK_EQ(index, BaseObject::kEmbedderType);
24222422
InternalFieldInfo* info = InternalFieldInfo::New(type());
24232423
return info;
24242424
}

src/node_process_methods.cc

Lines changed: 2 additions & 2 deletions
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

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,10 +1203,20 @@ void DeserializeNodeInternalFields(Local<Object> holder,
12031203
static_cast<int>(index),
12041204
(*holder),
12051205
static_cast<int>(payload.raw_size));
1206+
1207+
if (payload.raw_size == 0) {
1208+
holder->SetAlignedPointerInInternalField(index, nullptr);
1209+
return;
1210+
}
1211+
1212+
DCHECK_EQ(index, BaseObject::kEmbedderType);
1213+
12061214
Environment* env_ptr = static_cast<Environment*>(env);
12071215
const InternalFieldInfo* info =
12081216
reinterpret_cast<const InternalFieldInfo*>(payload.data);
1209-
1217+
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
1218+
// beginning of every InternalFieldInfo to ensure that we don't
1219+
// step on payloads that were not serialized by Node.js.
12101220
switch (info->type) {
12111221
#define V(PropertyName, NativeTypeName) \
12121222
case EmbedderObjectType::k_##PropertyName: { \
@@ -1227,21 +1237,44 @@ void DeserializeNodeInternalFields(Local<Object> holder,
12271237
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
12281238
int index,
12291239
void* env) {
1230-
void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
1231-
if (ptr == nullptr) {
1240+
// We only do one serialization for the kEmbedderType slot, the result
1241+
// contains everything necessary for deserializing the entire object,
1242+
// including the fields whose index is bigger than kEmbedderType
1243+
// (most importantly, BaseObject::kSlot).
1244+
// For Node.js this design is enough for all the native binding that are
1245+
// serializable.
1246+
if (index != BaseObject::kEmbedderType) {
1247+
return StartupData{nullptr, 0};
1248+
}
1249+
1250+
void* type_ptr = holder->GetAlignedPointerFromInternalField(index);
1251+
if (type_ptr == nullptr) {
1252+
return StartupData{nullptr, 0};
1253+
}
1254+
1255+
uint16_t type = *(static_cast<uint16_t*>(type_ptr));
1256+
per_process::Debug(DebugCategory::MKSNAPSHOT, "type = 0x%x\n", type);
1257+
if (type != kNodeEmbedderId) {
12321258
return StartupData{nullptr, 0};
12331259
}
1260+
12341261
per_process::Debug(DebugCategory::MKSNAPSHOT,
12351262
"Serialize internal field, index=%d, holder=%p\n",
12361263
static_cast<int>(index),
12371264
*holder);
1238-
DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
1239-
SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
1265+
1266+
void* binding_ptr =
1267+
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
1268+
per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr);
1269+
DCHECK(static_cast<BaseObject*>(binding_ptr)->is_snapshotable());
1270+
SnapshotableObject* obj = static_cast<SnapshotableObject*>(binding_ptr);
1271+
12401272
per_process::Debug(DebugCategory::MKSNAPSHOT,
12411273
"Object %p is %s, ",
12421274
*holder,
12431275
obj->GetTypeNameChars());
12441276
InternalFieldInfo* info = obj->Serialize(index);
1277+
12451278
per_process::Debug(DebugCategory::MKSNAPSHOT,
12461279
"payload size=%d\n",
12471280
static_cast<int>(info->length));
@@ -1256,8 +1289,9 @@ void SerializeBindingData(Environment* env,
12561289
env->ForEachBindingData([&](FastStringKey key,
12571290
BaseObjectPtr<BaseObject> binding) {
12581291
per_process::Debug(DebugCategory::MKSNAPSHOT,
1259-
"Serialize binding %i, %p, type=%s\n",
1292+
"Serialize binding %i (%p), object=%p, type=%s\n",
12601293
static_cast<int>(i),
1294+
binding.get(),
12611295
*(binding->object()),
12621296
key.c_str());
12631297

src/node_snapshotable.h

Lines changed: 0 additions & 5 deletions
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

Lines changed: 2 additions & 2 deletions
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)