From f071028a45db110ddabd13a2eefe149574d7d652 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 | 46 ++++++++++++++++++++++++++++++++----- src/node_snapshotable.h | 5 ---- src/node_v8.cc | 4 ++-- 8 files changed, 66 insertions(+), 23 deletions(-) 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; }