mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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:
parent
9a2e6bca4f
commit
89dd09310e
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user