Skip to content

Commit b68fa59

Browse files
committed
src: use effective cppgc wrapper id to deduce non-cppgc id
Previously we hard-code a wrapper id to be used in BaseObjects to avoid accidentally triggering cppgc on these non-cppgc-managed objects, but hard-coding can be be hacky and result in mismatch when we start to create CppHeap ourselves. This patch makes it more robust by deducing non-cppgc id from the effective cppgc id, if there is one. PR-URL: #48660 Refs: v8/v8@9327503 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
1 parent 44027fb commit b68fa59

11 files changed

+230
-29
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@
10471047
'test/cctest/test_aliased_buffer.cc',
10481048
'test/cctest/test_base64.cc',
10491049
'test/cctest/test_base_object_ptr.cc',
1050+
'test/cctest/test_cppgc.cc',
10501051
'test/cctest/test_node_postmortem_metadata.cc',
10511052
'test/cctest/test_environment.cc',
10521053
'test/cctest/test_linked_binding.cc',

src/base_object-inl.h

+14-9
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,28 @@ Realm* BaseObject::realm() const {
7070
return realm_;
7171
}
7272

73-
bool BaseObject::IsBaseObject(v8::Local<v8::Object> obj) {
73+
bool BaseObject::IsBaseObject(IsolateData* isolate_data,
74+
v8::Local<v8::Object> obj) {
7475
if (obj->InternalFieldCount() < BaseObject::kInternalFieldCount) {
7576
return false;
7677
}
77-
void* ptr =
78-
obj->GetAlignedPointerFromInternalField(BaseObject::kEmbedderType);
79-
return ptr == &kNodeEmbedderId;
78+
79+
uint16_t* ptr = static_cast<uint16_t*>(
80+
obj->GetAlignedPointerFromInternalField(BaseObject::kEmbedderType));
81+
return ptr == isolate_data->embedder_id_for_non_cppgc();
8082
}
8183

82-
void BaseObject::TagBaseObject(v8::Local<v8::Object> object) {
84+
void BaseObject::TagBaseObject(IsolateData* isolate_data,
85+
v8::Local<v8::Object> object) {
8386
DCHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
84-
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
85-
&kNodeEmbedderId);
87+
object->SetAlignedPointerInInternalField(
88+
BaseObject::kEmbedderType, isolate_data->embedder_id_for_non_cppgc());
8689
}
8790

88-
void BaseObject::SetInternalFields(v8::Local<v8::Object> object, void* slot) {
89-
TagBaseObject(object);
91+
void BaseObject::SetInternalFields(IsolateData* isolate_data,
92+
v8::Local<v8::Object> object,
93+
void* slot) {
94+
TagBaseObject(isolate_data, object);
9095
object->SetAlignedPointerInInternalField(BaseObject::kSlot, slot);
9196
}
9297

src/base_object.cc

+4-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ BaseObject::BaseObject(Realm* realm, Local<Object> object)
2222
: persistent_handle_(realm->isolate(), object), realm_(realm) {
2323
CHECK_EQ(false, object.IsEmpty());
2424
CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
25-
SetInternalFields(object, static_cast<void*>(this));
25+
SetInternalFields(realm->isolate_data(), object, static_cast<void*>(this));
2626
realm->AddCleanupHook(DeleteMe, static_cast<void*>(this));
2727
realm->modify_base_object_count(1);
2828
}
@@ -71,18 +71,13 @@ void BaseObject::MakeWeak() {
7171
WeakCallbackType::kParameter);
7272
}
7373

74-
// This just has to be different from the Chromium ones:
75-
// https://source.chromium.org/chromium/chromium/src/+/main:gin/public/gin_embedders.h;l=18-23;drc=5a758a97032f0b656c3c36a3497560762495501a
76-
// Otherwise, when Node is loaded in an isolate which uses cppgc, cppgc will
77-
// misinterpret the data stored in the embedder fields and try to garbage
78-
// collect them.
79-
uint16_t kNodeEmbedderId = 0x90de;
80-
8174
void BaseObject::LazilyInitializedJSTemplateConstructor(
8275
const FunctionCallbackInfo<Value>& args) {
8376
DCHECK(args.IsConstructCall());
8477
CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount);
85-
SetInternalFields(args.This(), nullptr);
78+
Environment* env = Environment::GetCurrent(args);
79+
DCHECK_NOT_NULL(env);
80+
SetInternalFields(env->isolate_data(), args.This(), nullptr);
8681
}
8782

