diff --git a/src/base_object.h b/src/base_object.h index 74af3c95274866..8dd988903aece5 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -38,11 +38,13 @@ namespace worker { class TransferData; } +extern uint16_t kNodeEmbedderId; + class BaseObject : public MemoryRetainer { public: - enum InternalFields { kSlot, kInternalFieldCount }; + enum InternalFields { kEmbedderType, kSlot, kInternalFieldCount }; - // Associates this object with `object`. It uses the 0th internal field for + // Associates this object with `object`. It uses the 1st internal field for // that, and in particular aborts if there is no such field. BaseObject(Environment* env, v8::Local object); ~BaseObject() override; diff --git a/src/env.cc b/src/env.cc index 24aeb329c593bf..aabbf57341e3c8 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1738,6 +1738,7 @@ void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb, Local holder, int index, InternalFieldInfo* info) { + DCHECK_EQ(index, BaseObject::kEmbedderType); DeserializeRequest request{cb, {isolate(), holder}, index, info}; deserialize_requests_.push_back(std::move(request)); } @@ -2017,7 +2018,9 @@ void Environment::RunWeakRefCleanup() { BaseObject::BaseObject(Environment* env, Local object) : persistent_handle_(env->isolate(), object), env_(env) { CHECK_EQ(false, object.IsEmpty()); - CHECK_GT(object->InternalFieldCount(), 0); + CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount); + object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, + &kNodeEmbedderId); object->SetAlignedPointerInInternalField(BaseObject::kSlot, static_cast(this)); env->AddCleanupHook(DeleteMe, static_cast(this)); @@ -2068,10 +2071,19 @@ void BaseObject::MakeWeak() { WeakCallbackType::kParameter); } +// This just has to be different from the Chromium ones: +// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a +// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will +// misinterpret the data stored in the embedder fields and try to garbage +// collect them. +uint16_t kNodeEmbedderId = 0x90de; + void BaseObject::LazilyInitializedJSTemplateConstructor( const FunctionCallbackInfo& args) { DCHECK(args.IsConstructCall()); - DCHECK_GT(args.This()->InternalFieldCount(), 0); + CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount); + args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, + &kNodeEmbedderId); args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr); } diff --git a/src/node_blob.cc b/src/node_blob.cc index aba95e47f353ad..4431a0e19f8860 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -465,7 +465,7 @@ void BlobBindingData::Deserialize( Local holder, int index, InternalFieldInfo* info) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); HandleScope scope(context->GetIsolate()); Environment* env = Environment::GetCurrent(context); BlobBindingData* binding = @@ -480,7 +480,7 @@ void BlobBindingData::PrepareForSerialization( } InternalFieldInfo* BlobBindingData::Serialize(int index) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = InternalFieldInfo::New(type()); return info; } diff --git a/src/node_file.cc b/src/node_file.cc index 31f58d61c0f251..78315fcdf641f6 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -2567,7 +2567,7 @@ void BindingData::Deserialize(Local context, Local holder, int index, InternalFieldInfo* info) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); HandleScope scope(context->GetIsolate()); Environment* env = Environment::GetCurrent(context); BindingData* binding = env->AddBindingData(context, holder); @@ -2584,7 +2584,7 @@ void BindingData::PrepareForSerialization(Local context, } InternalFieldInfo* BindingData::Serialize(int index) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = InternalFieldInfo::New(type()); return info; } diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index d7de7959254463..a65bb8254847f3 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -532,7 +532,7 @@ void BindingData::PrepareForSerialization(Local context, } InternalFieldInfo* BindingData::Serialize(int index) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = InternalFieldInfo::New(type()); return info; } @@ -541,7 +541,7 @@ void BindingData::Deserialize(Local context, Local holder, int index, InternalFieldInfo* info) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); v8::HandleScope scope(context->GetIsolate()); Environment* env = Environment::GetCurrent(context); // Recreate the buffer in the constructor. diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index a28bdc9ed5c4d7..f3bb4f443fc809 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1119,10 +1119,20 @@ void DeserializeNodeInternalFields(Local holder, static_cast(index), (*holder), static_cast(payload.raw_size)); + + if (payload.raw_size == 0) { + holder->SetAlignedPointerInInternalField(index, nullptr); + return; + } + + DCHECK_EQ(index, BaseObject::kEmbedderType); + Environment* env_ptr = static_cast(env); const InternalFieldInfo* info = reinterpret_cast(payload.data); - + // TODO(joyeecheung): we can add a constant kNodeEmbedderId to the + // beginning of every InternalFieldInfo to ensure that we don't + // step on payloads that were not serialized by Node.js. switch (info->type) { #define V(PropertyName, NativeTypeName) \ case EmbedderObjectType::k_##PropertyName: { \ @@ -1143,21 +1153,44 @@ void DeserializeNodeInternalFields(Local holder, StartupData SerializeNodeContextInternalFields(Local holder, int index, void* env) { - void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); - if (ptr == nullptr) { + // 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. + if (index != BaseObject::kEmbedderType) { + return StartupData{nullptr, 0}; + } + + void* type_ptr = holder->GetAlignedPointerFromInternalField(index); + if (type_ptr == nullptr) { + return StartupData{nullptr, 0}; + } + + uint16_t type = *(static_cast(type_ptr)); + per_process::Debug(DebugCategory::MKSNAPSHOT, "type = 0x%x\n", type); + if (type != kNodeEmbedderId) { return StartupData{nullptr, 0}; } + per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialize internal field, index=%d, holder=%p\n", static_cast(index), *holder); - DCHECK(static_cast(ptr)->is_snapshotable()); - SnapshotableObject* obj = static_cast(ptr); + + void* binding_ptr = + holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); + per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr); + DCHECK(static_cast(binding_ptr)->is_snapshotable()); + SnapshotableObject* obj = static_cast(binding_ptr); + per_process::Debug(DebugCategory::MKSNAPSHOT, "Object %p is %s, ", *holder, obj->GetTypeNameChars()); InternalFieldInfo* info = obj->Serialize(index); + per_process::Debug(DebugCategory::MKSNAPSHOT, "payload size=%d\n", static_cast(info->length)); @@ -1172,8 +1205,9 @@ void SerializeBindingData(Environment* env, env->ForEachBindingData([&](FastStringKey key, BaseObjectPtr binding) { per_process::Debug(DebugCategory::MKSNAPSHOT, - "Serialize binding %i, %p, type=%s\n", + "Serialize binding %i (%p), object=%p, type=%s\n", static_cast(i), + binding.get(), *(binding->object()), key.c_str()); diff --git a/src/node_snapshotable.h b/src/node_snapshotable.h index f0a8bce215e027..ec560b6d4fc633 100644 --- a/src/node_snapshotable.h +++ b/src/node_snapshotable.h @@ -30,11 +30,6 @@ enum class EmbedderObjectType : uint8_t { // When serializing an embedder object, we'll serialize the native states // into a chunk that can be mapped into a subclass of InternalFieldInfo, // and pass it into the V8 callback as the payload of StartupData. -// TODO(joyeecheung): the classification of types seem to be wrong. -// We'd need a type for each field of each class of native object. -// Maybe it's fine - we'll just use the type to invoke BaseObject constructors -// and specify that the BaseObject has only one field for us to serialize. -// And for non-BaseObject embedder objects, we'll use field-wise types. // The memory chunk looks like this: // // [ type ] - EmbedderObjectType (a uint8_t) diff --git a/src/node_v8.cc b/src/node_v8.cc index f49102e3c2f109..0f99bac0eaeaf1 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -124,7 +124,7 @@ void BindingData::Deserialize(Local context, Local holder, int index, InternalFieldInfo* info) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); HandleScope scope(context->GetIsolate()); Environment* env = Environment::GetCurrent(context); BindingData* binding = env->AddBindingData(context, holder); @@ -132,7 +132,7 @@ void BindingData::Deserialize(Local context, } InternalFieldInfo* BindingData::Serialize(int index) { - DCHECK_EQ(index, BaseObject::kSlot); + DCHECK_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = InternalFieldInfo::New(type()); return info; }