| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 9930f11 commit 7f2d61f
14 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -242,6 +242,89 @@ buffers and external strings. | |||
| 242 | 242 | } | |
| 243 | 243 | ``` | |
| 244 | 244 | ||
| 245 | + ## `v8.queryObjects(ctor[, options])` | ||
| 246 | + | ||
| 247 | + <!-- YAML | ||
| 248 | + added: REPLACEME | ||
| 249 | + --> | ||
| 250 | + | ||
| 251 | + > Stability: 1.1 - Active development | ||
| 252 | + | ||
| 253 | + * `ctor` {Function} The constructor that can be used to search on the | ||
| 254 | + prototype chain in order to filter target objects in the heap. | ||
| 255 | + * `options` {undefined|Object} | ||
| 256 | + * `format` {string} If it's `'count'`, the count of matched objects | ||
| 257 | + is returned. If it's `'summary'`, an array with summary strings | ||
| 258 | + of the matched objects is returned. | ||
| 259 | + * Returns: {number|Array<string>} | ||
| 260 | + | ||
| 261 | + This is similar to the [`queryObjects()` console API][] provided by the | ||
| 262 | + Chromium DevTools console. It can be used to search for objects that | ||
| 263 | + have the matching constructor on its prototype chain in the heap after | ||
| 264 | + a full garbage collection, which can be useful for memory leak | ||
| 265 | + regression tests. To avoid surprising results, users should avoid using | ||
| 266 | + this API on constructors whose implementation they don't control, or on | ||
| 267 | + constructors that can be invoked by other parties in the application. | ||
| 268 | + | ||
| 269 | + To avoid accidental leaks, this API does not return raw references to | ||
| 270 | + the objects found. By default, it returns the count of the objects | ||
| 271 | + found. If `options.format` is `'summary'`, it returns an array | ||
| 272 | + containing brief string representations for each object. The visibility | ||
| 273 | + provided in this API is similar to what the heap snapshot provides, | ||
| 274 | + while users can save the cost of serialization and parsing and directly | ||
| 275 | + filter the target objects during the search. | ||
| 276 | + | ||
| 277 | + Only objects created in the current execution context are included in the | ||
| 278 | + results. | ||
| 279 | + | ||
| 280 | + ```cjs | ||
| 281 | + const { queryObjects } = require('node:v8'); | ||
| 282 | + class A { foo = 'bar'; } | ||
| 283 | + console.log(queryObjects(A)); // 0 | ||
| 284 | + const a = new A(); | ||
| 285 | + console.log(queryObjects(A)); // 1 | ||
| 286 | + // [ "A { foo: 'bar' }" ] | ||
| 287 | + console.log(queryObjects(A, { format: 'summary' })); | ||
| 288 | + | ||
| 289 | + class B extends A { bar = 'qux'; } | ||
| 290 | + const b = new B(); | ||
| 291 | + console.log(queryObjects(B)); // 1 | ||
| 292 | + // [ "B { foo: 'bar', bar: 'qux' }" ] | ||
| 293 | + console.log(queryObjects(B, { format: 'summary' })); | ||
| 294 | + | ||
| 295 | + // Note that, when there are child classes inheriting from a constructor, | ||
| 296 | + // the constructor also shows up in the prototype chain of the child | ||
| 297 | + // classes's prototoype, so the child classes's prototoype would also be | ||
| 298 | + // included in the result. | ||
| 299 | + console.log(queryObjects(A)); // 3 | ||
| 300 | + // [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ] | ||
| 301 | + console.log(queryObjects(A, { format: 'summary' })); | ||
| 302 | + ``` | ||
| 303 | + | ||
| 304 | + ```mjs | ||
| 305 | + import { queryObjects } from 'node:v8'; | ||
| 306 | + class A { foo = 'bar'; } | ||
| 307 | + console.log(queryObjects(A)); // 0 | ||
| 308 | + const a = new A(); | ||
| 309 | + console.log(queryObjects(A)); // 1 | ||
| 310 | + // [ "A { foo: 'bar' }" ] | ||
| 311 | + console.log(queryObjects(A, { format: 'summary' })); | ||
| 312 | + | ||
| 313 | + class B extends A { bar = 'qux'; } | ||
| 314 | + const b = new B(); | ||
| 315 | + console.log(queryObjects(B)); // 1 | ||
| 316 | + // [ "B { foo: 'bar', bar: 'qux' }" ] | ||
| 317 | + console.log(queryObjects(B, { format: 'summary' })); | ||
| 318 | + | ||
| 319 | + // Note that, when there are child classes inheriting from a constructor, | ||
| 320 | + // the constructor also shows up in the prototype chain of the child | ||
| 321 | + // classes's prototoype, so the child classes's prototoype would also be | ||
| 322 | + // included in the result. | ||
| 323 | + console.log(queryObjects(A)); // 3 | ||
| 324 | + // [ "B { foo: 'bar', bar: 'qux' }", 'A {}', "A { foo: 'bar' }" ] | ||
| 325 | + console.log(queryObjects(A, { format: 'summary' })); | ||
| 326 | + ``` | ||
| 327 | + | ||
| 245 | 328 | ## `v8.setFlagsFromString(flags)` | |
| 246 | 329 | ||
| 247 | 330 | <!-- YAML | |
@@ -1212,6 +1295,7 @@ setTimeout(() => { | |||
| 1212 | 1295 | [`deserializer._readHostObject()`]: #deserializer_readhostobject | |
| 1213 | 1296 | [`deserializer.transferArrayBuffer()`]: #deserializertransferarraybufferid-arraybuffer | |
| 1214 | 1297 | [`init` callback]: #initpromise-parent | |
| 1298 | + [`queryObjects()` console API]: https://developer.chrome.com/docs/devtools/console/utilities#queryObjects-function | ||
| 1215 | 1299 | [`serialize()`]: #v8serializevalue | |
| 1216 | 1300 | [`serializer._getSharedArrayBufferId()`]: #serializer_getsharedarraybufferidsharedarraybuffer | |
| 1217 | 1301 | [`serializer._writeHostObject()`]: #serializer_writehostobjectobject | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,16 +2,31 @@ | |||
| 2 | 2 | const { | |
| 3 | 3 | Symbol, | |
| 4 | 4 | Uint8Array, | |
| 5 | + ArrayPrototypeMap, | ||
| 5 | 6 | } = primordials; | |
| 6 | 7 | const { | |
| 7 | 8 | kUpdateTimer, | |
| 8 | 9 | onStreamRead, | |
| 9 | 10 | } = require('internal/stream_base_commons'); | |
| 10 | 11 | const { owner_symbol } = require('internal/async_hooks').symbols; | |
| 11 | 12 | const { Readable } = require('stream'); | |
| 12 | - const { validateObject, validateBoolean } = require('internal/validators'); | ||
| 13 | - const { kEmptyObject } = require('internal/util'); | ||
| 14 | - | ||
| 13 | + const { | ||
| 14 | + validateObject, | ||
| 15 | + validateBoolean, | ||
| 16 | + validateFunction, | ||
| 17 | + } = require('internal/validators'); | ||
| 18 | + const { | ||
| 19 | + codes: { | ||
| 20 | + ERR_INVALID_ARG_VALUE, | ||
| 21 | + }, | ||
| 22 | + } = require('internal/errors'); | ||
| 23 | + const { kEmptyObject, emitExperimentalWarning } = require('internal/util'); | ||
| 24 | + const { | ||
| 25 | + queryObjects: _queryObjects, | ||
| 26 | + } = internalBinding('internal_only_v8'); | ||
| 27 | + const { | ||
| 28 | + inspect, | ||
| 29 | + } = require('internal/util/inspect'); | ||
| 15 | 30 | const kHandle = Symbol('kHandle'); | |
| 16 | 31 | ||
| 17 | 32 | function getHeapSnapshotOptions(options = kEmptyObject) { | |
@@ -50,7 +65,31 @@ class HeapSnapshotStream extends Readable { | |||
| 50 | 65 | } | |
| 51 | 66 | } | |
| 52 | 67 | ||
| 68 | + const inspectOptions = { | ||
| 69 | + __proto__: null, | ||
| 70 | + depth: 0, | ||
| 71 | + }; | ||
| 72 | + function queryObjects(ctor, options = kEmptyObject) { | ||
| 73 | + validateFunction(ctor, 'constructor'); | ||
| 74 | + if (options !== kEmptyObject) { | ||
| 75 | + validateObject(options, 'options'); | ||
| 76 | + } | ||
| 77 | + const format = options.format || 'count'; | ||
| 78 | + if (format !== 'count' && format !== 'summary') { | ||
| 79 | + throw new ERR_INVALID_ARG_VALUE('options.format', format); | ||
| 80 | + } | ||
| 81 | + emitExperimentalWarning('v8.queryObjects()'); | ||
| 82 | + // Matching the console API behavior - just access the .prototype. | ||
| 83 | + const objects = _queryObjects(ctor.prototype); | ||
| 84 | + if (format === 'count') { | ||
| 85 | + return objects.length; | ||
| 86 | + } | ||
| 87 | + // options.format is 'summary'. | ||
| 88 | + return ArrayPrototypeMap(objects, (object) => inspect(object, inspectOptions)); | ||
| 89 | + } | ||
| 90 | + | ||
| 53 | 91 | module.exports = { | |
| 54 | 92 | getHeapSnapshotOptions, | |
| 55 | 93 | HeapSnapshotStream, | |
| 94 | + queryObjects, | ||
| 56 | 95 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,16 +1,33 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + Error, | ||
| 5 | + StringPrototypeStartsWith, | ||
| 4 | 6 | globalThis, | |
| 5 | 7 | } = primordials; | |
| 6 | 8 | ||
| 7 | 9 | process.emitWarning( | |
| 8 | 10 | 'These APIs are for internal testing only. Do not use them.', | |
| 9 | 11 | 'internal/test/binding'); | |
| 10 | 12 | ||
| 13 | + function filteredInternalBinding(id) { | ||
| 14 | + // Disallows internal bindings with names that start with 'internal_only' | ||
| 15 | + // which means it should not be exposed to users even with | ||
| 16 | + // --expose-internals. | ||
| 17 | + if (StringPrototypeStartsWith(id, 'internal_only')) { | ||
| 18 | + // This code is only intended for internal errors and is not documented. | ||
| 19 | + // Do not use the normal error system. | ||
| 20 | + // eslint-disable-next-line no-restricted-syntax | ||
| 21 | + const error = new Error(`No such binding: ${id}`); | ||
| 22 | + error.code = 'ERR_INVALID_MODULE'; | ||
| 23 | + throw error; | ||
| 24 | + } | ||
| 25 | + return internalBinding(id); | ||
| 26 | + } | ||
| 27 | + | ||
| 11 | 28 | if (module.isPreloading) { | |
| 12 | - globalThis.internalBinding = internalBinding; | ||
| 29 | + globalThis.internalBinding = filteredInternalBinding; | ||
| 13 | 30 | globalThis.primordials = primordials; | |
| 14 | 31 | } | |
| 15 | 32 | ||
| 16 | - module.exports = { internalBinding, primordials }; | ||
| 33 | + module.exports = { internalBinding: filteredInternalBinding, primordials }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,6 +60,7 @@ const { | |||
| 60 | 60 | const { | |
| 61 | 61 | HeapSnapshotStream, | |
| 62 | 62 | getHeapSnapshotOptions, | |
| 63 | + queryObjects, | ||
| 63 | 64 | } = require('internal/heap_utils'); | |
| 64 | 65 | const promiseHooks = require('internal/promise_hooks'); | |
| 65 | 66 | const { getOptionValue } = require('internal/options'); | |
@@ -437,6 +438,7 @@ module.exports = { | |||
| 437 | 438 | serialize, | |
| 438 | 439 | writeHeapSnapshot, | |
| 439 | 440 | promiseHooks, | |
| 441 | + queryObjects, | ||
| 440 | 442 | startupSnapshot, | |
| 441 | 443 | setHeapSnapshotNearHeapLimit, | |
| 442 | 444 | GCProfiler, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,6 +80,7 @@ | |||
| 80 | 80 | 'src/handle_wrap.cc', | |
| 81 | 81 | 'src/heap_utils.cc', | |
| 82 | 82 | 'src/histogram.cc', | |
| 83 | + 'src/internal_only_v8.cc', | ||
| 83 | 84 | 'src/js_native_api.h', | |
| 84 | 85 | 'src/js_native_api_types.h', | |
| 85 | 86 | 'src/js_native_api_v8.cc', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -474,39 +474,6 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) { | |||
| 474 | 474 | return args.GetReturnValue().Set(filename_v); | |
| 475 | 475 | } | |
| 476 | 476 | ||
| 477 | - class PrototypeChainHas : public v8::QueryObjectPredicate { | ||
| 478 | - public: | ||
| 479 | - PrototypeChainHas(Local<Context> context, Local<Object> search) | ||
| 480 | - : context_(context), search_(search) {} | ||
| 481 | - | ||
| 482 | - // What we can do in the filter can be quite limited, but looking up | ||
| 483 | - // the prototype chain is something that the inspector console API | ||
| 484 | - // queryObject() does so it is supported. | ||
| 485 | - bool Filter(Local<Object> object) override { | ||
| 486 | - for (Local<Value> proto = object->GetPrototype(); proto->IsObject(); | ||
| 487 | - proto = proto.As<Object>()->GetPrototype()) { | ||
| 488 | - if (search_ == proto) return true; | ||
| 489 | - } | ||
| 490 | - return false; | ||
| 491 | - } | ||
| 492 | - | ||
| 493 | - private: | ||
| 494 | - Local<Context> context_; | ||
| 495 | - Local<Object> search_; | ||
| 496 | - }; | ||
| 497 | - | ||
| 498 | - void CountObjectsWithPrototype(const FunctionCallbackInfo<Value>& args) { | ||
| 499 | - CHECK_EQ(args.Length(), 1); | ||
| 500 | - CHECK(args[0]->IsObject()); | ||
| 501 | - Local<Object> proto = args[0].As<Object>(); | ||
| 502 | - Isolate* isolate = args.GetIsolate(); | ||
| 503 | - Local<Context> context = isolate->GetCurrentContext(); | ||
| 504 | - PrototypeChainHas prototype_chain_has(context, proto); | ||
| 505 | - std::vector<Global<Object>> out; | ||
| 506 | - isolate->GetHeapProfiler()->QueryObjects(context, &prototype_chain_has, &out); | ||
| 507 | - args.GetReturnValue().Set(static_cast<uint32_t>(out.size())); | ||
| 508 | - } | ||
| 509 | - | ||
| 510 | 477 | void Initialize(Local<Object> target, | |
| 511 | 478 | Local<Value> unused, | |
| 512 | 479 | Local<Context> context, | |
@@ -515,15 +482,12 @@ void Initialize(Local<Object> target, | |||
| 515 | 482 | SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot); | |
| 516 | 483 | SetMethod( | |
| 517 | 484 | context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream); | |
| 518 | - SetMethod( | ||
| 519 | - context, target, "countObjectsWithPrototype", CountObjectsWithPrototype); | ||
| 520 | 485 | } | |
| 521 | 486 | ||
| 522 | 487 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | |
| 523 | 488 | registry->Register(BuildEmbedderGraph); | |
| 524 | 489 | registry->Register(TriggerHeapSnapshot); | |
| 525 | 490 | registry->Register(CreateHeapSnapshotStream); | |
| 526 | - registry->Register(CountObjectsWithPrototype); | ||
| 527 | 491 | } | |
| 528 | 492 | ||
| 529 | 493 | } // namespace heap | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,85 @@ | |||
| 1 | + #include "node_binding.h" | ||
| 2 | + #include "node_external_reference.h" | ||
| 3 | + #include "util-inl.h" | ||
| 4 | + #include "v8-profiler.h" | ||
| 5 | + #include "v8.h" | ||
| 6 | + | ||
| 7 | + using v8::Array; | ||
| 8 | + using v8::Context; | ||
| 9 | + using v8::FunctionCallbackInfo; | ||
| 10 | + using v8::Global; | ||
| 11 | + using v8::Isolate; | ||
| 12 | + using v8::Local; | ||
| 13 | + using v8::Object; | ||
| 14 | + using v8::Value; | ||
| 15 | + | ||
| 16 | + namespace node { | ||
| 17 | + namespace internal_only_v8 { | ||
| 18 | + | ||
| 19 | + class PrototypeChainHas : public v8::QueryObjectPredicate { | ||
| 20 | + public: | ||
| 21 | + PrototypeChainHas(Local<Context> context, Local<Object> search) | ||
| 22 | + : context_(context), search_(search) {} | ||
| 23 | + | ||
| 24 | + // What we can do in the filter can be quite limited, but looking up | ||
| 25 | + // the prototype chain is something that the inspector console API | ||
| 26 | + // queryObject() does so it is supported. | ||
| 27 | + bool Filter(Local<Object> object) override { | ||
| 28 | + Local<Context> creation_context; | ||
| 29 | + if (!object->GetCreationContext().ToLocal(&creation_context)) { | ||
| 30 | + return false; | ||
| 31 | + } | ||
| 32 | + if (creation_context != context_) { | ||
| 33 | + return false; | ||
| 34 | + } | ||
| 35 | + for (Local<Value> proto = object->GetPrototype(); proto->IsObject(); | ||
| 36 | + proto = proto.As<Object>()->GetPrototype()) { | ||
| 37 | + if (search_ == proto) return true; | ||
| 38 | + } | ||
| 39 | + return false; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + private: | ||
| 43 | + Local<Context> context_; | ||
| 44 | + Local<Object> search_; | ||
| 45 | + }; | ||
| 46 | + | ||
| 47 | + void QueryObjects(const FunctionCallbackInfo<Value>& args) { | ||
| 48 | + CHECK_EQ(args.Length(), 1); | ||
| 49 | + Isolate* isolate = args.GetIsolate(); | ||
| 50 | + if (!args[0]->IsObject()) { | ||
| 51 | + args.GetReturnValue().Set(Array::New(isolate)); | ||
| 52 | + return; | ||
| 53 | + } | ||
| 54 | + Local<Object> proto = args[0].As<Object>(); | ||
| 55 | + Local<Context> context = isolate->GetCurrentContext(); | ||
| 56 | + PrototypeChainHas prototype_chain_has(context, proto.As<Object>()); | ||
| 57 | + std::vector<Global<Object>> out; | ||
| 58 | + isolate->GetHeapProfiler()->QueryObjects(context, &prototype_chain_has, &out); | ||
| 59 | + std::vector<Local<Value>> result; | ||
| 60 | + result.reserve(out.size()); | ||
| 61 | + for (size_t i = 0; i < out.size(); ++i) { | ||
| 62 | + result.push_back(out[i].Get(isolate)); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + args.GetReturnValue().Set(Array::New(isolate, result.data(), result.size())); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + void Initialize(Local<Object> target, | ||
| 69 | + Local<Value> unused, | ||
| 70 | + Local<Context> context, | ||
| 71 | + void* priv) { | ||
| 72 | + SetMethod(context, target, "queryObjects", QueryObjects); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | ||
| 76 | + registry->Register(QueryObjects); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + } // namespace internal_only_v8 | ||
| 80 | + } // namespace node | ||
| 81 | + | ||
| 82 | + NODE_BINDING_CONTEXT_AWARE_INTERNAL(internal_only_v8, | ||
| 83 | + node::internal_only_v8::Initialize) | ||
| 84 | + NODE_BINDING_EXTERNAL_REFERENCE( | ||
| 85 | + internal_only_v8, node::internal_only_v8::RegisterExternalReferences) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,8 @@ | |||
| 26 | 26 | // function. This helps the built-in bindings are loaded properly when | |
| 27 | 27 | // node is built as static library. No need to depend on the | |
| 28 | 28 | // __attribute__((constructor)) like mechanism in GCC. | |
| 29 | + // The binding IDs that start with 'internal_only' are not exposed to the user | ||
| 30 | + // land even from internal/test/binding module under --expose-internals. | ||
| 29 | 31 | #define NODE_BUILTIN_STANDARD_BINDINGS(V) \ | |
| 30 | 32 | V(async_wrap) \ | |
| 31 | 33 | V(blob) \ | |
@@ -46,6 +48,7 @@ | |||
| 46 | 48 | V(http2) \ | |
| 47 | 49 | V(http_parser) \ | |
| 48 | 50 | V(inspector) \ | |
| 51 | + V(internal_only_v8) \ | ||
| 49 | 52 | V(js_stream) \ | |
| 50 | 53 | V(js_udp_wrap) \ | |
| 51 | 54 | V(messaging) \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -109,6 +109,7 @@ class ExternalReferenceRegistry { | |||
| 109 | 109 | V(fs_event_wrap) \ | |
| 110 | 110 | V(handle_wrap) \ | |
| 111 | 111 | V(heap_utils) \ | |
| 112 | + V(internal_only_v8) \ | ||
| 112 | 113 | V(messaging) \ | |
| 113 | 114 | V(mksnapshot) \ | |
| 114 | 115 | V(module_wrap) \ | |
| Back | FazBrowse Home | New Git URL |
0 commit comments