Skip to content

Commit 90b5ed1

Browse files
joyeecheungrichardlau
authored andcommitted
src: implement countObjectsWithPrototype
This implements an internal utility for counting objects in the heap with a specified prototype. In addition this adds a checkIfCollectableByCounting() test helper. PR-URL: #50572 Refs: v8/v8@0fd478b Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent a2f39e9 commit 90b5ed1

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

‎src/heap_utils.cc‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,39 @@ void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args){
474474
return args.GetReturnValue().Set(filename_v);
475475
}
476476

477+
classPrototypeChainHas : publicv8::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+
boolFilter(Local<Object> object) override{
486+
for (Local<Value> proto = object->GetPrototype(); proto->IsObject();
487+
proto = proto.As<Object>()->GetPrototype()){
488+
if (search_ == proto) returntrue;
489+
}
490+
returnfalse;
491+
}
492+
493+
private:
494+
Local<Context> context_;
495+
Local<Object> search_;
496+
};
497+
498+
voidCountObjectsWithPrototype(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+
477510
voidInitialize(Local<Object> target,
478511
Local<Value> unused,
479512
Local<Context> context,
@@ -482,12 +515,15 @@ void Initialize(Local<Object> target,
482515
SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot);
483516
SetMethod(
484517
context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream);
518+
SetMethod(
519+
context, target, "countObjectsWithPrototype", CountObjectsWithPrototype);
485520
}
486521

487522
voidRegisterExternalReferences(ExternalReferenceRegistry* registry){
488523
registry->Register(BuildEmbedderGraph);
489524
registry->Register(TriggerHeapSnapshot);
490525
registry->Register(CreateHeapSnapshotStream);
526+
registry->Register(CountObjectsWithPrototype);
491527
}
492528

493529
} // namespace heap

‎test/common/gc.js‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,54 @@ async function runAndBreathe(fn, repeat, waitTime = 20){
7575
}
7676
}
7777

78+
/**
79+
* This requires --expose-internals.
80+
* This function can be used to check if an object factory leaks or not by
81+
* iterating over the heap and count objects with the specified class
82+
* (which is checked by looking up the prototype chain).
83+
* @param{(i: number) => number} fn The factory receiving iteration count
84+
* and returning number of objects created. The return value should be
85+
* precise otherwise false negatives can be produced.
86+
* @param{Function} klass The class whose object is used to count the objects
87+
* @param{number} count Number of iterations that this check should be done
88+
* @param{number} waitTime Optional breathing time for GC.
89+
*/
90+
asyncfunctioncheckIfCollectableByCounting(fn,klass,count,waitTime=20){
91+
const{ internalBinding }=require('internal/test/binding');
92+
const{ countObjectsWithPrototype }=internalBinding('heap_utils');
93+
const{ prototype, name }=klass;
94+
constinitialCount=countObjectsWithPrototype(prototype);
95+
console.log(`Initial count of ${name}: ${initialCount}`);
96+
lettotalCreated=0;
97+
for(leti=0;i<count;++i){
98+
constcreated=awaitfn(i);
99+
totalCreated+=created;
100+
console.log(`#${i}: created ${created}${name}, total ${totalCreated}`);
101+
awaitwait(waitTime);// give GC some breathing room.
102+
constcurrentCount=countObjectsWithPrototype(prototype);
103+
constcollected=totalCreated-(currentCount-initialCount);
104+
console.log(`#${i}: counted ${currentCount}${name}, collected ${collected}`);
105+
if(collected>0){
106+
console.log(`Detected ${collected} collected ${name}, finish early`);
107+
return;
108+
}
109+
}
110+
111+
awaitwait(waitTime);// give GC some breathing room.
112+
constcurrentCount=countObjectsWithPrototype(prototype);
113+
constcollected=totalCreated-(currentCount-initialCount);
114+
console.log(`Last count: counted ${currentCount}${name}, collected ${collected}`);
115+
// Some objects with the prototype can be collected.
116+
if(collected>0){
117+
console.log(`Detected ${collected} collected ${name}`);
118+
return;
119+
}
120+
121+
thrownewError(`${name} cannot be collected`);
122+
}
123+
78124
module.exports={
79125
checkIfCollectable,
80126
runAndBreathe,
127+
checkIfCollectableByCounting,
81128
};

0 commit comments

Comments
(0)