From 70662f40582ba28da582bfe5488227a6e4562f41 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Tue, 21 Jun 2022 14:08:41 +0200 Subject: [PATCH] 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 PR-URL: https://github.com/nodejs/node/pull/43521 Reviewed-By: James M Snell Reviewed-By: Darshan Sen --- src/base_object.h | 6 ++++-- src/env.cc | 16 ++++++++++++-- src/node_blob.cc | 4 ++-- src/node_file.cc | 4 ++-- src/node_process_methods.cc | 4 ++-- src/node_snapshotable.cc | 43 ++++++++++++++++++++++++++++++------- src/node_snapshotable.h | 5 ----- src/node_v8.cc | 4 ++-- 8 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/base_object.h b/src/base_object.h index 842f763a56d75c..6f072bf5ec0357 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 26acb9cfb1c04d..77b894bd361b2b 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1836,6 +1836,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)); } @@ -2129,7 +2130,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)); @@ -2180,10 +2183,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 ee61437f402fcd..142ba7465a47fd 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -464,7 +464,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 = @@ -479,7 +479,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 5c9e1216ea4668..33510afc29687f 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 bc5d41382f20f1..6f8b42be01063a 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -533,7 +533,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; } @@ -542,7 +542,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 15b488e9e2cce4..391b32424fbbaa 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -290,10 +290,14 @@ void DeserializeNodeInternalFields(Local holder, 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: { \ @@ -314,22 +318,44 @@ void DeserializeNodeInternalFields(Local holder, StartupData SerializeNodeContextInternalFields(Local holder, int index, void* env) { + // 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); - void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); - if (ptr == nullptr) { - return StartupData{nullptr, 0}; - } - 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)); @@ -344,8 +370,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 9c72ddaf5293ec..70b294f1eca5de 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -123,7 +123,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); @@ -131,7 +131,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; }