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.

PR-URL: https://github.com/nodejs/node/pull/48996
Refs: https://github.com/nodejs/build/issues/3043
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
Joyee Cheung 2023-08-15 20:32:14 +02:00 committed by GitHub
parent 9a2e6bca4f
commit 89dd09310e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 41 deletions

View File

@ -62,7 +62,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info = internal_field_info_;
internal_field_info_ = nullptr;
return info;
@ -72,7 +72,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
v8::HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
// Recreate the buffer in the constructor.

View File

@ -12,6 +12,7 @@
#include "node_options-inl.h"
#include "node_process-inl.h"
#include "node_shadow_realm.h"
#include "node_snapshotable.h"
#include "node_v8_platform-inl.h"
#include "node_worker.h"
#include "req_wrap-inl.h"
@ -1761,7 +1762,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
DeserializeRequest request{cb, {isolate(), holder}, index, info};
deserialize_requests_.push_back(std::move(request));
}

View File

@ -532,7 +532,7 @@ void BlobBindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
BlobBindingData* binding = realm->AddBindingData<BlobBindingData>(holder);
@ -548,7 +548,7 @@ bool BlobBindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BlobBindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;

View File

@ -3116,7 +3116,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
InternalFieldInfo* casted_info = static_cast<InternalFieldInfo*>(info);
@ -3144,7 +3144,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info = internal_field_info_;
internal_field_info_ = nullptr;
return info;

View File

@ -552,7 +552,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
@ -562,7 +562,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
v8::HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
// Recreate the buffer in the constructor.

View File

