Skip to content

Commit 152fce6

Browse files
committed
src: serialize both BaseObject slots
We previously only return startup data for the first slot for BaseObjects because we can already serialize all the necessary information in one go, but slots that do not get special startup data would be serialized verbatim which means that the pointer addresses are going to be part of the snapshot blob, resulting in indeterminism. This patch updates the serialization routines and capture information for both of the two slots - the first slot with type information and memory management type (which we can use in the future for cppgc-managed objects) and the second slot with data about the object itself. This way the embeedder slots can be serialized in a reproducible manner in the snapshot.
1 parent d396a04 commit 152fce6

File tree

11 files changed

+84
-41
lines changed

11 files changed

+84
-41
lines changed

src/encoding_binding.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
6262
}
6363

6464
InternalFieldInfoBase* BindingData::Serialize(int index) {
65-
DCHECK_EQ(index, BaseObject::kEmbedderType);
65+
DCHECK_IS_SNAPSHOT_SLOT(index);
6666
InternalFieldInfo* info = internal_field_info_;
6767
internal_field_info_ = nullptr;
6868
return info;
@@ -72,7 +72,7 @@ void BindingData::Deserialize(Local<Context> context,
7272
Local<Object> holder,
7373
int index,
7474
InternalFieldInfoBase* info) {
75-
DCHECK_EQ(index, BaseObject::kEmbedderType);
75+
DCHECK_IS_SNAPSHOT_SLOT(index);
7676
v8::HandleScope scope(context->GetIsolate());
7777
Realm* realm = Realm::GetCurrent(context);
7878
// Recreate the buffer in the constructor.

src/env.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "node_options-inl.h"
1313
#include "node_process-inl.h"
1414
#include "node_shadow_realm.h"
15+
#include "node_snapshotable.h"
1516
#include "node_v8_platform-inl.h"
1617
#include "node_worker.h"
1718
#include "req_wrap-inl.h"
@@ -1735,7 +1736,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
17351736
Local<Object> holder,
17361737
int index,
17371738
InternalFieldInfoBase* info) {
1738-
DCHECK_EQ(index, BaseObject::kEmbedderType);
1739+
DCHECK_IS_SNAPSHOT_SLOT(index);
17391740
DeserializeRequest request{cb, {isolate(), holder}, index, info};
17401741
deserialize_requests_.push_back(std::move(request));
17411742
}

src/node_blob.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ void BlobBindingData::Deserialize(Local<Context> context,
532532
Local<Object> holder,
533533
int index,
534534
InternalFieldInfoBase* info) {
535-
DCHECK_EQ(index, BaseObject::kEmbedderType);
535+
DCHECK_IS_SNAPSHOT_SLOT(index);
536536
HandleScope scope(context->GetIsolate());
537537
Realm* realm = Realm::GetCurrent(context);
538538
BlobBindingData* binding = realm->AddBindingData<BlobBindingData>(holder);
@@ -548,7 +548,7 @@ bool BlobBindingData::PrepareForSerialization(Local<Context> context,
548548
}
549549

550550
InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
551-
DCHECK_EQ(index, BaseObject::kEmbedderType);
551+
DCHECK_IS_SNAPSHOT_SLOT(index);
552552
InternalFieldInfo* info =
553553
InternalFieldInfoBase::New<InternalFieldInfo>(type());
554554
return info;

src/node_file.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3113,7 +3113,7 @@ void BindingData::Deserialize(Local<Context> context,
31133113
Local<Object> holder,
31143114
int index,
31153115
InternalFieldInfoBase* info) {
3116-
DCHECK_EQ(index, BaseObject::kEmbedderType);
3116+
DCHECK_IS_SNAPSHOT_SLOT(index);
31173117
HandleScope scope(context->GetIsolate());
31183118
Realm* realm = Realm::GetCurrent(context);
31193119
InternalFieldInfo* casted_info = static_cast<InternalFieldInfo*>(info);
@@ -3141,7 +3141,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
31413141
}
31423142

31433143
InternalFieldInfoBase* BindingData::Serialize(int index) {
3144-
DCHECK_EQ(index, BaseObject::kEmbedderType);
3144+
DCHECK_IS_SNAPSHOT_SLOT(index);
31453145
InternalFieldInfo* info = internal_field_info_;
31463146
internal_field_info_ = nullptr;
31473147
return info;

src/node_process_methods.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
552552
}
553553

554554
InternalFieldInfoBase* BindingData::Serialize(int index) {
555-
DCHECK_EQ(index, BaseObject::kEmbedderType);
555+
DCHECK_IS_SNAPSHOT_SLOT(index);
556556
InternalFieldInfo* info =
557557
InternalFieldInfoBase::New<InternalFieldInfo>(type());
558558
return info;
@@ -562,7 +562,7 @@ void BindingData::Deserialize(Local<Context> context,
562562
Local<Object> holder,
563563
int index,
564564
InternalFieldInfoBase* info) {
565-
DCHECK_EQ(index, BaseObject::kEmbedderType);
565+
DCHECK_IS_SNAPSHOT_SLOT(index);
566566
v8::HandleScope scope(context->GetIsolate());
567567
Realm* realm = Realm::GetCurrent(context);
568568
// Recreate the buffer in the constructor.

src/node_snapshotable.cc

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,25 +1107,33 @@ std::string SnapshotableObject::GetTypeName() const {
11071107
void DeserializeNodeInternalFields(Local<Object> holder,
11081108
int index,
11091109
StartupData payload,
1110-
void* env) {
1110+
void* callback_data) {
11111111
if (payload.raw_size == 0) {
1112-
holder->SetAlignedPointerInInternalField(index, nullptr);
11131112
return;
11141113
}
1114+
11151115
per_process::Debug(DebugCategory::MKSNAPSHOT,
11161116
"Deserialize internal field %d of %p, size=%d\n",
11171117
static_cast<int>(index),
11181118
(*holder),
11191119
static_cast<int>(payload.raw_size));
11201120

1121-
if (payload.raw_size == 0) {
1122-
holder->SetAlignedPointerInInternalField(index, nullptr);
1121+
Environment* env = static_cast<Environment*>(callback_data);
1122+
1123+
// To deserialize the first field, check the type and re-tag the object.
1124+
if (index == BaseObject::kEmbedderType) {
1125+
int size = sizeof(EmbedderTypeInfo);
1126+
DCHECK_EQ(payload.raw_size, size);
1127+
EmbedderTypeInfo read_data;
1128+
memcpy(&read_data, payload.data, size);
1129+
// For now we only support non-cppgc objects.
1130+
CHECK_EQ(read_data.mode, EmbedderTypeInfo::MemoryMode::kBaseObject);
1131+
BaseObject::TagBaseObject(env->isolate_data(), holder);
11231132
return;
11241133
}
11251134

1126-
DCHECK_EQ(index, BaseObject::kEmbedderType);
1127-
1128-
Environment* env_ptr = static_cast<Environment*>(env);
1135+
// To deserialize the second field, enqueue a deserialize request.
1136+
DCHECK_IS_SNAPSHOT_SLOT(index);
11291137
const InternalFieldInfoBase* info =
11301138
reinterpret_cast<const InternalFieldInfoBase*>(payload.data);
11311139
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
@@ -1138,7 +1146,7 @@ void DeserializeNodeInternalFields(Local<Object> holder,
11381146
"Object %p is %s\n", \
11391147
(*holder), \
11401148
#NativeTypeName); \
1141-
env_ptr->EnqueueDeserializeRequest( \
1149+
env->EnqueueDeserializeRequest( \
11421150
NativeTypeName::Deserialize, \
11431151
holder, \
11441152
index, \
@@ -1164,28 +1172,52 @@ void DeserializeNodeInternalFields(Local<Object> holder,
11641172
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
11651173
int index,
11661174
void* callback_data) {
1167-
// We only do one serialization for the kEmbedderType slot, the result
1168-
// contains everything necessary for deserializing the entire object,
1169-
// including the fields whose index is bigger than kEmbedderType
1170-
// (most importantly, BaseObject::kSlot).
1171-
// For Node.js this design is enough for all the native binding that are
1172-
// serializable.
1175+
// For the moment we do not set any internal fields in ArrayBuffer
1176+
// or ArrayBufferViews, so just return nullptr.
1177+
if (holder->IsArrayBuffer() || holder->IsArrayBufferView()) {
1178+
CHECK_NULL(holder->GetAlignedPointerFromInternalField(index));
1179+
return StartupData{nullptr, 0};
1180+
}
1181+
1182+
// Use the V8 convention and serialize unknown objects verbatim.
11731183
Environment* env = static_cast<Environment*>(callback_data);
1174-
if (index != BaseObject::kEmbedderType ||
1175-
!BaseObject::IsBaseObject(env->isolate_data(), holder)) {
1184+
if (!BaseObject::IsBaseObject(env->isolate_data(), holder)) {
1185+
per_process::Debug(DebugCategory::MKSNAPSHOT,
1186+
"Serialize unknown object, index=%d, holder=%p\n",
1187+
static_cast<int>(index),
1188+
*holder);
11761189
return StartupData{nullptr, 0};
11771190
}
11781191

11791192
per_process::Debug(DebugCategory::MKSNAPSHOT,
1180-
"Serialize internal field, index=%d, holder=%p\n",
1193+
"Serialize BaseObject, index=%d, holder=%p\n",
11811194
static_cast<int>(index),
11821195
*holder);
11831196

1184-
void* native_ptr =
1185-
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
1186-
per_process::Debug(DebugCategory::MKSNAPSHOT, "native = %p\n", native_ptr);
1187-
DCHECK(static_cast<BaseObject*>(native_ptr)->is_snapshotable());
1188-
SnapshotableObject* obj = static_cast<SnapshotableObject*>(native_ptr);
1197+
BaseObject* object_ptr = static_cast<BaseObject*>(
1198+
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot));
1199+
// If the native object is already set to null, ignore it.
1200+
if (object_ptr == nullptr) {
1201+
return StartupData{nullptr, 0};
1202+
}
1203+
1204+
DCHECK(object_ptr->is_snapshotable());
1205+
SnapshotableObject* obj = static_cast<SnapshotableObject*>(object_ptr);
1206+
1207+
// To serialize the type field, save data in a EmbedderTypeInfo.
1208+
if (index == BaseObject::kEmbedderType) {
1209+
int size = sizeof(EmbedderTypeInfo);
1210+
char* data = new char[size];
1211+
// We need to use placement new because V8 calls delete[] on the returned
1212+
// data.
1213+
// TODO(joyeecheung): support cppgc objects.
1214+
new (data) EmbedderTypeInfo(obj->type(),
1215+
EmbedderTypeInfo::MemoryMode::kBaseObject);
1216+
return StartupData{data, size};
1217+
}
1218+
1219+
// To serialize the slot field, invoke Serialize() method on the object.
1220+
DCHECK_IS_SNAPSHOT_SLOT(index);
11891221

11901222
per_process::Debug(DebugCategory::MKSNAPSHOT,
11911223
"Object %p is %s, ",
@@ -1341,7 +1373,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
13411373
}
13421374

13431375
InternalFieldInfoBase* BindingData::Serialize(int index) {
1344-
DCHECK_EQ(index, BaseObject::kEmbedderType);
1376+
DCHECK_IS_SNAPSHOT_SLOT(index);
13451377
InternalFieldInfo* info = internal_field_info_;
13461378
internal_field_info_ = nullptr;
13471379
return info;
@@ -1351,7 +1383,7 @@ void BindingData::Deserialize(Local<Context> context,
13511383
Local<Object> holder,
13521384
int index,
13531385
InternalFieldInfoBase* info) {
1354-
DCHECK_EQ(index, BaseObject::kEmbedderType);
1386+
DCHECK_IS_SNAPSHOT_SLOT(index);
13551387
v8::HandleScope scope(context->GetIsolate());
13561388
Realm* realm = Realm::GetCurrent(context);
13571389
// Recreate the buffer in the constructor.

src/node_snapshotable.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ struct InternalFieldInfoBase {
6868
InternalFieldInfoBase() = default;
6969
};
7070

71+
struct EmbedderTypeInfo {
72+
enum class MemoryMode : uint8_t { kBaseObject, kCppGC };
73+
EmbedderTypeInfo(EmbedderObjectType t, MemoryMode m) : type(t), mode(m) {}
74+
EmbedderTypeInfo() = default;
75+
EmbedderObjectType type;
76+
MemoryMode mode;
77+
};
78+
7179
// An interface for snapshotable native objects to inherit from.
7280
// Use the SERIALIZABLE_OBJECT_METHODS() macro in the class to define
7381
// the following methods to implement:
@@ -123,6 +131,8 @@ void SerializeSnapshotableObjects(Realm* realm,
123131
v8::SnapshotCreator* creator,
124132
RealmSerializeInfo* info);
125133

134+
#define DCHECK_IS_SNAPSHOT_SLOT(index) DCHECK_EQ(index, BaseObject::kSlot)
135+
126136
namespace mksnapshot {
127137
class BindingData : public SnapshotableObject {
128138
public:

src/node_url.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context,
5454
}
5555

5656
InternalFieldInfoBase* BindingData::Serialize(int index) {
57-
DCHECK_EQ(index, BaseObject::kEmbedderType);
57+
DCHECK_IS_SNAPSHOT_SLOT(index);
5858
InternalFieldInfo* info =
5959
InternalFieldInfoBase::New<InternalFieldInfo>(type());
6060
return info;
@@ -64,7 +64,7 @@ void BindingData::Deserialize(v8::Local<v8::Context> context,
6464
v8::Local<v8::Object> holder,
6565
int index,
6666
InternalFieldInfoBase* info) {
67-
DCHECK_EQ(index, BaseObject::kEmbedderType);
67+
DCHECK_IS_SNAPSHOT_SLOT(index);
6868
v8::HandleScope scope(context->GetIsolate());
6969
Realm* realm = Realm::GetCurrent(context);
7070
BindingData* binding = realm->AddBindingData<BindingData>(holder);

src/node_util.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ bool WeakReference::PrepareForSerialization(Local<Context> context,
226226
}
227227

228228
InternalFieldInfoBase* WeakReference::Serialize(int index) {
229-
DCHECK_EQ(index, BaseObject::kEmbedderType);
229+
DCHECK_IS_SNAPSHOT_SLOT(index);
230230
InternalFieldInfo* info =
231231
InternalFieldInfoBase::New<InternalFieldInfo>(type());
232232
info->target = target_index_;
@@ -238,7 +238,7 @@ void WeakReference::Deserialize(Local<Context> context,
238238
Local<Object> holder,
239239
int index,
240240
InternalFieldInfoBase* info) {
241-
DCHECK_EQ(index, BaseObject::kEmbedderType);
241+
DCHECK_IS_SNAPSHOT_SLOT(index);
242242
HandleScope scope(context->GetIsolate());
243243

244244
InternalFieldInfo* weak_info = reinterpret_cast<InternalFieldInfo*>(info);

src/node_v8.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void BindingData::Deserialize(Local<Context> context,
152152
Local<Object> holder,
153153
int index,
154154
InternalFieldInfoBase* info) {
155-
DCHECK_EQ(index, BaseObject::kEmbedderType);
155+
DCHECK_IS_SNAPSHOT_SLOT(index);
156156
HandleScope scope(context->GetIsolate());
157157
Realm* realm = Realm::GetCurrent(context);
158158
// Recreate the buffer in the constructor.
@@ -163,7 +163,7 @@ void BindingData::Deserialize(Local<Context> context,
163163
}
164164

165165
InternalFieldInfoBase* BindingData::Serialize(int index) {
166-
DCHECK_EQ(index, BaseObject::kEmbedderType);
166+
DCHECK_IS_SNAPSHOT_SLOT(index);
167167
InternalFieldInfo* info = internal_field_info_;
168168
internal_field_info_ = nullptr;
169169
return info;

0 commit comments

Comments
 (0)