mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
src: add helpers for creating cppgc-managed wrappers
This patch adds helpers for wrapper classes based on cppgc (Oilpan) in `src/cppgc_helpers.h`, including `node::CppgcMixin` and `ASSIGN_OR_RETURN_UNWRAP_CPPGC`, which are designed to have similar interface to BaseObject helpers to help migration. They are documented in the `CppgcMixin` section in `src/README.md` To disambiguate, the global `node::Unwrap<>` has now been moved as `node::BaseObject::Unwrap<>`, and `node::Cppgc::Unwrap<>` implements a similar unwrapping mechanism for cppgc-managed wrappers. PR-URL: https://github.com/nodejs/node/pull/52295 Refs: https://github.com/nodejs/node/issues/40786 Refs: https://docs.google.com/document/d/1ny2Qz_EsUnXGKJRGxoA-FXIE2xpLgaMAN6jD7eAkqFQ/edit Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
parent
4568df4c6d
commit
849db10fb3
1
node.gyp
1
node.gyp
@ -202,6 +202,7 @@
|
||||
'src/compile_cache.h',
|
||||
'src/connect_wrap.h',
|
||||
'src/connection_wrap.h',
|
||||
'src/cppgc_helpers.h',
|
||||
'src/dataqueue/queue.h',
|
||||
'src/debug_utils.h',
|
||||
'src/debug_utils-inl.h',
|
||||
|
225
src/README.md
225
src/README.md
@ -975,6 +975,228 @@ overview over libuv handles managed by Node.js.
|
||||
|
||||
<a id="callback-scopes"></a>
|
||||
|
||||
### `CppgcMixin`
|
||||
|
||||
V8 comes with a trace-based C++ garbage collection library called
|
||||
[Oilpan][], whose API is in headers under`deps/v8/include/cppgc`.
|
||||
In this document we refer to it as `cppgc` since that's the namespace
|
||||
of the library.
|
||||
|
||||
C++ objects managed using `cppgc` are allocated in the V8 heap
|
||||
and traced by V8's garbage collector. The `cppgc` library provides
|
||||
APIs for embedders to create references between cppgc-managed objects
|
||||
and other objects in the V8 heap (such as JavaScript objects or other
|
||||
objects in the V8 C++ API that can be passed around with V8 handles)
|
||||
in a way that's understood by V8's garbage collector.
|
||||
This helps avoiding accidental memory leaks and use-after-frees coming
|
||||
from incorrect cross-heap reference tracking, especially when there are
|
||||
cyclic references. This is what powers the
|
||||
[unified heap design in Chromium][] to avoid cross-heap memory issues,
|
||||
and it's being rolled out in Node.js to reap similar benefits.
|
||||
|
||||
For general guidance on how to use `cppgc`, see the
|
||||
[Oilpan documentation in Chromium][]. In Node.js there is a helper
|
||||
mixin `node::CppgcMixin` from `cppgc_helpers.h` to help implementing
|
||||
`cppgc`-managed wrapper objects with a [`BaseObject`][]-like interface.
|
||||
`cppgc`-manged objects in Node.js internals should extend this mixin,
|
||||
while non-`cppgc`-managed objects typically extend `BaseObject` - the
|
||||
latter are being migrated to be `cppgc`-managed wherever it's beneficial
|
||||
and practical. Typically `cppgc`-managed objects are more efficient to
|
||||
keep track of (which lowers initialization cost) and work better
|
||||
with V8's GC scheduling.
|
||||
|
||||
A `cppgc`-managed native wrapper should look something like this:
|
||||
|
||||
```cpp
|
||||
#include "cppgc_helpers.h"
|
||||
|
||||
// CPPGC_MIXIN is a helper macro for inheriting from cppgc::GarbageCollected,
|
||||
// cppgc::NameProvider and public CppgcMixin. Per cppgc rules, it must be
|
||||
// placed at the left-most position in the class hierarchy.
|
||||
class MyWrap final : CPPGC_MIXIN(ContextifyScript) {
|
||||
public:
|
||||
SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
|
||||
|
||||
// The constructor can only be called by `cppgc::MakeGarbageCollected()`.
|
||||
MyWrap(Environment* env, v8::Local<v8::Object> object);
|
||||
|
||||
// Helper for constructing MyWrap via `cppgc::MakeGarbageCollected()`.
|
||||
// Can be invoked by other C++ code outside of this class if necessary.
|
||||
// In that case the raw pointer returned may need to be managed by
|
||||
// cppgc::Persistent<> or cppgc::Member<> with corresponding tracing code.
|
||||
static MyWrap* New(Environment* env, v8::Local<v8::Object> object);
|
||||
// Binding method to help constructing MyWrap in JavaScript.
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
void Trace(cppgc::Visitor* visitor) const final;
|
||||
}
|
||||
```
|
||||
|
||||
`cppgc::GarbageCollected` types are expected to implement a
|
||||
`void Trace(cppgc::Visitor* visitor) const` method. When they are the
|
||||
final class in the hierarchy, this method must be marked `final`. For
|
||||
classes extending `node::CppgcMixn`, this should typically dispatch a
|
||||
call to `CppgcMixin::Trace()` first, then trace any additional owned data
|
||||
it has. See `deps/v8/include/cppgc/garbage-collected.h` see what types of
|
||||
data can be traced.
|
||||
|
||||
```cpp
|
||||
void MyWrap::Trace(cppgc::Visitor* visitor) const {
|
||||
CppgcMixin::Trace(visitor);
|
||||
visitor->Trace(...); // Trace any additional data MyWrap has
|
||||
}
|
||||
```
|
||||
|
||||
#### Constructing and wrapping `cppgc`-managed objects
|
||||
|
||||
C++ objects subclassing `node::CppgcMixin` have a counterpart JavaScript object.
|
||||
The two references each other internally - this cycle is well-understood by V8's
|
||||
garbage collector and can be managed properly.
|
||||
|
||||
Similar to `BaseObject`s, `cppgc`-managed wrappers objects must be created from
|
||||
object templates with at least `node::CppgcMixin::kInternalFieldCount` internal
|
||||
fields. To unify handling of the wrappers, the internal fields of
|
||||
`node::CppgcMixin` wrappers would have the same layout as `BaseObject`.
|
||||
|
||||
```cpp
|
||||
// To create the v8::FunctionTemplate that can be used to instantiate a
|
||||
// v8::Function for that serves as the JavaScript constructor of MyWrap:
|
||||
Local<FunctionTemplate> ctor_template = NewFunctionTemplate(isolate, MyWrap::New);
|
||||
ctor_template->InstanceTemplate()->SetInternalFieldCount(
|
||||
ContextifyScript::kInternalFieldCount);
|
||||
```
|
||||
|
||||
`cppgc::GarbageCollected` objects should not be allocated with usual C++
|
||||
primitives (e.g. using `new` or `std::make_unique` is forbidden). Instead
|
||||
they must be allocated using `cppgc::MakeGarbageCollected` - this would
|
||||
allocate them in the V8 heap and allow V8's garbage collector to trace them.
|
||||
It's recommended to use a `New` method to wrap the `cppgc::MakeGarbageCollected`
|
||||
call so that external C++ code does not need to know about its memory management
|
||||
scheme to construct it.
|
||||
|
||||
```cpp
|
||||
MyWrap* MyWrap::New(Environment* env, v8::Local<v8::Object> object) {
|
||||
// Per cppgc rules, the constructor of MyWrap cannot be invoked directly.
|
||||
// It's recommended to implement a New() static method that prepares
|
||||
// and forwards the necessary arguments to cppgc::MakeGarbageCollected()
|
||||
// and just return the raw pointer around - do not use any C++ smart
|
||||
// pointer with this, as this is not managed by the native memory
|
||||
// allocator but by V8.
|
||||
return cppgc::MakeGarbageCollected<MyWrap>(
|
||||
env->isolate()->GetCppHeap()->GetAllocationHandle(), env, object);
|
||||
}
|
||||
|
||||
// Binding method to be invoked by JavaScript.
|
||||
void MyWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
|
||||
CHECK(args.IsConstructCall());
|
||||
|
||||
// Get more arguments from JavaScript land if necessary.
|
||||
New(env, args.This());
|
||||
}
|
||||
```
|
||||
|
||||
In the constructor of `node::CppgcMixin` types, use
|
||||
`node::CppgcMixin::Wrap()` to finish the wrapping so that
|
||||
V8 can trace the C++ object from the JavaScript object.
|
||||
|
||||
```cpp
|
||||
MyWrap::MyWrap(Environment* env, v8::Local<v8::Object> object) {
|
||||
// This cannot invoke the mixin constructor and has to invoke via a static
|
||||
// method from it, per cppgc rules.
|
||||
CppgcMixin::Wrap(this, env, object);
|
||||
}
|
||||
```
|
||||
|
||||
#### Unwrapping `cppgc`-managed wrapper objects
|
||||
|
||||
When given a `v8::Local<v8::Object>` that is known to be the JavaScript
|
||||
wrapper object for `MyWrap`, uses the `node::CppgcMixin::Unwrap()` to
|
||||
get the C++ object from it:
|
||||
|
||||
```cpp
|
||||
v8::Local<v8::Object> object = ...; // Obtain the JavaScript from somewhere.
|
||||
MyWrap* wrap = CppgcMixin::Unwrap<MyWrap>(object);
|
||||
```
|
||||
|
||||
Similar to `ASSIGN_OR_RETURN_UNWRAP`, there is a `ASSIGN_OR_RETURN_UNWRAP_CPPGC`
|
||||
that can be used in binding methods to return early if the JavaScript object does
|
||||
not wrap the desired type. And similar to `BaseObject`, `node::CppgcMixin`
|
||||
provides `env()` and `object()` methods to quickly access the associated
|
||||
`node::Environment` and its JavaScript wrapper object.
|
||||
|
||||
```cpp
|
||||
ASSIGN_OR_RETURN_UNWRAP_CPPGC(&wrap, object);
|
||||
CHECK_EQ(wrap->object(), object);
|
||||
```
|
||||
|
||||
#### Creating C++ to JavaScript references in cppgc-managed objects
|
||||
|
||||
Unlike `BaseObject` which typically uses a `v8::Global` (either weak or strong)
|
||||
to reference an object from the V8 heap, cppgc-managed objects are expected to
|
||||
use `v8::TracedReference` (which supports any `v8::Data`). For example if the
|
||||
`MyWrap` object owns a `v8::UnboundScript`, in the class body the reference
|
||||
should be declared as
|
||||
|
||||
```cpp
|
||||
class MyWrap : ... {
|
||||
v8::TracedReference<v8::UnboundScript> script;
|
||||
}
|
||||
```
|
||||
|
||||
V8's garbage collector traces the references from `MyWrap` through the
|
||||
`MyWrap::Trace()` method, which should call `cppgc::Visitor::Trace` on the
|
||||
`v8::TracedReference`.
|
||||
|
||||
```cpp
|
||||
void MyWrap::Trace(cppgc::Visitor* visitor) const {
|
||||
CppgcMixin::Trace(visitor);
|
||||
visitor->Trace(script); // v8::TracedReference is supported by cppgc::Visitor
|
||||
}
|
||||
```
|
||||
|
||||
As long as a `MyWrap` object is alive, the `v8::UnboundScript` in its
|
||||
`v8::TracedReference` will be kept alive. When the `MyWrap` object is no longer
|
||||
reachable from the V8 heap, and there are no other references to the
|
||||
`v8::UnboundScript` it owns, the `v8::UnboundScript` will be garbage collected
|
||||
along with its owning `MyWrap`. The reference will also be automatically
|
||||
captured in the heap snapshots.
|
||||
|
||||
#### Creating JavaScript to C++ references for cppgc-managed objects
|
||||
|
||||
To create a reference from another JavaScript object to a C++ wrapper
|
||||
extending `node::CppgcMixin`, just create a JavaScript to JavaScript
|
||||
reference using the JavaScript side of the wrapper, which can be accessed
|
||||
using `node::CppgcMixin::object()`.
|
||||
|
||||
```cpp
|
||||
MyWrap* wrap = ....; // Obtain a reference to the cppgc-managed object.
|
||||
Local<Object> referrer = ...; // This is the referrer object.
|
||||
// To reference the C++ wrap from the JavaScript referrer, simply creates
|
||||
// a usual JavaScript property reference - the key can be a symbol or a
|
||||
// number too if necessary, or it can be a private symbol property added
|
||||
// using SetPrivate(). wrap->object() can also be passed to the JavaScript
|
||||
// land, which can be referenced by any JavaScript objects in an invisible
|
||||
// manner using a WeakMap or being inside a closure.
|
||||
referrer->Set(
|
||||
context, FIXED_ONE_BYTE_STRING(isolate, "ref"), wrap->object()
|
||||
).ToLocalChecked();
|
||||
```
|
||||
|
||||
Typically, a newly created cppgc-managed wrapper object should be held alive
|
||||
by the JavaScript land (for example, by being returned by a method and
|
||||
staying alive in a closure). Long-lived cppgc objects can also
|
||||
be held alive from C++ using persistent handles (see
|
||||
`deps/v8/include/cppgc/persistent.h`) or as members of other living
|
||||
cppgc-managed objects (see `deps/v8/include/cppgc/member.h`) if necessary.
|
||||
Its destructor will be called when no other objects from the V8 heap reference
|
||||
it, this can happen at any time after the garbage collector notices that
|
||||
it's no longer reachable and before the V8 isolate is torn down.
|
||||
See the [Oilpan documentation in Chromium][] for more details.
|
||||
|
||||
### Callback scopes
|
||||
|
||||
The public `CallbackScope` and the internally used `InternalCallbackScope`
|
||||
@ -1082,6 +1304,8 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
|
||||
[ECMAScript realm]: https://tc39.es/ecma262/#sec-code-realms
|
||||
[JavaScript value handles]: #js-handles
|
||||
[N-API]: https://nodejs.org/api/n-api.html
|
||||
[Oilpan]: https://v8.dev/blog/oilpan-library
|
||||
[Oilpan documentation in Chromium]: https://chromium.googlesource.com/v8/v8/+/main/include/cppgc/README.md
|
||||
[`BaseObject`]: #baseobject
|
||||
[`Context`]: #context
|
||||
[`Environment`]: #environment
|
||||
@ -1117,3 +1341,4 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
|
||||
[libuv handles]: #libuv-handles-and-requests
|
||||
[libuv requests]: #libuv-handles-and-requests
|
||||
[reference documentation for the libuv API]: http://docs.libuv.org/en/v1.x/
|
||||
[unified heap design in Chromium]: https://docs.google.com/document/d/1Hs60Zx1WPJ_LUjGvgzt1OQ5Cthu-fG-zif-vquUH_8c/edit
|
||||
|
@ -84,6 +84,11 @@ class BaseObject : public MemoryRetainer {
|
||||
static inline BaseObject* FromJSObject(v8::Local<v8::Value> object);
|
||||
template <typename T>
|
||||
static inline T* FromJSObject(v8::Local<v8::Value> object);
|
||||
// Global alias for FromJSObject() to avoid churn.
|
||||
template <typename T>
|
||||
static inline T* Unwrap(v8::Local<v8::Value> obj) {
|
||||
return BaseObject::FromJSObject<T>(obj);
|
||||
}
|
||||
|
||||
// Make the `v8::Global` a weak reference and, `delete` this object once
|
||||
// the JS object has been garbage collected and there are no (strong)
|
||||
@ -234,12 +239,6 @@ class BaseObject : public MemoryRetainer {
|
||||
PointerData* pointer_data_ = nullptr;
|
||||
};
|
||||
|
||||
// Global alias for FromJSObject() to avoid churn.
|
||||
template <typename T>
|
||||
inline T* Unwrap(v8::Local<v8::Value> obj) {
|
||||
return BaseObject::FromJSObject<T>(obj);
|
||||
}
|
||||
|
||||
#define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...) \
|
||||
do { \
|
||||
*ptr = static_cast<typename std::remove_reference<decltype(*ptr)>::type>( \
|
||||
|
137
src/cppgc_helpers.h
Normal file
137
src/cppgc_helpers.h
Normal file
@ -0,0 +1,137 @@
|
||||
#ifndef SRC_CPPGC_HELPERS_H_
|
||||
#define SRC_CPPGC_HELPERS_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include <type_traits> // std::remove_reference
|
||||
#include "cppgc/garbage-collected.h"
|
||||
#include "cppgc/name-provider.h"
|
||||
#include "env.h"
|
||||
#include "memory_tracker.h"
|
||||
#include "v8-cppgc.h"
|
||||
#include "v8-sandbox.h"
|
||||
#include "v8.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
/**
|
||||
* This is a helper mixin with a BaseObject-like interface to help
|
||||
* implementing wrapper objects managed by V8's cppgc (Oilpan) library.
|
||||
* cppgc-manged objects in Node.js internals should extend this mixin,
|
||||
* while non-cppgc-managed objects typically extend BaseObject - the
|
||||
* latter are being migrated to be cppgc-managed wherever it's beneficial
|
||||
* and practical. Typically cppgc-managed objects are more efficient to
|
||||
* keep track of (which lowers initialization cost) and work better
|
||||
* with V8's GC scheduling.
|
||||
*
|
||||
* A cppgc-managed native wrapper should look something like this, note
|
||||
* that per cppgc rules, CPPGC_MIXIN(Klass) must be at the left-most
|
||||
* position in the hierarchy (which ensures cppgc::GarbageCollected
|
||||
* is at the left-most position).
|
||||
*
|
||||
* class Klass final : CPPGC_MIXIN(Klass) {
|
||||
* public:
|
||||
* SET_CPPGC_NAME(Klass) // Sets the heap snapshot name to "Node / Klass"
|
||||
* void Trace(cppgc::Visitor* visitor) const final {
|
||||
* CppgcMixin::Trace(visitor);
|
||||
* visitor->Trace(...); // Trace any additional owned traceable data
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class CppgcMixin : public cppgc::GarbageCollectedMixin {
|
||||
public:
|
||||
// To help various callbacks access wrapper objects with different memory
|
||||
// management, cppgc-managed objects share the same layout as BaseObjects.
|
||||
enum InternalFields { kEmbedderType = 0, kSlot, kInternalFieldCount };
|
||||
|
||||
// The initialization cannot be done in the mixin constructor but has to be
|
||||
// invoked from the child class constructor, per cppgc::GarbageCollectedMixin
|
||||
// rules.
|
||||
template <typename T>
|
||||
static void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj) {
|
||||
CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount);
|
||||
ptr->env_ = env;
|
||||
v8::Isolate* isolate = env->isolate();
|
||||
ptr->traced_reference_ = v8::TracedReference<v8::Object>(isolate, obj);
|
||||
v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(isolate, obj, ptr);
|
||||
// Keep the layout consistent with BaseObjects.
|
||||
obj->SetAlignedPointerInInternalField(
|
||||
kEmbedderType, env->isolate_data()->embedder_id_for_cppgc());
|
||||
obj->SetAlignedPointerInInternalField(kSlot, ptr);
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> object() const {
|
||||
return traced_reference_.Get(env_->isolate());
|
||||
}
|
||||
|
||||
Environment* env() const { return env_; }
|
||||
|
||||
template <typename T>
|
||||
static T* Unwrap(v8::Local<v8::Object> obj) {
|
||||
// We are not using v8::Object::Unwrap currently because that requires
|
||||
// access to isolate which the ASSIGN_OR_RETURN_UNWRAP macro that we'll shim
|
||||
// with ASSIGN_OR_RETURN_UNWRAP_GC doesn't take, and we also want a
|
||||
// signature consistent with BaseObject::Unwrap() to avoid churn. Since
|
||||
// cppgc-managed objects share the same layout as BaseObjects, just unwrap
|
||||
// from the pointer in the internal field, which should be valid as long as
|
||||
// the object is still alive.
|
||||
if (obj->InternalFieldCount() != T::kInternalFieldCount) {
|
||||
return nullptr;
|
||||
}
|
||||
T* ptr = static_cast<T*>(obj->GetAlignedPointerFromInternalField(T::kSlot));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace()
|
||||
// methods.
|
||||
void Trace(cppgc::Visitor* visitor) const override {
|
||||
visitor->Trace(traced_reference_);
|
||||
}
|
||||
|
||||
private:
|
||||
Environment* env_;
|
||||
v8::TracedReference<v8::Object> traced_reference_;
|
||||
};
|
||||
|
||||
// If the class doesn't have additional owned traceable data, use this macro to
|
||||
// save the implementation of a custom Trace() method.
|
||||
#define DEFAULT_CPPGC_TRACE() \
|
||||
void Trace(cppgc::Visitor* visitor) const final { \
|
||||
CppgcMixin::Trace(visitor); \
|
||||
}
|
||||
|
||||
// This macro sets the node name in the heap snapshot with a "Node /" prefix.
|
||||
// Classes that use this macro must extend cppgc::NameProvider.
|
||||
#define SET_CPPGC_NAME(Klass) \
|
||||
inline const char* GetHumanReadableName() const final { \
|
||||
return "Node / " #Klass; \
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types
|
||||
* inheriting CppgcMixin.
|
||||
*/
|
||||
#define ASSIGN_OR_RETURN_UNWRAP_CPPGC(ptr, obj, ...) \
|
||||
do { \
|
||||
*ptr = CppgcMixin::Unwrap< \
|
||||
typename std::remove_reference<decltype(**ptr)>::type>(obj); \
|
||||
if (*ptr == nullptr) return __VA_ARGS__; \
|
||||
} while (0)
|
||||
} // namespace node
|
||||
|
||||
/**
|
||||
* Helper macro the manage the cppgc-based wrapper hierarchy. This must
|
||||
* be used at the left-most postion - right after `:` in the class inheritance,
|
||||
* like this:
|
||||
* class Klass : CPPGC_MIXIN(Klass) ... {}
|
||||
*
|
||||
* This needs to disable linters because it will be at odds with
|
||||
* clang-format.
|
||||
*/
|
||||
#define CPPGC_MIXIN(Klass) \
|
||||
public /* NOLINT(whitespace/indent) */ \
|
||||
cppgc::GarbageCollected<Klass>, public cppgc::NameProvider, public CppgcMixin
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#endif // SRC_CPPGC_HELPERS_H_
|
@ -805,7 +805,8 @@ ManagedEVPPKey ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(
|
||||
env, std::move(pkey), ret, "Failed to read asymmetric key");
|
||||
} else {
|
||||
CHECK(args[*offset]->IsObject());
|
||||
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(args[*offset].As<Object>());
|
||||
KeyObjectHandle* key =
|
||||
BaseObject::Unwrap<KeyObjectHandle>(args[*offset].As<Object>());
|
||||
CHECK_NOT_NULL(key);
|
||||
CHECK_NE(key->Data()->GetKeyType(), kKeyTypeSecret);
|
||||
(*offset) += 4;
|
||||
|
@ -419,7 +419,8 @@ ByteSource ByteSource::NullTerminatedCopy(Environment* env,
|
||||
|
||||
ByteSource ByteSource::FromSymmetricKeyObjectHandle(Local<Value> handle) {
|
||||
CHECK(handle->IsObject());
|
||||
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(handle.As<Object>());
|
||||
KeyObjectHandle* key =
|
||||
BaseObject::Unwrap<KeyObjectHandle>(handle.As<Object>());
|
||||
CHECK_NOT_NULL(key);
|
||||
return Foreign(key->Data()->GetSymmetricKey(),
|
||||
key->Data()->GetSymmetricKeySize());
|
||||
|
@ -288,7 +288,7 @@ FSReqBase* GetReqWrap(const v8::FunctionCallbackInfo<v8::Value>& args,
|
||||
bool use_bigint) {
|
||||
v8::Local<v8::Value> value = args[index];
|
||||
if (value->IsObject()) {
|
||||
return Unwrap<FSReqBase>(value.As<v8::Object>());
|
||||
return BaseObject::Unwrap<FSReqBase>(value.As<v8::Object>());
|
||||
}
|
||||
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
|
@ -91,7 +91,7 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
|
||||
CHECK_LT(id, host_objects_.size());
|
||||
Local<Object> object = host_objects_[id]->object(isolate);
|
||||
if (env_->js_transferable_constructor_template()->HasInstance(object)) {
|
||||
return Unwrap<JSTransferable>(object)->target();
|
||||
return BaseObject::Unwrap<JSTransferable>(object)->target();
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
@ -318,7 +318,7 @@ class SerializerDelegate : public ValueSerializer::Delegate {
|
||||
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override {
|
||||
if (BaseObject::IsBaseObject(env_->isolate_data(), object)) {
|
||||
return WriteHostObject(
|
||||
BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(object) });
|
||||
BaseObjectPtr<BaseObject>{BaseObject::Unwrap<BaseObject>(object)});
|
||||
}
|
||||
|
||||
if (JSTransferable::IsJSTransferable(env_, context_, object)) {
|
||||
@ -532,7 +532,8 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
}
|
||||
BaseObjectPtr<BaseObject> host_object;
|
||||
if (BaseObject::IsBaseObject(env->isolate_data(), entry)) {
|
||||
host_object = BaseObjectPtr<BaseObject>{Unwrap<BaseObject>(entry)};
|
||||
host_object =
|
||||
BaseObjectPtr<BaseObject>{BaseObject::Unwrap<BaseObject>(entry)};
|
||||
} else {
|
||||
if (!JSTransferable::IsJSTransferable(env, context, entry)) {
|
||||
ThrowDataCloneException(context, env->clone_untransferable_str());
|
||||
|
@ -240,7 +240,7 @@ static MaybeLocal<Object> AcceptHandle(Environment* env,
|
||||
if (!WrapType::Instantiate(env, parent, WrapType::SOCKET).ToLocal(&wrap_obj))
|
||||
return Local<Object>();
|
||||
|
||||
HandleWrap* wrap = Unwrap<HandleWrap>(wrap_obj);
|
||||
HandleWrap* wrap = BaseObject::Unwrap<HandleWrap>(wrap_obj);
|
||||
CHECK_NOT_NULL(wrap);
|
||||
uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(wrap->GetHandle());
|
||||
CHECK_NOT_NULL(stream);
|
||||
|
@ -56,7 +56,7 @@ using v8::Value;
|
||||
namespace {
|
||||
template <int (*fn)(uv_udp_t*, int)>
|
||||
void SetLibuvInt32(const FunctionCallbackInfo<Value>& args) {
|
||||
UDPWrap* wrap = Unwrap<UDPWrap>(args.This());
|
||||
UDPWrap* wrap = BaseObject::Unwrap<UDPWrap>(args.This());
|
||||
if (wrap == nullptr) {
|
||||
args.GetReturnValue().Set(UV_EBADF);
|
||||
return;
|
||||
|
@ -2,15 +2,21 @@
|
||||
const assert = require('assert');
|
||||
const util = require('util');
|
||||
|
||||
let internalBinding;
|
||||
try {
|
||||
internalBinding = require('internal/test/binding').internalBinding;
|
||||
} catch (e) {
|
||||
console.log('using `test/common/heap.js` requires `--expose-internals`');
|
||||
throw e;
|
||||
let _buildEmbedderGraph;
|
||||
function buildEmbedderGraph() {
|
||||
if (_buildEmbedderGraph) { return _buildEmbedderGraph(); }
|
||||
let internalBinding;
|
||||
try {
|
||||
internalBinding = require('internal/test/binding').internalBinding;
|
||||
} catch (e) {
|
||||
console.error('The test must be run with `--expose-internals`');
|
||||
throw e;
|
||||
}
|
||||
|
||||
({ buildEmbedderGraph: _buildEmbedderGraph } = internalBinding('heap_utils'));
|
||||
return _buildEmbedderGraph();
|
||||
}
|
||||
|
||||
const { buildEmbedderGraph } = internalBinding('heap_utils');
|
||||
const { getHeapSnapshot } = require('v8');
|
||||
|
||||
function createJSHeapSnapshot(stream = getHeapSnapshot()) {
|
||||
@ -211,6 +217,79 @@ function validateSnapshotNodes(...args) {
|
||||
return recordState().validateSnapshotNodes(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* A alternative heap snapshot validator that can be used to verify cppgc-managed nodes.
|
||||
* Modified from
|
||||
* https://chromium.googlesource.com/v8/v8/+/b00e995fb212737802810384ba2b868d0d92f7e5/test/unittests/heap/cppgc-js/unified-heap-snapshot-unittest.cc#134
|
||||
* @param {string} rootName Name of the root node. Typically a class name used to filter all native nodes with
|
||||
* this name. For cppgc-managed objects, this is typically the name configured by
|
||||
* SET_CPPGC_NAME() prefixed with an additional "Node /" prefix e.g.
|
||||
* "Node / ContextifyScript"
|
||||
* @param {[{
|
||||
* node_name?: string,
|
||||
* edge_name?: string,
|
||||
* node_type?: string,
|
||||
* edge_type?: string,
|
||||
* }]} retainingPath The retaining path specification to search from the root nodes.
|
||||
* @returns {[object]} All the leaf nodes matching the retaining path specification. If none can be found,
|
||||
* logs the nodes found in the last matching step of the path (if any), and throws an
|
||||
* assertion error.
|
||||
*/
|
||||
function findByRetainingPath(rootName, retainingPath) {
|
||||
const nodes = createJSHeapSnapshot();
|
||||
let haystack = nodes.filter((n) => n.name === rootName && n.type !== 'string');
|
||||
|
||||
for (let i = 0; i < retainingPath.length; ++i) {
|
||||
const expected = retainingPath[i];
|
||||
const newHaystack = [];
|
||||
|
||||
for (const parent of haystack) {
|
||||
for (let j = 0; j < parent.outgoingEdges.length; j++) {
|
||||
const edge = parent.outgoingEdges[j];
|
||||
// The strings are represented as { type: 'string', name: '<string content>' } in the snapshot.
|
||||
// Ignore them or we'll poke into strings that are just referenced as names of real nodes,
|
||||
// unless the caller is specifically looking for string nodes via `node_type`.
|
||||
let match = (edge.to.type !== 'string');
|
||||
if (expected.node_type) {
|
||||
match = (edge.to.type === expected.node_type);
|
||||
}
|
||||
if (expected.node_name && edge.to.name !== expected.node_name) {
|
||||
match = false;
|
||||
}
|
||||
if (expected.edge_name && edge.name !== expected.edge_name) {
|
||||
match = false;
|
||||
}
|
||||
if (expected.edge_type && edge.type !== expected.type) {
|
||||
match = false;
|
||||
}
|
||||
if (match) {
|
||||
newHaystack.push(edge.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newHaystack.length === 0) {
|
||||
const format = (val) => util.inspect(val, { breakLength: 128, depth: 3 });
|
||||
console.error('#');
|
||||
console.error('# Retaining path to search for:');
|
||||
for (let j = 0; j < retainingPath.length; ++j) {
|
||||
console.error(`# - '${format(retainingPath[j])}'${i === j ? '\t<--- not found' : ''}`);
|
||||
}
|
||||
console.error('#\n');
|
||||
console.error('# Nodes found in the last step include:');
|
||||
for (let j = 0; j < haystack.length; ++j) {
|
||||
console.error(`# - '${format(haystack[j])}`);
|
||||
}
|
||||
|
||||
assert.fail(`Could not find target edge ${format(expected)} in the heap snapshot.`);
|
||||
}
|
||||
|
||||
haystack = newHaystack;
|
||||
}
|
||||
|
||||
return haystack;
|
||||
}
|
||||
|
||||
function getHeapSnapshotOptionTests() {
|
||||
const fixtures = require('../common/fixtures');
|
||||
const cases = [
|
||||
@ -245,5 +324,6 @@ function getHeapSnapshotOptionTests() {
|
||||
module.exports = {
|
||||
recordState,
|
||||
validateSnapshotNodes,
|
||||
findByRetainingPath,
|
||||
getHeapSnapshotOptionTests,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user