@ -1146,25 +1146,33 @@ std::string SnapshotableObject::GetTypeName() const {
void DeserializeNodeInternalFields(Local<Object> holder,
int index,
StartupData payload,
void* env) {
void* callback_data) {
if (payload.raw_size == 0) {
holder->SetAlignedPointerInInternalField(index, nullptr);
return;
}
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Deserialize internal field %d of %p, size=%d\n",
static_cast<int>(index),
(*holder),
static_cast<int>(payload.raw_size));
if (payload.raw_size == 0) {
holder->SetAlignedPointerInInternalField(index, nullptr);
Environment* env = static_cast<Environment*>(callback_data);
// To deserialize the first field, check the type and re-tag the object.
if (index == BaseObject::kEmbedderType) {
int size = sizeof(EmbedderTypeInfo);
DCHECK_EQ(payload.raw_size, size);
EmbedderTypeInfo read_data;
memcpy(&read_data, payload.data, size);
// For now we only support non-cppgc objects.
CHECK_EQ(read_data.mode, EmbedderTypeInfo::MemoryMode::kBaseObject);
BaseObject::TagBaseObject(env->isolate_data(), holder);
return;
}
DCHECK_EQ(index, BaseObject::kEmbedderType);
Environment* env_ptr = static_cast<Environment*>(env);
// To deserialize the second field, enqueue a deserialize request.
DCHECK_IS_SNAPSHOT_SLOT(index);
const InternalFieldInfoBase* info =
reinterpret_cast<const InternalFieldInfoBase*>(payload.data);
// TODO(joyeecheung): we can add a constant kNodeEmbedderId to the
@ -1177,7 +1185,7 @@ void DeserializeNodeInternalFields(Local<Object> holder,
"Object %p is %s\n", \
(*holder), \
#NativeTypeName); \
env_ptr->EnqueueDeserializeRequest( \
env->EnqueueDeserializeRequest( \
NativeTypeName::Deserialize, \
holder, \
index, \
@ -1203,28 +1211,52 @@ void DeserializeNodeInternalFields(Local<Object> holder,
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
int index,
void* callback_data) {
// We only do one serialization for the kEmbedderType slot, the result
// contains everything necessary for deserializing the entire object,
// including the fields whose index is bigger than kEmbedderType
// (most importantly, BaseObject::kSlot).
// For Node.js this design is enough for all the native binding that are
// serializable.
// For the moment we do not set any internal fields in ArrayBuffer
// or ArrayBufferViews, so just return nullptr.
if (holder->IsArrayBuffer() || holder->IsArrayBufferView()) {
CHECK_NULL(holder->GetAlignedPointerFromInternalField(index));
return StartupData{nullptr, 0};
}
// Use the V8 convention and serialize unknown objects verbatim.
Environment* env = static_cast<Environment*>(callback_data);
if (index != BaseObject::kEmbedderType ||
!BaseObject::IsBaseObject(env->isolate_data(), holder)) {
if (!BaseObject::IsBaseObject(env->isolate_data(), holder)) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize unknown object, index=%d, holder=%p\n",
static_cast<int>(index),
*holder);
return StartupData{nullptr, 0};
}
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize internal field, index=%d, holder=%p\n",
"Serialize BaseObject, index=%d, holder=%p\n",
static_cast<int>(index),
*holder);
void* native_ptr =
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
per_process::Debug(DebugCategory::MKSNAPSHOT, "native = %p\n", native_ptr);
DCHECK(static_cast<BaseObject*>(native_ptr)->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(native_ptr);
BaseObject* object_ptr = static_cast<BaseObject*>(
holder->GetAlignedPointerFromInternalField(BaseObject::kSlot));
// If the native object is already set to null, ignore it.
if (object_ptr == nullptr) {
return StartupData{nullptr, 0};
}
DCHECK(object_ptr->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(object_ptr);
// To serialize the type field, save data in a EmbedderTypeInfo.
if (index == BaseObject::kEmbedderType) {
int size = sizeof(EmbedderTypeInfo);
char* data = new char[size];
// We need to use placement new because V8 calls delete[] on the returned
// data.
// TODO(joyeecheung): support cppgc objects.
new (data) EmbedderTypeInfo(obj->type(),
EmbedderTypeInfo::MemoryMode::kBaseObject);
return StartupData{data, size};
}
// To serialize the slot field, invoke Serialize() method on the object.
DCHECK_IS_SNAPSHOT_SLOT(index);
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Object %p is %s, ",
@ -1380,7 +1412,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info = internal_field_info_;
internal_field_info_ = nullptr;
return info;
@ -1390,7 +1422,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
v8::HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
// Recreate the buffer in the constructor.

View File

@ -68,6 +68,14 @@ struct InternalFieldInfoBase {
InternalFieldInfoBase() = default;
};
struct EmbedderTypeInfo {
enum class MemoryMode : uint8_t { kBaseObject, kCppGC };
EmbedderTypeInfo(EmbedderObjectType t, MemoryMode m) : type(t), mode(m) {}
EmbedderTypeInfo() = default;
EmbedderObjectType type;
MemoryMode mode;
};
// An interface for snapshotable native objects to inherit from.
// Use the SERIALIZABLE_OBJECT_METHODS() macro in the class to define
// the following methods to implement:
@ -123,6 +131,8 @@ void SerializeSnapshotableObjects(Realm* realm,
v8::SnapshotCreator* creator,
RealmSerializeInfo* info);
#define DCHECK_IS_SNAPSHOT_SLOT(index) DCHECK_EQ(index, BaseObject::kSlot)
namespace mksnapshot {
class BindingData : public SnapshotableObject {
public:

View File

@ -54,7 +54,7 @@ bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
@ -64,7 +64,7 @@ void BindingData::Deserialize(v8::Local<v8::Context> context,
v8::Local<v8::Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
v8::HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
BindingData* binding = realm->AddBindingData<BindingData>(holder);

View File

@ -246,7 +246,7 @@ bool WeakReference::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* WeakReference::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
info->target = target_index_;
@ -258,7 +258,7 @@ void WeakReference::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
HandleScope scope(context->GetIsolate());
InternalFieldInfo* weak_info = reinterpret_cast<InternalFieldInfo*>(info);

View File

@ -152,7 +152,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
// Recreate the buffer in the constructor.
@ -163,7 +163,7 @@ void BindingData::Deserialize(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info = internal_field_info_;
internal_field_info_ = nullptr;
return info;

View File

@ -94,7 +94,7 @@ bool BindingData::PrepareForSerialization(Local<Context> context,
}
InternalFieldInfoBase* BindingData::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
return info;
@ -104,7 +104,7 @@ void BindingData::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
DCHECK_IS_SNAPSHOT_SLOT(index);
v8::HandleScope scope(context->GetIsolate());
Realm* realm = Realm::GetCurrent(context);
// Recreate the buffer in the constructor.