[inspector] added Runtime.queryObjects
Runtime.queryObjects method: 1. force gc, 2. iterate through heap and get all objects with passed constructorName or with passed constructor name in prototype chain, 3. return these objects as JSArray. Main use case is regression tests for memory leaks. R=pfeldman@chromium.org,alph@chromium.org,ulan@chromium.org Bug: v8:6732 Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_chromium_rel_ng Change-Id: I52f0803366f14bb24376653615d870a4f21f83e7 Reviewed-on: https://chromium-review.googlesource.com/619594 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Alexei Filippov <alph@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Pavel Feldman <pfeldman@chromium.org> Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Cr-Commit-Position: refs/heads/master@{#47478}
This commit is contained in:
parent
b99f783065
commit
f546ec1a5d
@ -9987,6 +9987,15 @@ v8::Local<debug::GeneratorObject> debug::GeneratorObject::Cast(
|
||||
return ToApiHandle<debug::GeneratorObject>(Utils::OpenHandle(*value));
|
||||
}
|
||||
|
||||
void debug::QueryObjects(v8::Local<v8::Context> v8_context,
|
||||
QueryObjectPredicate* predicate,
|
||||
PersistentValueVector<v8::Object>* objects) {
|
||||
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_context->GetIsolate());
|
||||
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
|
||||
isolate->heap_profiler()->QueryObjects(Utils::OpenHandle(*v8_context),
|
||||
predicate, objects);
|
||||
}
|
||||
|
||||
Local<String> CpuProfileNode::GetFunctionName() const {
|
||||
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
|
||||
i::Isolate* isolate = node->isolate();
|
||||
|
@ -391,6 +391,20 @@ class StackTraceIterator {
|
||||
DISALLOW_COPY_AND_ASSIGN(StackTraceIterator);
|
||||
};
|
||||
|
||||
class QueryObjectPredicate {
|
||||
public:
|
||||
virtual ~QueryObjectPredicate() = default;
|
||||
virtual bool Filter(v8::Local<v8::Object> object) = 0;
|
||||
|
||||
protected:
|
||||
// This method can be used only inside of Filter function.
|
||||
v8::MaybeLocal<v8::Function> GetConstructor(v8::Object* object);
|
||||
};
|
||||
|
||||
void QueryObjects(v8::Local<v8::Context> context,
|
||||
QueryObjectPredicate* predicate,
|
||||
v8::PersistentValueVector<v8::Object>* objects);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace v8
|
||||
|
||||
|
@ -343,6 +343,16 @@
|
||||
{ "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."}
|
||||
],
|
||||
"description": "Runs script with given id in a given context."
|
||||
},
|
||||
{
|
||||
"name": "queryObjects",
|
||||
"parameters": [
|
||||
{ "name": "constructorObjectId", "$ref": "RemoteObjectId", "description": "Identifier of the constructor to return objects for." }
|
||||
],
|
||||
"returns": [
|
||||
{ "name": "objects", "$ref": "RemoteObject", "description": "Array with objects." }
|
||||
],
|
||||
"experimental": true
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "src/inspector/v8-debugger.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/inspector/inspected-context.h"
|
||||
#include "src/inspector/protocol/Protocol.h"
|
||||
#include "src/inspector/script-breakpoint.h"
|
||||
@ -130,6 +132,65 @@ void cleanupExpiredWeakPointers(Map& map) {
|
||||
}
|
||||
}
|
||||
|
||||
class QueryObjectPredicate : public v8::debug::QueryObjectPredicate {
|
||||
public:
|
||||
QueryObjectPredicate(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Function> constructor)
|
||||
: m_context(context), m_constructor(constructor) {}
|
||||
|
||||
bool Filter(v8::Local<v8::Object> object) override {
|
||||
if (CheckObject(*object)) return true;
|
||||
std::vector<v8::Object*> prototypeChain;
|
||||
v8::Local<v8::Value> prototype;
|
||||
// Get prototype chain for current object until first visited prototype.
|
||||
for (prototype = object->GetPrototype(); IsUnvisitedPrototype(prototype);
|
||||
prototype = prototype.As<v8::Object>()->GetPrototype()) {
|
||||
prototypeChain.push_back(v8::Object::Cast(*prototype));
|
||||
}
|
||||
// Include first visited prototype if any.
|
||||
if (prototype->IsObject()) {
|
||||
prototypeChain.push_back(v8::Object::Cast(*prototype));
|
||||
}
|
||||
bool hasMatched = false;
|
||||
// Go from last prototype to first one, mark all prototypes as matched after
|
||||
// first matched prototype.
|
||||
for (auto it = prototypeChain.rbegin(); it != prototypeChain.rend(); ++it) {
|
||||
hasMatched = hasMatched || CheckObject(*it);
|
||||
if (hasMatched) m_matchedPrototypes.insert(*it);
|
||||
m_visitedPrototypes.insert(*it);
|
||||
}
|
||||
return hasMatched;
|
||||
}
|
||||
|
||||
private:
|
||||
bool CheckObject(v8::Object* object) {
|
||||
if (m_matchedPrototypes.find(object) != m_matchedPrototypes.end())
|
||||
return true;
|
||||
if (m_visitedPrototypes.find(object) != m_visitedPrototypes.end())
|
||||
return false;
|
||||
v8::Local<v8::Context> objectContext = object->CreationContext();
|
||||
if (objectContext != m_context) return false;
|
||||
v8::Local<v8::Function> constructor;
|
||||
if (!GetConstructor(object).ToLocal(&constructor)) return false;
|
||||
return constructor == m_constructor;
|
||||
}
|
||||
|
||||
bool IsUnvisitedPrototype(v8::Local<v8::Value> prototypeValue) {
|
||||
if (!prototypeValue->IsObject()) return false;
|
||||
v8::Object* prototypeObject = v8::Object::Cast(*prototypeValue);
|
||||
v8::Local<v8::Context> prototypeContext =
|
||||
prototypeObject->CreationContext();
|
||||
if (prototypeContext != m_context) return false;
|
||||
return m_visitedPrototypes.find(prototypeObject) ==
|
||||
m_visitedPrototypes.end();
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> m_context;
|
||||
v8::Local<v8::Function> m_constructor;
|
||||
std::unordered_set<v8::Object*> m_visitedPrototypes;
|
||||
std::unordered_set<v8::Object*> m_matchedPrototypes;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
|
||||
@ -661,6 +722,24 @@ v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
|
||||
return properties;
|
||||
}
|
||||
|
||||
v8::Local<v8::Array> V8Debugger::queryObjects(
|
||||
v8::Local<v8::Context> context, v8::Local<v8::Function> constructor) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
v8::PersistentValueVector<v8::Object> v8Objects(isolate);
|
||||
QueryObjectPredicate predicate(context, constructor);
|
||||
v8::debug::QueryObjects(context, &predicate, &v8Objects);
|
||||
|
||||
v8::MicrotasksScope microtasksScope(isolate,
|
||||
v8::MicrotasksScope::kDoNotRunMicrotasks);
|
||||
v8::Local<v8::Array> resultArray = v8::Array::New(
|
||||
m_inspector->isolate(), static_cast<int>(v8Objects.Size()));
|
||||
for (size_t i = 0; i < v8Objects.Size(); ++i) {
|
||||
createDataProperty(context, resultArray, static_cast<int>(i),
|
||||
v8Objects.Get(i));
|
||||
}
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
|
||||
v8::Local<v8::StackTrace> v8StackTrace) {
|
||||
return V8StackTraceImpl::create(this, currentContextGroupId(), v8StackTrace,
|
||||
|
@ -88,6 +88,9 @@ class V8Debugger : public v8::debug::DebugDelegate {
|
||||
v8::MaybeLocal<v8::Array> internalProperties(v8::Local<v8::Context>,
|
||||
v8::Local<v8::Value>);
|
||||
|
||||
v8::Local<v8::Array> queryObjects(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Function> constructor);
|
||||
|
||||
void asyncTaskScheduled(const StringView& taskName, void* task,
|
||||
bool recurring);
|
||||
void asyncTaskCanceled(void* task);
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "src/inspector/v8-inspector-impl.h"
|
||||
#include "src/inspector/v8-inspector-session-impl.h"
|
||||
#include "src/inspector/v8-stack-trace-impl.h"
|
||||
#include "src/inspector/v8-value-utils.h"
|
||||
#include "src/tracing/trace-event.h"
|
||||
|
||||
#include "include/v8-inspector.h"
|
||||
@ -521,6 +522,21 @@ void V8RuntimeAgentImpl::runScript(
|
||||
EvaluateCallbackWrapper<RunScriptCallback>::wrap(std::move(callback)));
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::queryObjects(
|
||||
const String16& constructorObjectId,
|
||||
std::unique_ptr<protocol::Runtime::RemoteObject>* objects) {
|
||||
InjectedScript::ObjectScope scope(m_session, constructorObjectId);
|
||||
Response response = scope.initialize();
|
||||
if (!response.isSuccess()) return response;
|
||||
if (!scope.object()->IsFunction()) {
|
||||
return Response::Error("Constructor should be instance of Function");
|
||||
}
|
||||
v8::Local<v8::Array> resultArray = m_inspector->debugger()->queryObjects(
|
||||
scope.context(), v8::Local<v8::Function>::Cast(scope.object()));
|
||||
return scope.injectedScript()->wrapObject(
|
||||
resultArray, scope.objectGroupName(), false, false, objects);
|
||||
}
|
||||
|
||||
void V8RuntimeAgentImpl::restore() {
|
||||
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
|
||||
return;
|
||||
|
@ -97,6 +97,9 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
|
||||
Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
|
||||
Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
|
||||
std::unique_ptr<RunScriptCallback>) override;
|
||||
Response queryObjects(
|
||||
const String16& constructorObjectId,
|
||||
std::unique_ptr<protocol::Runtime::RemoteObject>* objects) override;
|
||||
|
||||
void reset();
|
||||
void reportExecutionContextCreated(InspectedContext*);
|
||||
|
@ -3541,6 +3541,8 @@ Handle<Context> JSReceiver::GetCreationContext() {
|
||||
while (receiver->IsJSBoundFunction()) {
|
||||
receiver = JSBoundFunction::cast(receiver)->bound_target_function();
|
||||
}
|
||||
// Externals are JSObjects with null as a constructor.
|
||||
DCHECK(!receiver->IsExternal());
|
||||
Object* constructor = receiver->map()->GetConstructor();
|
||||
JSFunction* function;
|
||||
if (constructor->IsJSFunction()) {
|
||||
|
@ -12,6 +12,19 @@
|
||||
#include "src/profiler/sampling-heap-profiler.h"
|
||||
|
||||
namespace v8 {
|
||||
|
||||
v8::MaybeLocal<v8::Function> debug::QueryObjectPredicate::GetConstructor(
|
||||
v8::Object* v8_object) {
|
||||
internal::Handle<internal::JSReceiver> object(Utils::OpenHandle(v8_object));
|
||||
internal::Handle<internal::Map> map(object->map());
|
||||
internal::Object* maybe_constructor = map->GetConstructor();
|
||||
if (maybe_constructor->IsJSFunction()) {
|
||||
return Utils::ToLocal(
|
||||
internal::handle(internal::JSFunction::cast(maybe_constructor)));
|
||||
}
|
||||
return v8::MaybeLocal<v8::Function>();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
HeapProfiler::HeapProfiler(Heap* heap)
|
||||
@ -207,6 +220,25 @@ void HeapProfiler::ClearHeapObjectMap() {
|
||||
|
||||
Heap* HeapProfiler::heap() const { return ids_->heap(); }
|
||||
|
||||
void HeapProfiler::QueryObjects(Handle<Context> context,
|
||||
debug::QueryObjectPredicate* predicate,
|
||||
PersistentValueVector<v8::Object>* objects) {
|
||||
// We should return accurate information about live objects, so we need to
|
||||
// collect all garbage first.
|
||||
isolate()->heap()->CollectAllAvailableGarbage(
|
||||
GarbageCollectionReason::kLowMemoryNotification);
|
||||
heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask,
|
||||
GarbageCollectionReason::kHeapProfiler);
|
||||
HeapIterator heap_iterator(heap(), HeapIterator::kFilterUnreachable);
|
||||
HeapObject* heap_obj;
|
||||
while ((heap_obj = heap_iterator.next()) != nullptr) {
|
||||
if (!heap_obj->IsJSObject() || heap_obj->IsExternal()) continue;
|
||||
v8::Local<v8::Object> v8_obj(
|
||||
Utils::ToLocal(handle(JSObject::cast(heap_obj))));
|
||||
if (!predicate->Filter(v8_obj)) continue;
|
||||
objects->Append(v8_obj);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -76,6 +76,10 @@ class HeapProfiler {
|
||||
|
||||
Isolate* isolate() const { return heap()->isolate(); }
|
||||
|
||||
void QueryObjects(Handle<Context> context,
|
||||
debug::QueryObjectPredicate* predicate,
|
||||
v8::PersistentValueVector<v8::Object>* objects);
|
||||
|
||||
private:
|
||||
Heap* heap() const;
|
||||
|
||||
|
73
test/inspector/runtime/query-objects-expected.txt
Normal file
73
test/inspector/runtime/query-objects-expected.txt
Normal file
@ -0,0 +1,73 @@
|
||||
Checks Runtime.queryObjects
|
||||
|
||||
Running test: testClass
|
||||
Declare class Foo & store its constructor.
|
||||
Create object with class Foo.
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Foo
|
||||
[1] : Foo
|
||||
]
|
||||
Create object with class Foo.
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Foo
|
||||
[1] : Foo
|
||||
[2] : Foo
|
||||
]
|
||||
|
||||
Running test: testDerivedNewClass
|
||||
Declare class Foo & store its constructor.
|
||||
Declare class Boo extends Foo & store its constructor.
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Boo
|
||||
[1] : Foo
|
||||
]
|
||||
Query objects with Boo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Boo
|
||||
]
|
||||
Create object with class Foo
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Boo
|
||||
[1] : Foo
|
||||
[2] : Foo
|
||||
]
|
||||
Create object with class Boo
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Boo
|
||||
[1] : Boo
|
||||
[2] : Foo
|
||||
[3] : Foo
|
||||
]
|
||||
Query objects with Boo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Boo
|
||||
[1] : Boo
|
||||
]
|
||||
|
||||
Running test: testNewFunction
|
||||
Declare Foo & store it.
|
||||
Create object using Foo.
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Foo
|
||||
]
|
||||
Create object using Foo.
|
||||
Query objects with Foo constructor.
|
||||
Dump each object constructor name.
|
||||
[
|
||||
[0] : Foo
|
||||
[1] : Foo
|
||||
]
|
102
test/inspector/runtime/query-objects.js
Normal file
102
test/inspector/runtime/query-objects.js
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2017 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
let {session, contextGroup, Protocol} =
|
||||
InspectorTest.start('Checks Runtime.queryObjects');
|
||||
|
||||
InspectorTest.runAsyncTestSuite([
|
||||
async function testClass() {
|
||||
let contextGroup = new InspectorTest.ContextGroup();
|
||||
let session = contextGroup.connect();
|
||||
let Protocol = session.Protocol;
|
||||
|
||||
InspectorTest.log('Declare class Foo & store its constructor.');
|
||||
await Protocol.Runtime.evaluate({
|
||||
expression: 'class Foo{constructor(){}};'
|
||||
});
|
||||
let {result:{result:{objectId}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Foo'
|
||||
});
|
||||
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
InspectorTest.log('Create object with class Foo.');
|
||||
Protocol.Runtime.evaluate({expression: 'new Foo()'});
|
||||
await queryObjects(session, objectId, 'Foo');
|
||||
}
|
||||
|
||||
session.disconnect();
|
||||
},
|
||||
|
||||
async function testDerivedNewClass() {
|
||||
let contextGroup = new InspectorTest.ContextGroup();
|
||||
let session = contextGroup.connect();
|
||||
let Protocol = session.Protocol;
|
||||
|
||||
InspectorTest.log('Declare class Foo & store its constructor.');
|
||||
Protocol.Runtime.evaluate({expression: 'class Foo{};'});
|
||||
let {result:{result:{objectId}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Foo'
|
||||
});
|
||||
let fooConstructorId = objectId;
|
||||
|
||||
InspectorTest.log('Declare class Boo extends Foo & store its constructor.');
|
||||
Protocol.Runtime.evaluate({expression: 'class Boo extends Foo{};'});
|
||||
({result:{result:{objectId}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Boo'
|
||||
}));
|
||||
let booConstructorId = objectId;
|
||||
|
||||
await queryObjects(session, fooConstructorId, 'Foo');
|
||||
await queryObjects(session, booConstructorId, 'Boo');
|
||||
|
||||
InspectorTest.log('Create object with class Foo');
|
||||
Protocol.Runtime.evaluate({expression: 'new Foo()'});
|
||||
await queryObjects(session, fooConstructorId, 'Foo');
|
||||
|
||||
InspectorTest.log('Create object with class Boo');
|
||||
Protocol.Runtime.evaluate({expression: 'new Boo()'});
|
||||
await queryObjects(session, fooConstructorId, 'Foo');
|
||||
await queryObjects(session, booConstructorId, 'Boo');
|
||||
|
||||
session.disconnect();
|
||||
},
|
||||
|
||||
async function testNewFunction() {
|
||||
let contextGroup = new InspectorTest.ContextGroup();
|
||||
let session = contextGroup.connect();
|
||||
let Protocol = session.Protocol;
|
||||
|
||||
InspectorTest.log('Declare Foo & store it.');
|
||||
Protocol.Runtime.evaluate({expression: 'function Foo(){}'});
|
||||
let {result:{result:{objectId}}} = await Protocol.Runtime.evaluate({
|
||||
expression: 'Foo'
|
||||
});
|
||||
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
InspectorTest.log('Create object using Foo.');
|
||||
Protocol.Runtime.evaluate({expression: 'new Foo()'});
|
||||
await queryObjects(session, objectId, 'Foo');
|
||||
}
|
||||
session.disconnect();
|
||||
}
|
||||
]);
|
||||
|
||||
const constructorsNameFunction = `
|
||||
function() {
|
||||
return this.map(o => o.constructor.name).sort();
|
||||
}`;
|
||||
|
||||
async function queryObjects(sesion, constructorObjectId, name) {
|
||||
let {result:{objects}} = await sesion.Protocol.Runtime.queryObjects({
|
||||
constructorObjectId
|
||||
});
|
||||
InspectorTest.log(`Query objects with ${name} constructor.`);
|
||||
let {result:{result:{value}}} = await sesion.Protocol.Runtime.callFunctionOn({
|
||||
objectId: objects.objectId,
|
||||
functionDeclaration: constructorsNameFunction,
|
||||
returnByValue: true
|
||||
});
|
||||
InspectorTest.log('Dump each object constructor name.');
|
||||
InspectorTest.logMessage(value);
|
||||
}
|
Loading…
Reference in New Issue
Block a user