8883
Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(

src/base_object.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ namespace worker {
4141
class TransferData;
4242
}
4343

44-
extern uint16_t kNodeEmbedderId;
45-
4644
class BaseObject : public MemoryRetainer {
4745
public:
4846
enum InternalFields { kEmbedderType, kSlot, kInternalFieldCount };
@@ -74,10 +72,13 @@ class BaseObject : public MemoryRetainer {
7472
// was also passed to the `BaseObject()` constructor initially.
7573
// This may return `nullptr` if the C++ object has not been constructed yet,
7674
// e.g. when the JS object used `MakeLazilyInitializedJSTemplate`.
77-
static inline void SetInternalFields(v8::Local<v8::Object> object,
75+
static inline void SetInternalFields(IsolateData* isolate_data,
76+
v8::Local<v8::Object> object,
7877
void* slot);
79-
static inline bool IsBaseObject(v8::Local<v8::Object> object);
80-
static inline void TagBaseObject(v8::Local<v8::Object> object);
78+
static inline bool IsBaseObject(IsolateData* isolate_data,
79+
v8::Local<v8::Object> object);
80+
static inline void TagBaseObject(IsolateData* isolate_data,
81+
v8::Local<v8::Object> object);
8182
static void LazilyInitializedJSTemplateConstructor(
8283
const v8::FunctionCallbackInfo<v8::Value>& args);
8384
static inline BaseObject* FromJSObject(v8::Local<v8::Value> object);

src/env-inl.h

+8
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ inline uv_loop_t* IsolateData::event_loop() const {
6161
return event_loop_;
6262
}
6363

64+
inline uint16_t* IsolateData::embedder_id_for_cppgc() const {
65+
return &(wrapper_data_->cppgc_id);
66+
}
67+
68+
inline uint16_t* IsolateData::embedder_id_for_non_cppgc() const {
69+
return &(wrapper_data_->non_cppgc_id);
70+
}
71+
6472
inline NodeArrayBufferAllocator* IsolateData::node_allocator() const {
6573
return node_allocator_;
6674
}

src/env.cc

+47
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "tracing/agent.h"
2020
#include "tracing/traced_value.h"
2121
#include "util-inl.h"
22+
#include "v8-cppgc.h"
2223
#include "v8-profiler.h"
2324

2425
#include <algorithm>
@@ -28,6 +29,7 @@
2829
#include <iostream>
2930
#include <limits>
3031
#include <memory>
32+
#include <unordered_map>
3133

3234
namespace node {
3335

@@ -497,6 +499,11 @@ void IsolateData::CreateProperties() {
497499
contextify::ContextifyContext::InitializeGlobalTemplates(this);
498500
}
499501

502+
constexpr uint16_t kDefaultCppGCEmebdderID = 0x90de;
503+
Mutex IsolateData::isolate_data_mutex_;
504+
std::unordered_map<uint16_t, std::unique_ptr<PerIsolateWrapperData>>
505+
IsolateData::wrapper_data_map_;
506+
500507
IsolateData::IsolateData(Isolate* isolate,
501508
uv_loop_t* event_loop,
502509
MultiIsolatePlatform* platform,
@@ -510,6 +517,46 @@ IsolateData::IsolateData(Isolate* isolate,
510517
snapshot_data_(snapshot_data) {
511518
options_.reset(
512519
new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
520+
v8::CppHeap* cpp_heap = isolate->GetCppHeap();
521+
522+
uint16_t cppgc_id = kDefaultCppGCEmebdderID;
523+
if (cpp_heap != nullptr) {
524+
// The general convention of the wrappable layout for cppgc in the
525+
// ecosystem is:
526+
// [ 0 ] -> embedder id
527+
// [ 1 ] -> wrappable instance
528+
// If the Isolate includes a CppHeap attached by another embedder,
529+
// And if they also use the field 0 for the ID, we DCHECK that
530+
// the layout matches our layout, and record the embedder ID for cppgc
531+
// to avoid accidentally enabling cppgc on non-cppgc-managed wrappers .
532+
v8::WrapperDescriptor descriptor = cpp_heap->wrapper_descriptor();
533+
if (descriptor.wrappable_type_index == BaseObject::kEmbedderType) {
534+
cppgc_id = descriptor.embedder_id_for_garbage_collected;
535+
DCHECK_EQ(descriptor.wrappable_instance_index, BaseObject::kSlot);
536+
}
537+
// If the CppHeap uses the slot we use to put non-cppgc-traced BaseObject
538+
// for embedder ID, V8 could accidentally enable cppgc on them. So
539+
// safe guard against this.
540+
DCHECK_NE(descriptor.wrappable_type_index, BaseObject::kSlot);
541+
}
542+
// We do not care about overflow since we just want this to be different
543+
// from the cppgc id.
544+
uint16_t non_cppgc_id = cppgc_id + 1;
545+
546+
{
547+
// GC could still be run after the IsolateData is destroyed, so we store
548+
// the ids in a static map to ensure pointers to them are still valid
549+
// then. In practice there should be very few variants of the cppgc id
550+
// in one process so the size of this map should be very small.
551+
node::Mutex::ScopedLock lock(isolate_data_mutex_);
552+
auto it = wrapper_data_map_.find(cppgc_id);
553+
if (it == wrapper_data_map_.end()) {
554+
auto pair = wrapper_data_map_.emplace(
555+
cppgc_id, new PerIsolateWrapperData{cppgc_id, non_cppgc_id});
556+
it = pair.first;
557+
}
558+
wrapper_data_ = it->second.get();
559+
}
513560

514561
if (snapshot_data == nullptr) {
515562
CreateProperties();

src/env.h

+14
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,19 @@ struct IsolateDataSerializeInfo {
124124
const IsolateDataSerializeInfo& i);
125125
};
126126

127+
struct PerIsolateWrapperData {
128+
uint16_t cppgc_id;
129+
uint16_t non_cppgc_id;
130+
};
131+
127132
class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
128133
public:
129134
IsolateData(v8::Isolate* isolate,
130135
uv_loop_t* event_loop,
131136
MultiIsolatePlatform* platform = nullptr,
132137
ArrayBufferAllocator* node_allocator = nullptr,
133138
const SnapshotData* snapshot_data = nullptr);
139+
134140
SET_MEMORY_INFO_NAME(IsolateData)
135141
SET_SELF_SIZE(IsolateData)
136142
void MemoryInfo(MemoryTracker* tracker) const override;
@@ -139,6 +145,9 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
139145
bool is_building_snapshot() const { return is_building_snapshot_; }
140146
void set_is_building_snapshot(bool value) { is_building_snapshot_ = value; }
141147

148+
uint16_t* embedder_id_for_cppgc() const;
149+
uint16_t* embedder_id_for_non_cppgc() const;
150+
142151
inline uv_loop_t* event_loop() const;
143152
inline MultiIsolatePlatform* platform() const;
144153
inline const SnapshotData* snapshot_data() const;
@@ -223,6 +232,11 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
223232
std::shared_ptr<PerIsolateOptions> options_;
224233
worker::Worker* worker_context_ = nullptr;
225234
bool is_building_snapshot_ = false;
235+
PerIsolateWrapperData* wrapper_data_;
236+
237+
static Mutex isolate_data_mutex_;
238+
static std::unordered_map<uint16_t, std::unique_ptr<PerIsolateWrapperData>>
239+
wrapper_data_map_;
226240
};
227241

228242
struct ContextInfo {

src/node_messaging.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,15 @@ class SerializerDelegate : public ValueSerializer::Delegate {
307307
bool HasCustomHostObject(Isolate* isolate) override { return true; }
308308

309309
Maybe<bool> IsHostObject(Isolate* isolate, Local<Object> object) override {
310-
if (BaseObject::IsBaseObject(object)) {
310+
if (BaseObject::IsBaseObject(env_->isolate_data(), object)) {
311311
return Just(true);
312312
}
313313

314314
return Just(JSTransferable::IsJSTransferable(env_, context_, object));
315315
}
316316

317317
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override {
318-
if (BaseObject::IsBaseObject(object)) {
318+
if (BaseObject::IsBaseObject(env_->isolate_data(), object)) {
319319
return WriteHostObject(
320320
BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(object) });
321321
}
@@ -529,7 +529,7 @@ Maybe<bool> Message::Serialize(Environment* env,
529529
return Nothing<bool>();
530530
}
531531
BaseObjectPtr<BaseObject> host_object;
532-
if (BaseObject::IsBaseObject(entry)) {
532+
if (BaseObject::IsBaseObject(env->isolate_data(), entry)) {
533533
host_object = BaseObjectPtr<BaseObject>{Unwrap<BaseObject>(entry)};
534534
} else {
535535
if (!JSTransferable::IsJSTransferable(env, context, entry)) {
@@ -1328,7 +1328,7 @@ JSTransferable::NestedTransferables() const {
13281328
continue;
13291329
}
13301330
Local<Object> obj = value.As<Object>();
1331-
if (BaseObject::IsBaseObject(obj)) {
1331+
if (BaseObject::IsBaseObject(env()->isolate_data(), obj)) {
13321332
ret.emplace_back(Unwrap<BaseObject>(obj));
13331333
continue;
13341334
}

src/node_snapshotable.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -1164,14 +1164,16 @@ void DeserializeNodeInternalFields(Local<Object> holder,
11641164

11651165
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
11661166
int index,
1167-
void* env) {
1167+
void* callback_data) {
11681168
// We only do one serialization for the kEmbedderType slot, the result
11691169
// contains everything necessary for deserializing the entire object,
11701170
// including the fields whose index is bigger than kEmbedderType
11711171
// (most importantly, BaseObject::kSlot).
11721172
// For Node.js this design is enough for all the native binding that are
11731173
// serializable.
1174-
if (index != BaseObject::kEmbedderType || !BaseObject::IsBaseObject(holder)) {
1174+
Environment* env = static_cast<Environment*>(callback_data);
1175+
if (index != BaseObject::kEmbedderType ||
1176+
!BaseObject::IsBaseObject(env->isolate_data(), holder)) {
11751177
return StartupData{nullptr, 0};
11761178
}
11771179

test/cctest/node_test_fixture.cc

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void NodeTestEnvironment::SetUp() {
3434
}
3535

3636
void NodeTestEnvironment::TearDown() {
37+
cppgc::ShutdownProcess();
3738
v8::V8::Dispose();
3839
v8::V8::DisposePlatform();
3940
NodeZeroIsolateTestFixture::platform->Shutdown();

0 commit comments

Comments
 (0)