mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
v8: implement v8.queryObjects() for memory leak regression testing
This is similar to the `queryObjects()` console API provided by the Chromium DevTools console. It can be used to search for objects that have the matching constructor on its prototype chain in the entire heap, which can be useful for memory leak regression tests. To avoid surprising results, users should avoid using this API on constructors whose implementation they don't control, or on constructors that can be invoked by other parties in the application. To avoid accidental leaks, this API does not return raw references to the objects found. By default, it returns the count of the objects found. If `options.format` is `'summary'`, it returns an array containing brief string representations for each object. The visibility provided in this API is similar to what the heap snapshot provides, while users can save the cost of serialization and parsing and directly filer the target objects during the search. We have been using this API internally for the test suite, which has been more stable than any other leak regression testing strategies in the CI. With a public implementation we can now use the public API instead. PR-URL: https://github.com/nodejs/node/pull/51927 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
parent
9930f114a0
commit
7f2d61f82a
@ -242,6 +242,89 @@ buffers and external strings.
|
||||
}
|
||||
```
|
||||
|
||||
## `v8.queryObjects(ctor[, options])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
* `ctor` {Function} The constructor that can be used to search on the
|
||||
prototype chain in order to filter target objects in the heap.
|
||||
* `options` {undefined|Object}
|
||||
* `format` {string} If it's `'count'`, the count of matched objects
|
||||
is returned. If it's `'summary'`, an array with summary strings
|
||||
of the matched objects is returned.
|
||||
* Returns: {number|Array<string>}
|
||||
|
||||
This is similar to the [`queryObjects()` console API][] provided by the
|
||||
Chromium DevTools console. It can be used to search for objects that
|
||||
have the matching constructor on its prototype chain in the heap after
|
||||
a full garbage collection, which can be useful for memory leak
|
||||
regression tests. To avoid surprising results, users should avoid using
|
||||
this API on constructors whose implementation they don't control, or on
|
||||
constructors that can be invoked by other parties in the application.
|
||||
|
||||
To avoid accidental leaks, this API does not return raw references to
|
||||
the objects found. By default, it returns the count of the objects
|
||||
found. If `options.format` is `'summary'`, it returns an array
|
||||
containing brief string representations for each object. The visibility
|
||||
provided in this API is similar to what the heap snapshot provides,
|
||||
while users can save the cost of serialization and parsing and directly
|
||||
filter the target objects during the search.
|
||||
|
||||
Only objects created in the current execution context are included in the
|
||||
results.
|
||||
|
||||
```cjs
|
||||
const { queryObjects } = require('node:v8');
|
||||
class A { foo = 'bar'; }
|
||||
console.log(queryObjects(A)); // 0
|
||||
const a = new A();
|
||||
console.log(queryObjects(A)); // 1
|
||||
// [ "A { foo: 'bar' }" ]
|
||||
console.log(queryObjects(A, { format: 'summary' }));
|
||||
|
||||
class B extends A { bar = 'qux'; }
|
||||
const b = new B();
|
||||
console.log(queryObjects(B)); // 1
|
||||
// [ "B { foo: 'bar', bar: 'qux' }" ]
|
||||
console.log(queryObjects(B, { format: 'summary' }));
|
||||
|
||||
// Note that, when there are child classes inheriting from a constructor,
|
||||
// the constructor also shows up in the prototype chain of the child
|
||||
// classes's prototoype, so the child classes's prototoype would also be
|
||||
// included in the result.
|
||||
console.log(queryObjects(A)); // 3
|
||||
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
|
||||
console.log(queryObjects(A, { format: 'summary' }));
|
||||
```
|
||||
|
||||
```mjs
|
||||
import { queryObjects } from 'node:v8';
|
||||
class A { foo = 'bar'; }
|
||||
console.log(queryObjects(A)); // 0
|
||||
const a = new A();
|
||||
console.log(queryObjects(A)); // 1
|
||||
// [ "A { foo: 'bar' }" ]
|
||||
console.log(queryObjects(A, { format: 'summary' }));
|
||||
|
||||
class B extends A { bar = 'qux'; }
|
||||
const b = new B();
|
||||
console.log(queryObjects(B)); // 1
|
||||
// [ "B { foo: 'bar', bar: 'qux' }" ]
|
||||
console.log(queryObjects(B, { format: 'summary' }));
|
||||
|
||||
// Note that, when there are child classes inheriting from a constructor,
|
||||
// the constructor also shows up in the prototype chain of the child
|
||||
// classes's prototoype, so the child classes's prototoype would also be
|
||||
// included in the result.
|
||||
console.log(queryObjects(A)); // 3
|
||||
// [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ]
|
||||
console.log(queryObjects(A, { format: 'summary' }));
|
||||
```
|
||||
|
||||
## `v8.setFlagsFromString(flags)`
|
||||
|
||||
<!-- YAML
|
||||
@ -1212,6 +1295,7 @@ setTimeout(() => {
|
||||
[`deserializer._readHostObject()`]: #deserializer_readhostobject
|
||||
[`deserializer.transferArrayBuffer()`]: #deserializertransferarraybufferid-arraybuffer
|
||||
[`init` callback]: #initpromise-parent
|
||||
[`queryObjects()` console API]: https://developer.chrome.com/docs/devtools/console/utilities#queryObjects-function
|
||||
[`serialize()`]: #v8serializevalue
|
||||
[`serializer._getSharedArrayBufferId()`]: #serializer_getsharedarraybufferidsharedarraybuffer
|
||||
[`serializer._writeHostObject()`]: #serializer_writehostobjectobject
|
||||
|
@ -2,6 +2,7 @@
|
||||
const {
|
||||
Symbol,
|
||||
Uint8Array,
|
||||
ArrayPrototypeMap,
|
||||
} = primordials;
|
||||
const {
|
||||
kUpdateTimer,
|
||||
@ -9,9 +10,23 @@ const {
|
||||
} = require('internal/stream_base_commons');
|
||||
const { owner_symbol } = require('internal/async_hooks').symbols;
|
||||
const { Readable } = require('stream');
|
||||
const { validateObject, validateBoolean } = require('internal/validators');
|
||||
const { kEmptyObject } = require('internal/util');
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
validateBoolean,
|
||||
validateFunction,
|
||||
} = require('internal/validators');
|
||||
const {
|
||||
codes: {
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
},
|
||||
} = require('internal/errors');
|
||||
const { kEmptyObject, emitExperimentalWarning } = require('internal/util');
|
||||
const {
|
||||
queryObjects: _queryObjects,
|
||||
} = internalBinding('internal_only_v8');
|
||||
const {
|
||||
inspect,
|
||||
} = require('internal/util/inspect');
|
||||
const kHandle = Symbol('kHandle');
|
||||
|
||||
function getHeapSnapshotOptions(options = kEmptyObject) {
|
||||
@ -50,7 +65,31 @@ class HeapSnapshotStream extends Readable {
|
||||
}
|
||||
}
|
||||
|
||||
const inspectOptions = {
|
||||
__proto__: null,
|
||||
depth: 0,
|
||||
};
|
||||
function queryObjects(ctor, options = kEmptyObject) {
|
||||
validateFunction(ctor, 'constructor');
|
||||
if (options !== kEmptyObject) {
|
||||
validateObject(options, 'options');
|
||||
}
|
||||
const format = options.format || 'count';
|
||||
if (format !== 'count' && format !== 'summary') {
|
||||
throw new ERR_INVALID_ARG_VALUE('options.format', format);
|
||||
}
|
||||
emitExperimentalWarning('v8.queryObjects()');
|
||||
// Matching the console API behavior - just access the .prototype.
|
||||
const objects = _queryObjects(ctor.prototype);
|
||||
if (format === 'count') {
|
||||
return objects.length;
|
||||
}
|
||||
// options.format is 'summary'.
|
||||
return ArrayPrototypeMap(objects, (object) => inspect(object, inspectOptions));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHeapSnapshotOptions,
|
||||
HeapSnapshotStream,
|
||||
queryObjects,
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
Error,
|
||||
StringPrototypeStartsWith,
|
||||
globalThis,
|
||||
} = primordials;
|
||||
|
||||
@ -8,9 +10,24 @@ process.emitWarning(
|
||||
'These APIs are for internal testing only. Do not use them.',
|
||||
'internal/test/binding');
|
||||
|
||||
function filteredInternalBinding(id) {
|
||||
// Disallows internal bindings with names that start with 'internal_only'
|
||||
// which means it should not be exposed to users even with
|
||||
// --expose-internals.
|
||||
if (StringPrototypeStartsWith(id, 'internal_only')) {
|
||||
// This code is only intended for internal errors and is not documented.
|
||||
// Do not use the normal error system.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const error = new Error(`No such binding: ${id}`);
|
||||
error.code = 'ERR_INVALID_MODULE';
|
||||
throw error;
|
||||
}
|
||||
return internalBinding(id);
|
||||
}
|
||||
|
||||
if (module.isPreloading) {
|
||||
globalThis.internalBinding = internalBinding;
|
||||
globalThis.internalBinding = filteredInternalBinding;
|
||||
globalThis.primordials = primordials;
|
||||
}
|
||||
|
||||
module.exports = { internalBinding, primordials };
|
||||
module.exports = { internalBinding: filteredInternalBinding, primordials };
|
||||
|
@ -60,6 +60,7 @@ const {
|
||||
const {
|
||||
HeapSnapshotStream,
|
||||
getHeapSnapshotOptions,
|
||||
queryObjects,
|
||||
} = require('internal/heap_utils');
|
||||
const promiseHooks = require('internal/promise_hooks');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
@ -437,6 +438,7 @@ module.exports = {
|
||||
serialize,
|
||||
writeHeapSnapshot,
|
||||
promiseHooks,
|
||||
queryObjects,
|
||||
startupSnapshot,
|
||||
setHeapSnapshotNearHeapLimit,
|
||||
GCProfiler,
|
||||
|
1
node.gyp
1
node.gyp
@ -80,6 +80,7 @@
|
||||
'src/handle_wrap.cc',
|
||||
'src/heap_utils.cc',
|
||||
'src/histogram.cc',
|
||||
'src/internal_only_v8.cc',
|
||||
'src/js_native_api.h',
|
||||
'src/js_native_api_types.h',
|
||||
'src/js_native_api_v8.cc',
|
||||
|
@ -474,39 +474,6 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
|
||||
return args.GetReturnValue().Set(filename_v);
|
||||
}
|
||||
|
||||
class PrototypeChainHas : public v8::QueryObjectPredicate {
|
||||
public:
|
||||
PrototypeChainHas(Local<Context> context, Local<Object> search)
|
||||
: context_(context), search_(search) {}
|
||||
|
||||
// What we can do in the filter can be quite limited, but looking up
|
||||
// the prototype chain is something that the inspector console API
|
||||
// queryObject() does so it is supported.
|
||||
bool Filter(Local<Object> object) override {
|
||||
for (Local<Value> proto = object->GetPrototype(); proto->IsObject();
|
||||
proto = proto.As<Object>()->GetPrototype()) {
|
||||
if (search_ == proto) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Local<Context> context_;
|
||||
Local<Object> search_;
|
||||
};
|
||||
|
||||
void CountObjectsWithPrototype(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsObject());
|
||||
Local<Object> proto = args[0].As<Object>();
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
PrototypeChainHas prototype_chain_has(context, proto);
|
||||
std::vector<Global<Object>> out;
|
||||
isolate->GetHeapProfiler()->QueryObjects(context, &prototype_chain_has, &out);
|
||||
args.GetReturnValue().Set(static_cast<uint32_t>(out.size()));
|
||||
}
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
@ -515,15 +482,12 @@ void Initialize(Local<Object> target,
|
||||
SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot);
|
||||
SetMethod(
|
||||
context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream);
|
||||
SetMethod(
|
||||
context, target, "countObjectsWithPrototype", CountObjectsWithPrototype);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(BuildEmbedderGraph);
|
||||
registry->Register(TriggerHeapSnapshot);
|
||||
registry->Register(CreateHeapSnapshotStream);
|
||||
registry->Register(CountObjectsWithPrototype);
|
||||
}
|
||||
|
||||
} // namespace heap
|
||||
|
85
src/internal_only_v8.cc
Normal file
85
src/internal_only_v8.cc
Normal file
@ -0,0 +1,85 @@
|
||||
#include "node_binding.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "util-inl.h"
|
||||
#include "v8-profiler.h"
|
||||
#include "v8.h"
|
||||
|
||||
using v8::Array;
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Global;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::Value;
|
||||
|
||||
namespace node {
|
||||
namespace internal_only_v8 {
|
||||
|
||||
class PrototypeChainHas : public v8::QueryObjectPredicate {
|
||||
public:
|
||||
PrototypeChainHas(Local<Context> context, Local<Object> search)
|
||||
: context_(context), search_(search) {}
|
||||
|
||||
// What we can do in the filter can be quite limited, but looking up
|
||||
// the prototype chain is something that the inspector console API
|
||||
// queryObject() does so it is supported.
|
||||
bool Filter(Local<Object> object) override {
|
||||
Local<Context> creation_context;
|
||||
if (!object->GetCreationContext().ToLocal(&creation_context)) {
|
||||
return false;
|
||||
}
|
||||
if (creation_context != context_) {
|
||||
return false;
|
||||
}
|
||||
for (Local<Value> proto = object->GetPrototype(); proto->IsObject();
|
||||
proto = proto.As<Object>()->GetPrototype()) {
|
||||
if (search_ == proto) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Local<Context> context_;
|
||||
Local<Object> search_;
|
||||
};
|
||||
|
||||
void QueryObjects(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
if (!args[0]->IsObject()) {
|
||||
args.GetReturnValue().Set(Array::New(isolate));
|
||||
return;
|
||||
}
|
||||
Local<Object> proto = args[0].As<Object>();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
PrototypeChainHas prototype_chain_has(context, proto.As<Object>());
|
||||
std::vector<Global<Object>> out;
|
||||
isolate->GetHeapProfiler()->QueryObjects(context, &prototype_chain_has, &out);
|
||||
std::vector<Local<Value>> result;
|
||||
result.reserve(out.size());
|
||||
for (size_t i = 0; i < out.size(); ++i) {
|
||||
result.push_back(out[i].Get(isolate));
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(Array::New(isolate, result.data(), result.size()));
|
||||
}
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
SetMethod(context, target, "queryObjects", QueryObjects);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(QueryObjects);
|
||||
}
|
||||
|
||||
} // namespace internal_only_v8
|
||||
} // namespace node
|
||||
|
||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(internal_only_v8,
|
||||
node::internal_only_v8::Initialize)
|
||||
NODE_BINDING_EXTERNAL_REFERENCE(
|
||||
internal_only_v8, node::internal_only_v8::RegisterExternalReferences)
|
@ -26,6 +26,8 @@
|
||||
// function. This helps the built-in bindings are loaded properly when
|
||||
// node is built as static library. No need to depend on the
|
||||
// __attribute__((constructor)) like mechanism in GCC.
|
||||
// The binding IDs that start with 'internal_only' are not exposed to the user
|
||||
// land even from internal/test/binding module under --expose-internals.
|
||||
#define NODE_BUILTIN_STANDARD_BINDINGS(V) \
|
||||
V(async_wrap) \
|
||||
V(blob) \
|
||||
@ -46,6 +48,7 @@
|
||||
V(http2) \
|
||||
V(http_parser) \
|
||||
V(inspector) \
|
||||
V(internal_only_v8) \
|
||||
V(js_stream) \
|
||||
V(js_udp_wrap) \
|
||||
V(messaging) \
|
||||
|
@ -109,6 +109,7 @@ class ExternalReferenceRegistry {
|
||||
V(fs_event_wrap) \
|
||||
V(handle_wrap) \
|
||||
V(heap_utils) \
|
||||
V(internal_only_v8) \
|
||||
V(messaging) \
|
||||
V(mksnapshot) \
|
||||
V(module_wrap) \
|
||||
|
@ -83,15 +83,14 @@ async function runAndBreathe(fn, repeat, waitTime = 20) {
|
||||
* @param {(i: number) => number} fn The factory receiving iteration count
|
||||
* and returning number of objects created. The return value should be
|
||||
* precise otherwise false negatives can be produced.
|
||||
* @param {Function} klass The class whose object is used to count the objects
|
||||
* @param {Function} ctor The constructor of the objects being counted.
|
||||
* @param {number} count Number of iterations that this check should be done
|
||||
* @param {number} waitTime Optional breathing time for GC.
|
||||
*/
|
||||
async function checkIfCollectableByCounting(fn, klass, count, waitTime = 20) {
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const { countObjectsWithPrototype } = internalBinding('heap_utils');
|
||||
const { prototype, name } = klass;
|
||||
const initialCount = countObjectsWithPrototype(prototype);
|
||||
async function checkIfCollectableByCounting(fn, ctor, count, waitTime = 20) {
|
||||
const { queryObjects } = require('v8');
|
||||
const { name } = ctor;
|
||||
const initialCount = queryObjects(ctor, { format: 'count' });
|
||||
console.log(`Initial count of ${name}: ${initialCount}`);
|
||||
let totalCreated = 0;
|
||||
for (let i = 0; i < count; ++i) {
|
||||
@ -99,7 +98,7 @@ async function checkIfCollectableByCounting(fn, klass, count, waitTime = 20) {
|
||||
totalCreated += created;
|
||||
console.log(`#${i}: created ${created} ${name}, total ${totalCreated}`);
|
||||
await wait(waitTime); // give GC some breathing room.
|
||||
const currentCount = countObjectsWithPrototype(prototype);
|
||||
const currentCount = queryObjects(ctor, { format: 'count' });
|
||||
const collected = totalCreated - (currentCount - initialCount);
|
||||
console.log(`#${i}: counted ${currentCount} ${name}, collected ${collected}`);
|
||||
if (collected > 0) {
|
||||
@ -109,7 +108,7 @@ async function checkIfCollectableByCounting(fn, klass, count, waitTime = 20) {
|
||||
}
|
||||
|
||||
await wait(waitTime); // give GC some breathing room.
|
||||
const currentCount = countObjectsWithPrototype(prototype);
|
||||
const currentCount = queryObjects(ctor, { format: 'count' });
|
||||
const collected = totalCreated - (currentCount - initialCount);
|
||||
console.log(`Last count: counted ${currentCount} ${name}, collected ${collected}`);
|
||||
// Some objects with the prototype can be collected.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Flags: --expose-internals --experimental-vm-modules --max-old-space-size=16 --trace-gc
|
||||
// Flags: --experimental-vm-modules --max-old-space-size=16 --trace-gc
|
||||
'use strict';
|
||||
|
||||
// This tests that vm.SourceTextModule() does not leak.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Flags: --expose-internals --max-old-space-size=16
|
||||
// Flags: --max-old-space-size=16
|
||||
'use strict';
|
||||
|
||||
// This test ensures that diagnostic channel references aren't leaked.
|
||||
|
9
test/parallel/test-internal-only-binding.js
Normal file
9
test/parallel/test-internal-only-binding.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
// Flags: --expose-internals
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
|
||||
assert.throws(() => internalBinding('internal_only_v8'), {
|
||||
code: 'ERR_INVALID_MODULE'
|
||||
});
|
104
test/parallel/test-v8-query-objects.js
Normal file
104
test/parallel/test-v8-query-objects.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
// This tests the v8.queryObjects() API.
|
||||
|
||||
const common = require('../common');
|
||||
const v8 = require('v8');
|
||||
const assert = require('assert');
|
||||
const { inspect } = require('util');
|
||||
|
||||
function format(obj) {
|
||||
return inspect(obj, { depth: 0 });
|
||||
}
|
||||
|
||||
common.expectWarning(
|
||||
'ExperimentalWarning',
|
||||
'v8.queryObjects() is an experimental feature and might change at any time',
|
||||
);
|
||||
|
||||
{
|
||||
for (const invalid of [undefined, 1, null, false, {}, 'foo']) {
|
||||
assert.throws(() => v8.queryObjects(invalid), { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
}
|
||||
for (const invalid of [1, null, false, 'foo']) {
|
||||
assert.throws(() => v8.queryObjects(() => {}, invalid), { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
}
|
||||
assert.throws(() => v8.queryObjects(() => {}, { format: 'invalid' }), { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
}
|
||||
|
||||
{
|
||||
class TestV8QueryObjectsClass {}
|
||||
// By default, returns count of objects with the constructor on the prototype.
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass), 0);
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'count' }), 0);
|
||||
// 'summary' format returns an array.
|
||||
assert.deepStrictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'summary' }), []);
|
||||
|
||||
// Create an instance and check that it shows up in the results.
|
||||
const obj = new TestV8QueryObjectsClass();
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass), 1);
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'count' }), 1);
|
||||
assert.deepStrictEqual(
|
||||
v8.queryObjects(TestV8QueryObjectsClass, { format: 'summary' }),
|
||||
[ format(obj)]
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// ES6 class inheritance.
|
||||
class TestV8QueryObjectsBaseClass {}
|
||||
class TestV8QueryObjectsChildClass extends TestV8QueryObjectsBaseClass {}
|
||||
const summary = v8.queryObjects(TestV8QueryObjectsBaseClass, { format: 'summary' });
|
||||
// TestV8QueryObjectsChildClass's prototype's [[Prototype]] slot is
|
||||
// TestV8QueryObjectsBaseClass's prototoype so it shows up in the query.
|
||||
assert.deepStrictEqual(summary, [
|
||||
format(TestV8QueryObjectsChildClass.prototype),
|
||||
]);
|
||||
const obj = new TestV8QueryObjectsChildClass();
|
||||
assert.deepStrictEqual(
|
||||
v8.queryObjects(TestV8QueryObjectsBaseClass, { format: 'summary' }).sort(),
|
||||
[
|
||||
format(TestV8QueryObjectsChildClass.prototype),
|
||||
format(obj),
|
||||
].sort()
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
v8.queryObjects(TestV8QueryObjectsChildClass, { format: 'summary' }),
|
||||
[ format(obj) ],
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
function TestV8QueryObjectsCtor() {}
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor), 0);
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'count' }), 0);
|
||||
assert.deepStrictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'summary' }), []);
|
||||
|
||||
// Create an instance and check that it shows up in the results.
|
||||
const obj = new TestV8QueryObjectsCtor();
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor), 1);
|
||||
assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'count' }), 1);
|
||||
assert.deepStrictEqual(
|
||||
v8.queryObjects(TestV8QueryObjectsCtor, { format: 'summary' }),
|
||||
[ format(obj)]
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Classic inheritance.
|
||||
function TestV8QueryObjectsBaseCtor() {}
|
||||
|
||||
function TestV8QueryObjectsChildCtor() {}
|
||||
Object.setPrototypeOf(TestV8QueryObjectsChildCtor.prototype, TestV8QueryObjectsBaseCtor.prototype);
|
||||
Object.setPrototypeOf(TestV8QueryObjectsChildCtor, TestV8QueryObjectsBaseCtor);
|
||||
|
||||
const summary = v8.queryObjects(TestV8QueryObjectsBaseCtor, { format: 'summary' });
|
||||
assert.deepStrictEqual(summary, [
|
||||
format(TestV8QueryObjectsChildCtor.prototype),
|
||||
]);
|
||||
const obj = new TestV8QueryObjectsChildCtor();
|
||||
assert.deepStrictEqual(
|
||||
v8.queryObjects(TestV8QueryObjectsChildCtor, { format: 'summary' }),
|
||||
[ format(obj) ],
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user