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