Commit f546ec1a authored by Alexey Kozyatinskiy's avatar Alexey Kozyatinskiy Committed by Commit Bot

[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/619594Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarAlexei Filippov <alph@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarPavel Feldman <pfeldman@chromium.org>
Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47478}
parent b99f7830
...@@ -9987,6 +9987,15 @@ v8::Local<debug::GeneratorObject> debug::GeneratorObject::Cast( ...@@ -9987,6 +9987,15 @@ v8::Local<debug::GeneratorObject> debug::GeneratorObject::Cast(
return ToApiHandle<debug::GeneratorObject>(Utils::OpenHandle(*value)); 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 { Local<String> CpuProfileNode::GetFunctionName() const {
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this); const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
i::Isolate* isolate = node->isolate(); i::Isolate* isolate = node->isolate();
......
...@@ -391,6 +391,20 @@ class StackTraceIterator { ...@@ -391,6 +391,20 @@ class StackTraceIterator {
DISALLOW_COPY_AND_ASSIGN(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 debug
} // namespace v8 } // namespace v8
......
...@@ -343,6 +343,16 @@ ...@@ -343,6 +343,16 @@
{ "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."}
], ],
"description": "Runs script with given id in a given context." "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": [ "events": [
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "src/inspector/v8-debugger.h" #include "src/inspector/v8-debugger.h"
#include <unordered_set>
#include "src/inspector/inspected-context.h" #include "src/inspector/inspected-context.h"
#include "src/inspector/protocol/Protocol.h" #include "src/inspector/protocol/Protocol.h"
#include "src/inspector/script-breakpoint.h" #include "src/inspector/script-breakpoint.h"
...@@ -130,6 +132,65 @@ void cleanupExpiredWeakPointers(Map& map) { ...@@ -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 } // namespace
V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
...@@ -661,6 +722,24 @@ v8::MaybeLocal<v8::Array> V8Debugger::internalProperties( ...@@ -661,6 +722,24 @@ v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
return properties; 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( std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
v8::Local<v8::StackTrace> v8StackTrace) { v8::Local<v8::StackTrace> v8StackTrace) {
return V8StackTraceImpl::create(this, currentContextGroupId(), v8StackTrace, return V8StackTraceImpl::create(this, currentContextGroupId(), v8StackTrace,
......
...@@ -88,6 +88,9 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -88,6 +88,9 @@ class V8Debugger : public v8::debug::DebugDelegate {
v8::MaybeLocal<v8::Array> internalProperties(v8::Local<v8::Context>, v8::MaybeLocal<v8::Array> internalProperties(v8::Local<v8::Context>,
v8::Local<v8::Value>); 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, void asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring); bool recurring);
void asyncTaskCanceled(void* task); void asyncTaskCanceled(void* task);
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "src/inspector/v8-inspector-impl.h" #include "src/inspector/v8-inspector-impl.h"
#include "src/inspector/v8-inspector-session-impl.h" #include "src/inspector/v8-inspector-session-impl.h"
#include "src/inspector/v8-stack-trace-impl.h" #include "src/inspector/v8-stack-trace-impl.h"
#include "src/inspector/v8-value-utils.h"
#include "src/tracing/trace-event.h" #include "src/tracing/trace-event.h"
#include "include/v8-inspector.h" #include "include/v8-inspector.h"
...@@ -521,6 +522,21 @@ void V8RuntimeAgentImpl::runScript( ...@@ -521,6 +522,21 @@ void V8RuntimeAgentImpl::runScript(
EvaluateCallbackWrapper<RunScriptCallback>::wrap(std::move(callback))); 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() { void V8RuntimeAgentImpl::restore() {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false)) if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
return; return;
......
...@@ -97,6 +97,9 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend { ...@@ -97,6 +97,9 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue, Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
Maybe<bool> generatePreview, Maybe<bool> awaitPromise, Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
std::unique_ptr<RunScriptCallback>) override; std::unique_ptr<RunScriptCallback>) override;
Response queryObjects(
const String16& constructorObjectId,
std::unique_ptr<protocol::Runtime::RemoteObject>* objects) override;
void reset(); void reset();
void reportExecutionContextCreated(InspectedContext*); void reportExecutionContextCreated(InspectedContext*);
......
...@@ -3541,6 +3541,8 @@ Handle<Context> JSReceiver::GetCreationContext() { ...@@ -3541,6 +3541,8 @@ Handle<Context> JSReceiver::GetCreationContext() {
while (receiver->IsJSBoundFunction()) { while (receiver->IsJSBoundFunction()) {
receiver = JSBoundFunction::cast(receiver)->bound_target_function(); receiver = JSBoundFunction::cast(receiver)->bound_target_function();
} }
// Externals are JSObjects with null as a constructor.
DCHECK(!receiver->IsExternal());
Object* constructor = receiver->map()->GetConstructor(); Object* constructor = receiver->map()->GetConstructor();
JSFunction* function; JSFunction* function;
if (constructor->IsJSFunction()) { if (constructor->IsJSFunction()) {
......
...@@ -12,6 +12,19 @@ ...@@ -12,6 +12,19 @@
#include "src/profiler/sampling-heap-profiler.h" #include "src/profiler/sampling-heap-profiler.h"
namespace v8 { 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 { namespace internal {
HeapProfiler::HeapProfiler(Heap* heap) HeapProfiler::HeapProfiler(Heap* heap)
...@@ -207,6 +220,25 @@ void HeapProfiler::ClearHeapObjectMap() { ...@@ -207,6 +220,25 @@ void HeapProfiler::ClearHeapObjectMap() {
Heap* HeapProfiler::heap() const { return ids_->heap(); } 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 internal
} // namespace v8 } // namespace v8
...@@ -76,6 +76,10 @@ class HeapProfiler { ...@@ -76,6 +76,10 @@ class HeapProfiler {
Isolate* isolate() const { return heap()->isolate(); } Isolate* isolate() const { return heap()->isolate(); }
void QueryObjects(Handle<Context> context,
debug::QueryObjectPredicate* predicate,
v8::PersistentValueVector<v8::Object>* objects);
private: private:
Heap* heap() const; Heap* heap() const;
......
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
]
// 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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment