Commit 033d3b57 authored by yangguo's avatar yangguo Committed by Commit bot

[debugger] change coverage format to nested, with function name.

R=jgruber@chromium.org, kozyatinskiy@chromium.org
BUG=v8:5808

Review-Url: https://codereview.chromium.org/2694623003
Cr-Commit-Position: refs/heads/master@{#43175}
parent 0d91d379
...@@ -39,30 +39,8 @@ class SharedToCounterMap ...@@ -39,30 +39,8 @@ class SharedToCounterMap
static uint32_t Hash(SharedFunctionInfo* key) { static uint32_t Hash(SharedFunctionInfo* key) {
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(key)); return static_cast<uint32_t>(reinterpret_cast<intptr_t>(key));
} }
};
class ScriptDataBuilder {
public:
void Add(int end_position, uint32_t count) {
DCHECK(entries_.empty() || entries_.back().end_position <= end_position);
if (entries_.empty()) {
if (end_position > 0) entries_.push_back({end_position, count});
} else if (entries_.back().count == count) {
// Extend last range.
entries_.back().end_position = end_position;
} else if (entries_.back().end_position < end_position) {
// Add new range.
entries_.push_back({end_position, count});
}
}
std::vector<Coverage::RangeEntry> Finish() {
std::vector<Coverage::RangeEntry> result;
std::swap(result, entries_);
return result;
}
private: DisallowHeapAllocation no_gc;
std::vector<Coverage::RangeEntry> entries_;
}; };
std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) { std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
...@@ -99,14 +77,7 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) { ...@@ -99,14 +77,7 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// Make sure entries in the counter map is not invalidated by GC. // Make sure entries in the counter map is not invalidated by GC.
DisallowHeapAllocation no_gc; DisallowHeapAllocation no_gc;
// Stack to track nested functions. std::vector<Range*> stack;
struct FunctionNode {
FunctionNode(int s, int e, uint32_t c) : start(s), end(e), count(c) {}
int start;
int end;
uint32_t count;
};
std::vector<FunctionNode> stack;
// Iterate shared function infos of every script and build a mapping // Iterate shared function infos of every script and build a mapping
// between source ranges and invocation counts. // between source ranges and invocation counts.
...@@ -116,11 +87,9 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) { ...@@ -116,11 +87,9 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// Dismiss non-user scripts. // Dismiss non-user scripts.
if (script->type() != Script::TYPE_NORMAL) continue; if (script->type() != Script::TYPE_NORMAL) continue;
DCHECK(stack.empty()); DCHECK(stack.empty());
int script_end = String::cast(script->source())->length(); int source_length = String::cast(script->source())->length();
// If not rooted, the top-level function is likely no longer alive. Set the result.emplace_back(Handle<Script>(script, isolate), source_length);
// outer-most count to 1 to indicate that the script has run at least once. stack.push_back(&result.back().toplevel);
stack.push_back({0, script_end, 1});
ScriptDataBuilder builder;
// Iterate through the list of shared function infos, reconstruct the // Iterate through the list of shared function infos, reconstruct the
// nesting, and compute the ranges covering different invocation counts. // nesting, and compute the ranges covering different invocation counts.
HandleScope scope(isolate); HandleScope scope(isolate);
...@@ -130,30 +99,29 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) { ...@@ -130,30 +99,29 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
if (start == kNoSourcePosition) start = info->start_position(); if (start == kNoSourcePosition) start = info->start_position();
int end = info->end_position(); int end = info->end_position();
uint32_t count = counter_map.Get(info); uint32_t count = counter_map.Get(info);
// The shared function infos are sorted by start. if (info->is_toplevel()) {
DCHECK_LE(stack.back().start, start); // Top-level function is available.
// If the start are the same, the outer function comes before the inner. DCHECK_EQ(1, stack.size());
DCHECK(stack.back().start < start || stack.back().end >= end); result.back().toplevel.start = start;
// Drop the stack to the outer function. result.back().toplevel.end = end;
while (start > stack.back().end) { result.back().toplevel.count = count;
// Write out rest of function being dropped. } else {
builder.Add(stack.back().end, stack.back().count); // The shared function infos are sorted by start.
stack.pop_back(); DCHECK_LE(stack.back()->start, start);
// Drop the stack to the outer function.
while (start > stack.back()->end) stack.pop_back();
Range* outer = stack.back();
// New nested function.
DCHECK_LE(end, outer->end);
outer->inner.emplace_back(start, end, count);
Range& nested = outer->inner.back();
String* name = info->DebugName();
nested.name.resize(name->length());
String::WriteToFlat(name, nested.name.data(), 0, name->length());
stack.push_back(&nested);
} }
// Write out outer function up to the start of new function.
builder.Add(start, stack.back().count);
// New nested function.
DCHECK_LE(end, stack.back().end);
stack.emplace_back(start, end, count);
}
// Drop the stack to the script level.
while (!stack.empty()) {
// Write out rest of function being dropped.
builder.Add(stack.back().end, stack.back().count);
stack.pop_back();
} }
result.emplace_back(script->id(), builder.Finish()); stack.clear();
} }
return result; return result;
} }
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include <vector> #include <vector>
#include "src/allocation.h" #include "src/allocation.h"
#include "src/debug/debug-interface.h"
#include "src/objects.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -17,16 +19,21 @@ class Isolate; ...@@ -17,16 +19,21 @@ class Isolate;
class Coverage : public AllStatic { class Coverage : public AllStatic {
public: public:
struct RangeEntry { struct Range {
int end_position; Range(int s, int e, uint32_t c) : start(s), end(e), count(c) {}
int start;
int end;
uint32_t count; uint32_t count;
std::vector<uint16_t> name;
std::vector<Range> inner;
}; };
struct ScriptData { struct ScriptData {
ScriptData(int s, std::vector<RangeEntry> e) // Initialize top-level function in case it has been garbage-collected.
: script_id(s), entries(std::move(e)) {} ScriptData(Handle<Script> s, int source_length)
int script_id; : script(s), toplevel(0, source_length, 1) {}
std::vector<RangeEntry> entries; Handle<Script> script;
Range toplevel;
}; };
static std::vector<ScriptData> Collect(Isolate* isolate); static std::vector<ScriptData> Collect(Isolate* isolate);
......
...@@ -1894,43 +1894,73 @@ RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) { ...@@ -1894,43 +1894,73 @@ RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
return NULL; return NULL;
} }
namespace {
Handle<JSObject> CreateRangeObject(Isolate* isolate,
const Coverage::Range* range,
Handle<String> inner_string,
Handle<String> start_string,
Handle<String> end_string,
Handle<String> count_string) {
HandleScope scope(isolate);
Factory* factory = isolate->factory();
Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
JSObject::AddProperty(range_obj, start_string,
factory->NewNumberFromInt(range->start), NONE);
JSObject::AddProperty(range_obj, end_string,
factory->NewNumberFromInt(range->end), NONE);
JSObject::AddProperty(range_obj, count_string,
factory->NewNumberFromUint(range->count), NONE);
Handle<String> name = factory->anonymous_string();
if (!range->name.empty()) {
Vector<const uc16> name_vector(range->name.data(),
static_cast<int>(range->name.size()));
name = factory->NewStringFromTwoByte(name_vector).ToHandleChecked();
}
JSObject::AddProperty(range_obj, factory->name_string(), name, NONE);
if (!range->inner.empty()) {
int size = static_cast<int>(range->inner.size());
Handle<FixedArray> inner_array = factory->NewFixedArray(size);
for (int i = 0; i < size; i++) {
Handle<JSObject> element =
CreateRangeObject(isolate, &range->inner[i], inner_string,
start_string, end_string, count_string);
inner_array->set(i, *element);
}
Handle<JSArray> inner =
factory->NewJSArrayWithElements(inner_array, FAST_ELEMENTS);
JSObject::AddProperty(range_obj, inner_string, inner, NONE);
}
return scope.CloseAndEscape(range_obj);
}
} // anonymous namespace
RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) { RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
HandleScope scope(isolate); HandleScope scope(isolate);
// Collect coverage data. // Collect coverage data.
std::vector<Coverage::ScriptData> scripts = Coverage::Collect(isolate); std::vector<Coverage::ScriptData> script_data = Coverage::Collect(isolate);
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
// Turn the returned data structure into JavaScript. // Turn the returned data structure into JavaScript.
// Create an array of scripts. // Create an array of scripts.
int num_scripts = static_cast<int>(scripts.size()); int num_scripts = static_cast<int>(script_data.size());
// Prepare property keys. // Prepare property keys.
Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts); Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
Handle<String> id_string = factory->NewStringFromStaticChars("script_id"); Handle<String> script_string = factory->NewStringFromStaticChars("script");
Handle<String> entries_string = factory->NewStringFromStaticChars("entries"); Handle<String> toplevel_string =
Handle<String> end_string = factory->NewStringFromStaticChars("end_position"); factory->NewStringFromStaticChars("toplevel");
Handle<String> inner_string = factory->NewStringFromStaticChars("inner");
Handle<String> start_string = factory->NewStringFromStaticChars("start");
Handle<String> end_string = factory->NewStringFromStaticChars("end");
Handle<String> count_string = factory->NewStringFromStaticChars("count"); Handle<String> count_string = factory->NewStringFromStaticChars("count");
for (int i = 0; i < num_scripts; i++) { for (int i = 0; i < num_scripts; i++) {
// Create an object for each script, containing the script id and entries. const auto& data = script_data[i];
const auto& script = scripts[i];
HandleScope inner_scope(isolate); HandleScope inner_scope(isolate);
int num_entries = static_cast<int>(script.entries.size());
Handle<FixedArray> entries_array = factory->NewFixedArray(num_entries);
for (int j = 0; j < num_entries; j++) {
// Create an object for each entry, containing the end position and count.
const auto& entry = script.entries[j];
Handle<JSObject> entry_obj = factory->NewJSObjectWithNullProto();
JSObject::AddProperty(entry_obj, end_string,
factory->NewNumberFromInt(entry.end_position),
NONE);
JSObject::AddProperty(entry_obj, count_string,
factory->NewNumberFromUint(entry.count), NONE);
entries_array->set(j, *entry_obj);
}
Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto(); Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto();
JSObject::AddProperty(script_obj, id_string, Handle<JSObject> wrapper = Script::GetWrapper(data.script);
factory->NewNumberFromInt(script.script_id), NONE); JSObject::AddProperty(script_obj, script_string, wrapper, NONE);
JSObject::AddProperty( Handle<JSObject> toplevel =
script_obj, entries_string, CreateRangeObject(isolate, &data.toplevel, inner_string, start_string,
factory->NewJSArrayWithElements(entries_array, FAST_ELEMENTS), NONE); end_string, count_string);
JSObject::AddProperty(script_obj, toplevel_string, toplevel, NONE);
scripts_array->set(i, *script_obj); scripts_array->set(i, *script_obj);
} }
return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS); return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS);
......
...@@ -7,27 +7,22 @@ ...@@ -7,27 +7,22 @@
// Test code coverage without explicitly activating it upfront. // Test code coverage without explicitly activating it upfront.
function GetCoverage(source) { function GetCoverage(source) {
var scripts = %DebugGetLoadedScripts(); for (var script of %DebugCollectCoverage()) {
for (var script of scripts) { if (script.script.source == source) return script.toplevel;
if (script.source == source) {
var coverage = %DebugCollectCoverage();
for (var data of coverage) {
if (data.script_id == script.id) return data.entries;
}
}
} }
return undefined; return undefined;
} }
function ApplyCoverageToSource(source, coverage) { function ApplyCoverageToSource(source, range) {
var result = ""; var content = "";
var cursor = 0; var cursor = range.start;
for (var entry of coverage) { if (range.inner) for (var inner of range.inner) {
var chunk = source.substring(cursor, entry.end_position); content += source.substring(cursor, inner.start);
cursor = entry.end_position; content += ApplyCoverageToSource(source, inner);
result += `[${chunk}[${entry.count}]]`; cursor = inner.end;
} }
return result; content += source.substring(cursor, range.end);
return `[${content}](${range.name}:${range.count})`;
} }
function TestCoverage(name, source, expectation) { function TestCoverage(name, source, expectation) {
...@@ -48,9 +43,9 @@ f(); ...@@ -48,9 +43,9 @@ f();
f(); f();
`, `,
` `
[function f() {}[2]][ [[function f() {}](f:2)
f(); f();
f();[1]] f();](anonymous:1)
` `
); );
...@@ -62,9 +57,9 @@ f(); ...@@ -62,9 +57,9 @@ f();
f(); f();
`, `,
` `
[var f = [1]][() => 1[2]][; [var f = [() => 1](f:2);
f(); f();
f();[1]] f();](anonymous:1)
` `
); );
...@@ -80,13 +75,14 @@ f(); ...@@ -80,13 +75,14 @@ f();
f(); f();
`, `,
` `
[function f() { [[function f() {
[2]][function g() {}[4]][ [function g() {}](g:4)
g(); g();
g(); g();
}[2]][ }](f:2)
f(); f();
f();[1]] f();](anonymous:1)
` `
); );
...@@ -100,10 +96,10 @@ function fib(x) { ...@@ -100,10 +96,10 @@ function fib(x) {
fib(5); fib(5);
`, `,
` `
[function fib(x) { [[function fib(x) {
if (x < 2) return 1; if (x < 2) return 1;
return fib(x-1) + fib(x-2); return fib(x-1) + fib(x-2);
}[15]][ }](fib:15)
fib(5);[1]] fib(5);](anonymous:1)
` `
); );
...@@ -7,32 +7,28 @@ ...@@ -7,32 +7,28 @@
// Test precise code coverage. // Test precise code coverage.
function GetCoverage(source) { function GetCoverage(source) {
var scripts = %DebugGetLoadedScripts(); for (var script of %DebugCollectCoverage()) {
for (var script of scripts) { if (script.script.source == source) return script.toplevel;
if (script.source == source) {
var coverage = %DebugCollectCoverage();
for (var data of coverage) {
if (data.script_id == script.id) return data.entries;
}
}
} }
return undefined; return undefined;
} }
function ApplyCoverageToSource(source, coverage) { function ApplyCoverageToSource(source, range) {
var result = ""; var content = "";
var cursor = 0; var cursor = range.start;
for (var entry of coverage) { if (range.inner) for (var inner of range.inner) {
var chunk = source.substring(cursor, entry.end_position); content += source.substring(cursor, inner.start);
cursor = entry.end_position; content += ApplyCoverageToSource(source, inner);
result += `[${chunk}[${entry.count}]]`; cursor = inner.end;
} }
return result; content += source.substring(cursor, range.end);
return `[${content}](${range.name}:${range.count})`;
} }
function TestCoverage(name, source, expectation) { function TestCoverage(name, source, expectation) {
source = source.trim(); source = source.trim();
eval(source); eval(source);
%CollectGarbage("remove dead objects");
var coverage = GetCoverage(source); var coverage = GetCoverage(source);
if (expectation === undefined) { if (expectation === undefined) {
assertEquals(undefined, coverage); assertEquals(undefined, coverage);
...@@ -74,7 +70,7 @@ TestCoverage( ...@@ -74,7 +70,7 @@ TestCoverage(
(function f() {})(); (function f() {})();
`, `,
` `
[(function f() {})();[1]] [([function f() {}](f:1))();](anonymous:1)
` `
); );
...@@ -88,9 +84,9 @@ for (var i = 0; i < 10; i++) { ...@@ -88,9 +84,9 @@ for (var i = 0; i < 10; i++) {
`, `,
` `
[for (var i = 0; i < 10; i++) { [for (var i = 0; i < 10; i++) {
let f = [1]][() => 1[5]][; let f = [() => 1](f:5);
i += f(); i += f();
}[1]] }](anonymous:1)
` `
); );
......
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