[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:
Alexey Kozyatinskiy 2017-08-18 17:27:52 -07:00 committed by Commit Bot
parent b99f783065
commit f546ec1a5d
12 changed files with 347 additions and 0 deletions

View File

@ -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();

View File

@ -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

View File

@ -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": [

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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*);

View File

@ -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()) {

View File

@ -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

View File

@ -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;

View 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
]

View 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);
}