Commit 901c29eb authored by yangguo's avatar yangguo Committed by Commit bot

[inspector] extend protocol for code coverage.

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

Review-Url: https://codereview.chromium.org/2700743002
Cr-Commit-Position: refs/heads/master@{#43363}
parent e2de1b86
...@@ -9541,46 +9541,50 @@ Local<String> CpuProfileNode::GetFunctionName() const { ...@@ -9541,46 +9541,50 @@ Local<String> CpuProfileNode::GetFunctionName() const {
} }
} }
debug::Coverage::Range::Range(i::CoverageRange* range, debug::Coverage::FunctionData::FunctionData(i::CoverageFunction* function,
Local<debug::Script> script) Local<debug::Script> script)
: range_(range), script_(script) { : function_(function) {
i::Handle<i::Script> i_script = v8::Utils::OpenHandle(*script); i::Handle<i::Script> i_script = v8::Utils::OpenHandle(*script);
i::Script::PositionInfo start; i::Script::PositionInfo start;
i::Script::PositionInfo end; i::Script::PositionInfo end;
i::Script::GetPositionInfo(i_script, range->start, &start, i::Script::GetPositionInfo(i_script, function->start, &start,
i::Script::WITH_OFFSET); i::Script::WITH_OFFSET);
i::Script::GetPositionInfo(i_script, range->end, &end, i::Script::GetPositionInfo(i_script, function->end, &end,
i::Script::WITH_OFFSET); i::Script::WITH_OFFSET);
start_ = Location(start.line, start.column); start_ = Location(start.line, start.column);
end_ = Location(end.line, end.column); end_ = Location(end.line, end.column);
} }
uint32_t debug::Coverage::Range::Count() { return range_->count; } uint32_t debug::Coverage::FunctionData::Count() { return function_->count; }
size_t debug::Coverage::Range::NestedCount() { return range_->inner.size(); } MaybeLocal<String> debug::Coverage::FunctionData::Name() {
return ToApiHandle<String>(function_->name);
}
Local<debug::Script> debug::Coverage::ScriptData::GetScript() {
return ToApiHandle<debug::Script>(script_->script);
}
debug::Coverage::Range debug::Coverage::Range::GetNested(size_t i) { size_t debug::Coverage::ScriptData::FunctionCount() {
return Range(&range_->inner[i], script_); return script_->functions.size();
} }
MaybeLocal<String> debug::Coverage::Range::Name() { debug::Coverage::FunctionData debug::Coverage::ScriptData::GetFunctionData(
return ToApiHandle<String>(range_->name); size_t i) {
return FunctionData(&script_->functions.at(i), GetScript());
} }
debug::Coverage::~Coverage() { delete coverage_; } debug::Coverage::~Coverage() { delete coverage_; }
size_t debug::Coverage::ScriptCount() { return coverage_->size(); } size_t debug::Coverage::ScriptCount() { return coverage_->size(); }
Local<debug::Script> debug::Coverage::GetScript(size_t i) { debug::Coverage::ScriptData debug::Coverage::GetScriptData(size_t i) {
return ToApiHandle<debug::Script>(coverage_->at(i).script); return ScriptData(&coverage_->at(i));
}
debug::Coverage::Range debug::Coverage::GetRange(size_t i) {
return Range(&coverage_->at(i).toplevel, GetScript(i));
} }
debug::Coverage debug::Coverage::Collect(Isolate* isolate) { debug::Coverage debug::Coverage::Collect(Isolate* isolate, bool reset_count) {
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate))); return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate),
reset_count));
} }
void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) { void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) {
......
...@@ -1657,51 +1657,15 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) { ...@@ -1657,51 +1657,15 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
JSON::Stringify(context, dispatch_counters).ToLocalChecked()); JSON::Stringify(context, dispatch_counters).ToLocalChecked());
} }
namespace {
void ReadRange(std::ofstream* s, std::vector<uint32_t>* lines,
debug::Coverage::Range range) {
// Ensure space in the array.
lines->resize(std::max(static_cast<size_t>(range.End().GetLineNumber() + 1),
lines->size()),
0);
// Boundary lines could be shared between two functions with different
// invocation counts. Take the maximum.
lines->at(range.Start().GetLineNumber()) =
std::max(lines->at(range.Start().GetLineNumber()), range.Count());
lines->at(range.End().GetLineNumber()) =
std::max(lines->at(range.End().GetLineNumber()), range.Count());
// Invocation counts for non-boundary lines are overwritten.
int line_plus_one = range.Start().GetLineNumber() + 1;
for (int i = line_plus_one; i < range.End().GetLineNumber(); i++) {
lines->at(i) = range.Count();
}
// Note that we use 0-based line numbers. But LCOV uses 1-based line numbers.
// Recurse over inner ranges.
for (size_t i = 0; i < range.NestedCount(); i++) {
ReadRange(s, lines, range.GetNested(i));
}
// Write function stats.
Local<String> name;
std::stringstream name_stream;
if (range.Name().ToLocal(&name)) {
name_stream << ToSTLString(name);
} else {
name_stream << "<" << line_plus_one << "-";
name_stream << range.Start().GetColumnNumber() << ">";
}
*s << "FN:" << line_plus_one << "," << name_stream.str() << std::endl;
*s << "FNDA:" << range.Count() << "," << name_stream.str() << std::endl;
}
} // anonymous namespace
// Write coverage data in LCOV format. See man page for geninfo(1). // Write coverage data in LCOV format. See man page for geninfo(1).
void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) { void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
if (!file) return; if (!file) return;
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
debug::Coverage coverage = debug::Coverage::Collect(isolate); debug::Coverage coverage = debug::Coverage::Collect(isolate, false);
std::ofstream sink(file, std::ofstream::app); std::ofstream sink(file, std::ofstream::app);
for (size_t i = 0; i < coverage.ScriptCount(); i++) { for (size_t i = 0; i < coverage.ScriptCount(); i++) {
Local<debug::Script> script = coverage.GetScript(i); debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
Local<debug::Script> script = script_data.GetScript();
// Skip unnamed scripts. // Skip unnamed scripts.
Local<String> name; Local<String> name;
if (!script->Name().ToLocal(&name)) continue; if (!script->Name().ToLocal(&name)) continue;
...@@ -1711,7 +1675,33 @@ void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) { ...@@ -1711,7 +1675,33 @@ void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
sink << "SF:"; sink << "SF:";
sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl; sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
std::vector<uint32_t> lines; std::vector<uint32_t> lines;
ReadRange(&sink, &lines, coverage.GetRange(i)); for (size_t j = 0; j < script_data.FunctionCount(); j++) {
debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(j);
int start_line = function_data.Start().GetLineNumber();
int end_line = function_data.End().GetLineNumber();
uint32_t count = function_data.Count();
// Ensure space in the array.
lines.resize(std::max(static_cast<size_t>(end_line + 1), lines.size()),
0);
// Boundary lines could be shared between two functions with different
// invocation counts. Take the maximum.
lines[start_line] = std::max(lines[start_line], count);
lines[end_line] = std::max(lines[end_line], count);
// Invocation counts for non-boundary lines are overwritten.
for (int k = start_line + 1; k < end_line; k++) lines[k] = count;
// Write function stats.
Local<String> name;
std::stringstream name_stream;
if (function_data.Name().ToLocal(&name)) {
name_stream << ToSTLString(name);
} else {
name_stream << "<" << start_line + 1 << "-";
name_stream << function_data.Start().GetColumnNumber() << ">";
}
sink << "FN:" << start_line + 1 << "," << name_stream.str() << std::endl;
sink << "FNDA:" << count << "," << name_stream.str() << std::endl;
}
// Write per-line coverage. LCOV uses 1-based line numbers. // Write per-line coverage. LCOV uses 1-based line numbers.
for (size_t i = 0; i < lines.size(); i++) { for (size_t i = 0; i < lines.size(); i++) {
sink << "DA:" << (i + 1) << "," << lines[i] << std::endl; sink << "DA:" << (i + 1) << "," << lines[i] << std::endl;
......
...@@ -58,12 +58,7 @@ bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) { ...@@ -58,12 +58,7 @@ bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
} }
} // anonymous namespace } // anonymous namespace
CoverageScript::CoverageScript(Isolate* isolate, Handle<Script> s, Coverage* Coverage::Collect(Isolate* isolate, bool reset_count) {
int source_length)
: script(s),
toplevel(0, source_length, 1, isolate->factory()->empty_string()) {}
Coverage* Coverage::Collect(Isolate* isolate) {
SharedToCounterMap counter_map; SharedToCounterMap counter_map;
// Feed invocation count into the counter map. // Feed invocation count into the counter map.
...@@ -76,6 +71,7 @@ Coverage* Coverage::Collect(Isolate* isolate) { ...@@ -76,6 +71,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
SharedFunctionInfo* shared = vector->shared_function_info(); SharedFunctionInfo* shared = vector->shared_function_info();
DCHECK(shared->IsSubjectToDebugging()); DCHECK(shared->IsSubjectToDebugging());
uint32_t count = static_cast<uint32_t>(vector->invocation_count()); uint32_t count = static_cast<uint32_t>(vector->invocation_count());
if (reset_count) vector->clear_invocation_count();
counter_map.Add(shared, count); counter_map.Add(shared, count);
} }
} else { } else {
...@@ -88,6 +84,7 @@ Coverage* Coverage::Collect(Isolate* isolate) { ...@@ -88,6 +84,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
SharedFunctionInfo* shared = vector->shared_function_info(); SharedFunctionInfo* shared = vector->shared_function_info();
if (!shared->IsSubjectToDebugging()) continue; if (!shared->IsSubjectToDebugging()) continue;
uint32_t count = static_cast<uint32_t>(vector->invocation_count()); uint32_t count = static_cast<uint32_t>(vector->invocation_count());
if (reset_count) vector->clear_invocation_count();
counter_map.Add(shared, count); counter_map.Add(shared, count);
} }
} }
...@@ -101,47 +98,39 @@ Coverage* Coverage::Collect(Isolate* isolate) { ...@@ -101,47 +98,39 @@ Coverage* Coverage::Collect(Isolate* isolate) {
if (script->type() != Script::TYPE_NORMAL) continue; if (script->type() != Script::TYPE_NORMAL) continue;
// Create and add new script data. // Create and add new script data.
int source_end = String::cast(script->source())->length();
Handle<Script> script_handle(script, isolate); Handle<Script> script_handle(script, isolate);
result->emplace_back(isolate, script_handle, source_end); result->emplace_back(isolate, script_handle);
std::vector<CoverageFunction>* functions = &result->back().functions;
std::vector<SharedFunctionInfo*> sorted; std::vector<SharedFunctionInfo*> sorted;
bool has_toplevel = false;
{ {
// Collect a list of shared function infos sorted by start position. // Sort functions by start position, from outer to inner functions.
// Shared function infos are usually already sorted. Except for classes.
// If the start position is the same, sort from outer to inner function.
SharedFunctionInfo::ScriptIterator infos(script_handle); SharedFunctionInfo::ScriptIterator infos(script_handle);
while (SharedFunctionInfo* info = infos.Next()) sorted.push_back(info); while (SharedFunctionInfo* info = infos.Next()) {
has_toplevel |= info->is_toplevel();
sorted.push_back(info);
}
std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo); std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo);
} }
std::vector<CoverageRange*> stack; functions->reserve(sorted.size() + (has_toplevel ? 0 : 1));
stack.push_back(&result->back().toplevel);
if (!has_toplevel) {
// Add a replacement toplevel function if it does not exist.
int source_end = String::cast(script->source())->length();
functions->emplace_back(0, source_end, 1u,
isolate->factory()->empty_string());
}
// Use sorted list to reconstruct function nesting. // Use sorted list to reconstruct function nesting.
for (SharedFunctionInfo* info : sorted) { for (SharedFunctionInfo* info : sorted) {
int start = StartPosition(info); int start = StartPosition(info);
int end = info->end_position(); int end = info->end_position();
uint32_t count = counter_map.Get(info); uint32_t count = counter_map.Get(info);
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();
CoverageRange* outer = stack.back();
// New nested function.
DCHECK_LE(end, outer->end);
Handle<String> name(info->DebugName(), isolate); Handle<String> name(info->DebugName(), isolate);
outer->inner.emplace_back(start, end, count, name); functions->emplace_back(start, end, count, name);
stack.push_back(&outer->inner.back());
}
} }
} }
return result; return result;
......
...@@ -16,28 +16,28 @@ namespace internal { ...@@ -16,28 +16,28 @@ namespace internal {
// Forward declaration. // Forward declaration.
class Isolate; class Isolate;
struct CoverageRange { struct CoverageFunction {
CoverageRange(int s, int e, uint32_t c, Handle<String> n) CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
: start(s), end(e), count(c), name(n) {} : start(s), end(e), count(c), name(n) {}
int start; int start;
int end; int end;
uint32_t count; uint32_t count;
Handle<String> name; Handle<String> name;
std::vector<CoverageRange> inner;
}; };
struct CoverageScript { struct CoverageScript {
// Initialize top-level function in case it has been garbage-collected. // Initialize top-level function in case it has been garbage-collected.
CoverageScript(Isolate* isolate, Handle<Script> s, int source_length); CoverageScript(Isolate* isolate, Handle<Script> s) : script(s) {}
Handle<Script> script; Handle<Script> script;
CoverageRange toplevel; // Functions are sorted by start position, from outer to inner function.
std::vector<CoverageFunction> functions;
}; };
class Coverage : public std::vector<CoverageScript> { class Coverage : public std::vector<CoverageScript> {
public: public:
// Allocate a new Coverage object and populate with result. // Allocate a new Coverage object and populate with result.
// The ownership is transferred to the caller. // The ownership is transferred to the caller.
static Coverage* Collect(Isolate* isolate); static Coverage* Collect(Isolate* isolate, bool reset_count);
// Enable precise code coverage. This disables optimization and makes sure // Enable precise code coverage. This disables optimization and makes sure
// invocation count is not affected by GC. // invocation count is not affected by GC.
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
struct CoverageRange; struct CoverageFunction;
struct CoverageScript;
class Coverage; class Coverage;
class Script; class Script;
} }
...@@ -211,33 +212,45 @@ class GeneratorObject { ...@@ -211,33 +212,45 @@ class GeneratorObject {
*/ */
class V8_EXPORT_PRIVATE Coverage { class V8_EXPORT_PRIVATE Coverage {
public: public:
class V8_EXPORT_PRIVATE Range { class ScriptData; // Forward declaration.
class V8_EXPORT_PRIVATE FunctionData {
public: public:
// 0-based line and colum numbers. // 0-based line and colum numbers.
Location Start() { return start_; } Location Start() { return start_; }
Location End() { return end_; } Location End() { return end_; }
uint32_t Count(); uint32_t Count();
size_t NestedCount();
Range GetNested(size_t i);
MaybeLocal<String> Name(); MaybeLocal<String> Name();
private: private:
Range(i::CoverageRange* range, Local<debug::Script> script); FunctionData(i::CoverageFunction* function, Local<debug::Script> script);
i::CoverageRange* range_; i::CoverageFunction* function_;
Location start_; Location start_;
Location end_; Location end_;
Local<debug::Script> script_;
friend class debug::Coverage; friend class v8::debug::Coverage::ScriptData;
};
class V8_EXPORT_PRIVATE ScriptData {
public:
Local<debug::Script> GetScript();
size_t FunctionCount();
FunctionData GetFunctionData(size_t i);
private:
explicit ScriptData(i::CoverageScript* script) : script_(script) {}
i::CoverageScript* script_;
friend class v8::debug::Coverage;
}; };
static Coverage Collect(Isolate* isolate); static Coverage Collect(Isolate* isolate, bool reset_count);
static void TogglePrecise(Isolate* isolate, bool enable); static void TogglePrecise(Isolate* isolate, bool enable);
size_t ScriptCount(); size_t ScriptCount();
Local<debug::Script> GetScript(size_t i); ScriptData GetScriptData(size_t i);
Range GetRange(size_t i); bool IsEmpty() { return coverage_ == nullptr; }
~Coverage(); ~Coverage();
......
...@@ -204,6 +204,38 @@ ...@@ -204,6 +204,38 @@
{ "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." }, { "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." },
{ "name": "promiseCreationFrame", "$ref": "CallFrame", "optional": true, "experimental": true, "description": "Creation frame of the Promise which produced the next synchronous trace when resolved, if available." } { "name": "promiseCreationFrame", "$ref": "CallFrame", "optional": true, "experimental": true, "description": "Creation frame of the Promise which produced the next synchronous trace when resolved, if available." }
] ]
},
{ "id": "CoverageRange",
"type": "object",
"description": "Coverage data for a source range.",
"properties": [
{ "name": "startLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range start." },
{ "name": "startColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range start." },
{ "name": "endLineNumber", "type": "integer", "description": "JavaScript script line number (0-based) for the range end." },
{ "name": "endColumnNumber", "type": "integer", "description": "JavaScript script column number (0-based) for the range end." },
{ "name": "count", "type": "integer", "description": "Collected execution count of the source range." }
],
"experimental": "true"
},
{ "id": "FunctionCoverage",
"type": "object",
"description": "Coverage data for a JavaScript function.",
"properties": [
{ "name": "functionName", "type": "string", "description": "JavaScript function name." },
{ "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." }
],
"experimental": "true"
},
{
"id": "ScriptCoverage",
"type": "object",
"description": "Coverage data for a JavaScript script.",
"properties": [
{ "name": "scriptId", "$ref": "ScriptId", "description": "JavaScript script id." },
{ "name": "url", "type": "string", "description": "JavaScript script name or url." },
{ "name": "functions", "type": "array", "items": { "$ref": "FunctionCoverage" }, "description": "Functions contained in the script that has coverage data." }
],
"experimental": "true"
} }
], ],
"commands": [ "commands": [
...@@ -343,6 +375,32 @@ ...@@ -343,6 +375,32 @@
{ "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": "startPreciseCoverage",
"description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters.",
"experimental": true
},
{
"name": "stopPreciseCoverage",
"description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code.",
"experimental": true
},
{
"name": "takePreciseCoverage",
"returns": [
{ "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
],
"description": "Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started.",
"experimental": true
},
{
"name": "getBestEffortCoverage",
"returns": [
{ "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." }
],
"description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection.",
"experimental": true
} }
], ],
"events": [ "events": [
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "src/inspector/v8-runtime-agent-impl.h" #include "src/inspector/v8-runtime-agent-impl.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h" #include "src/inspector/injected-script.h"
#include "src/inspector/inspected-context.h" #include "src/inspector/inspected-context.h"
#include "src/inspector/protocol/Protocol.h" #include "src/inspector/protocol/Protocol.h"
...@@ -51,6 +52,7 @@ namespace V8RuntimeAgentImplState { ...@@ -51,6 +52,7 @@ namespace V8RuntimeAgentImplState {
static const char customObjectFormatterEnabled[] = static const char customObjectFormatterEnabled[] =
"customObjectFormatterEnabled"; "customObjectFormatterEnabled";
static const char runtimeEnabled[] = "runtimeEnabled"; static const char runtimeEnabled[] = "runtimeEnabled";
static const char preciseCoverageStarted[] = "preciseCoverageStarted";
}; };
using protocol::Runtime::RemoteObject; using protocol::Runtime::RemoteObject;
...@@ -645,6 +647,88 @@ void V8RuntimeAgentImpl::runScript( ...@@ -645,6 +647,88 @@ void V8RuntimeAgentImpl::runScript(
std::move(callback)); std::move(callback));
} }
Response V8RuntimeAgentImpl::startPreciseCoverage() {
m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, true);
v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), true);
return Response::OK();
}
Response V8RuntimeAgentImpl::stopPreciseCoverage() {
m_state->setBoolean(V8RuntimeAgentImplState::preciseCoverageStarted, false);
v8::debug::Coverage::TogglePrecise(m_inspector->isolate(), false);
return Response::OK();
}
namespace {
Response takeCoverage(
v8::Isolate* isolate, bool reset_count,
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
out_result) {
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>> result =
protocol::Array<protocol::Runtime::ScriptCoverage>::create();
v8::HandleScope handle_scope(isolate);
v8::debug::Coverage coverage =
v8::debug::Coverage::Collect(isolate, reset_count);
for (size_t i = 0; i < coverage.ScriptCount(); i++) {
v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
v8::Local<v8::debug::Script> script = script_data.GetScript();
std::unique_ptr<protocol::Array<protocol::Runtime::FunctionCoverage>>
functions =
protocol::Array<protocol::Runtime::FunctionCoverage>::create();
for (size_t j = 0; j < script_data.FunctionCount(); j++) {
v8::debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(j);
std::unique_ptr<protocol::Array<protocol::Runtime::CoverageRange>>
ranges = protocol::Array<protocol::Runtime::CoverageRange>::create();
// At this point we only have per-function coverage data, so there is
// only one range per function.
ranges->addItem(
protocol::Runtime::CoverageRange::create()
.setStartLineNumber(function_data.Start().GetLineNumber())
.setStartColumnNumber(function_data.Start().GetColumnNumber())
.setEndLineNumber(function_data.End().GetLineNumber())
.setEndColumnNumber(function_data.End().GetColumnNumber())
.setCount(function_data.Count())
.build());
functions->addItem(
protocol::Runtime::FunctionCoverage::create()
.setFunctionName(toProtocolString(
function_data.Name().FromMaybe(v8::Local<v8::String>())))
.setRanges(std::move(ranges))
.build());
}
String16 url;
v8::Local<v8::String> name;
if (script->Name().ToLocal(&name) || script->SourceURL().ToLocal(&name)) {
url = toProtocolString(name);
}
result->addItem(protocol::Runtime::ScriptCoverage::create()
.setScriptId(String16::fromInteger(script->Id()))
.setUrl(url)
.setFunctions(std::move(functions))
.build());
}
*out_result = std::move(result);
return Response::OK();
}
} // anonymous namespace
Response V8RuntimeAgentImpl::takePreciseCoverage(
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
out_result) {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
false)) {
return Response::Error("Precise coverage has not been started.");
}
return takeCoverage(m_inspector->isolate(), true, out_result);
}
Response V8RuntimeAgentImpl::getBestEffortCoverage(
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
out_result) {
return takeCoverage(m_inspector->isolate(), false, out_result);
}
void V8RuntimeAgentImpl::restore() { void V8RuntimeAgentImpl::restore() {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false)) if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
return; return;
...@@ -653,6 +737,9 @@ void V8RuntimeAgentImpl::restore() { ...@@ -653,6 +737,9 @@ void V8RuntimeAgentImpl::restore() {
if (m_state->booleanProperty( if (m_state->booleanProperty(
V8RuntimeAgentImplState::customObjectFormatterEnabled, false)) V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
m_session->setCustomObjectFormatterEnabled(true); m_session->setCustomObjectFormatterEnabled(true);
if (m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
false))
startPreciseCoverage();
} }
Response V8RuntimeAgentImpl::enable() { Response V8RuntimeAgentImpl::enable() {
...@@ -680,6 +767,7 @@ Response V8RuntimeAgentImpl::disable() { ...@@ -680,6 +767,7 @@ Response V8RuntimeAgentImpl::disable() {
reset(); reset();
m_inspector->client()->endEnsureAllContextsInGroup( m_inspector->client()->endEnsureAllContextsInGroup(
m_session->contextGroupId()); m_session->contextGroupId());
stopPreciseCoverage();
return Response::OK(); return Response::OK();
} }
......
...@@ -97,6 +97,14 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend { ...@@ -97,6 +97,14 @@ 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 startPreciseCoverage() override;
Response stopPreciseCoverage() override;
Response takePreciseCoverage(
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
out_result) override;
Response getBestEffortCoverage(
std::unique_ptr<protocol::Array<protocol::Runtime::ScriptCoverage>>*
out_result) override;
void reset(); void reset();
void reportExecutionContextCreated(InspectedContext*); void reportExecutionContextCreated(InspectedContext*);
......
...@@ -1895,49 +1895,16 @@ RUNTIME_FUNCTION(Runtime_DebugIsActive) { ...@@ -1895,49 +1895,16 @@ RUNTIME_FUNCTION(Runtime_DebugIsActive) {
return Smi::FromInt(isolate->debug()->is_active()); return Smi::FromInt(isolate->debug()->is_active());
} }
RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) { RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
UNIMPLEMENTED(); UNIMPLEMENTED();
return NULL; return NULL;
} }
namespace {
Handle<JSObject> CreateRangeObject(Isolate* isolate, const CoverageRange* 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);
JSObject::AddProperty(range_obj, factory->name_string(), range->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);
DCHECK_EQ(0, args.length());
// Collect coverage data. // Collect coverage data.
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate)); std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate, false));
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.
...@@ -1945,22 +1912,31 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) { ...@@ -1945,22 +1912,31 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
// Prepare property keys. // Prepare property keys.
Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts); Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
Handle<String> script_string = factory->NewStringFromStaticChars("script"); 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> start_string = factory->NewStringFromStaticChars("start");
Handle<String> end_string = factory->NewStringFromStaticChars("end"); 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++) {
const auto& data = coverage->at(i); const auto& script_data = coverage->at(i);
HandleScope inner_scope(isolate); HandleScope inner_scope(isolate);
Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto(); int num_functions = static_cast<int>(script_data.functions.size());
Handle<JSObject> wrapper = Script::GetWrapper(data.script); Handle<FixedArray> functions_array = factory->NewFixedArray(num_functions);
for (int j = 0; j < num_functions; j++) {
const auto& function_data = script_data.functions[j];
Handle<JSObject> range_obj = factory->NewJSObjectWithNullProto();
JSObject::AddProperty(range_obj, start_string,
factory->NewNumberFromInt(function_data.start),
NONE);
JSObject::AddProperty(range_obj, end_string,
factory->NewNumberFromInt(function_data.end), NONE);
JSObject::AddProperty(range_obj, count_string,
factory->NewNumberFromUint(function_data.count),
NONE);
functions_array->set(j, *range_obj);
}
Handle<JSArray> script_obj =
factory->NewJSArrayWithElements(functions_array, FAST_ELEMENTS);
Handle<JSObject> wrapper = Script::GetWrapper(script_data.script);
JSObject::AddProperty(script_obj, script_string, wrapper, NONE); 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); scripts_array->set(i, *script_obj);
} }
return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS); return *factory->NewJSArrayWithElements(scripts_array, FAST_ELEMENTS);
......
...@@ -6637,27 +6637,28 @@ TEST(DebugCoverage) { ...@@ -6637,27 +6637,28 @@ TEST(DebugCoverage) {
"f();\n" "f();\n"
"f();"); "f();");
CompileRun(source); CompileRun(source);
v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate); v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate, false);
CHECK_EQ(1u, coverage.ScriptCount()); CHECK_EQ(1u, coverage.ScriptCount());
v8::Local<v8::debug::Script> script = coverage.GetScript(0); v8::debug::Coverage::ScriptData script_data = coverage.GetScriptData(0);
v8::Local<v8::debug::Script> script = script_data.GetScript();
CHECK(script->Source() CHECK(script->Source()
.ToLocalChecked() .ToLocalChecked()
->Equals(env.local(), source) ->Equals(env.local(), source)
.FromMaybe(false)); .FromMaybe(false));
v8::debug::Coverage::Range range = coverage.GetRange(0); CHECK_EQ(2u, script_data.FunctionCount());
CHECK_EQ(0, range.Start().GetLineNumber()); v8::debug::Coverage::FunctionData function_data =
CHECK_EQ(0, range.Start().GetColumnNumber()); script_data.GetFunctionData(0);
CHECK_EQ(3, range.End().GetLineNumber()); CHECK_EQ(0, function_data.Start().GetLineNumber());
CHECK_EQ(4, range.End().GetColumnNumber()); CHECK_EQ(0, function_data.Start().GetColumnNumber());
CHECK_EQ(1, range.Count()); CHECK_EQ(3, function_data.End().GetLineNumber());
CHECK_EQ(1u, range.NestedCount()); CHECK_EQ(4, function_data.End().GetColumnNumber());
CHECK_EQ(1, function_data.Count());
range = range.GetNested(0);
CHECK_EQ(0, range.Start().GetLineNumber()); function_data = script_data.GetFunctionData(1);
CHECK_EQ(0, range.Start().GetColumnNumber()); CHECK_EQ(0, function_data.Start().GetLineNumber());
CHECK_EQ(1, range.End().GetLineNumber()); CHECK_EQ(0, function_data.Start().GetColumnNumber());
CHECK_EQ(1, range.End().GetColumnNumber()); CHECK_EQ(1, function_data.End().GetLineNumber());
CHECK_EQ(2, range.Count()); CHECK_EQ(1, function_data.End().GetColumnNumber());
CHECK_EQ(0, range.NestedCount()); CHECK_EQ(2, function_data.Count());
} }
This diff is collapsed.
// 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.
var source =
`
function fib(x) {
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}
(function iife() {
return 1;
})();
fib(5);
`;
print("Test collecting code coverage data with Runtime.collectCoverage.");
function ClearAndGC() {
return Protocol.Runtime.evaluate({ expression: "fib = null;" })
.then(() => Protocol.HeapProfiler.enable())
.then(() => Protocol.HeapProfiler.collectGarbage())
.then(() => Protocol.HeapProfiler.disable());
}
InspectorTest.runTestSuite([
function testPreciseCoverage(next)
{
Protocol.Runtime.enable()
.then(Protocol.Runtime.startPreciseCoverage)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "1", persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(ClearAndGC)
.then(InspectorTest.logMessage)
.then(Protocol.Runtime.takePreciseCoverage)
.then(InspectorTest.logMessage)
.then(Protocol.Runtime.takePreciseCoverage)
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.stopPreciseCoverage)
.then(Protocol.Runtime.disable)
.then(next);
},
function testPreciseCoverageFail(next)
{
Protocol.Runtime.enable()
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "2", persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.takePreciseCoverage)
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.disable)
.then(next);
},
function testBestEffortCoverage(next)
{
Protocol.Runtime.enable()
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "3", persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.getBestEffortCoverage)
.then(InspectorTest.logMessage)
.then(Protocol.Runtime.getBestEffortCoverage)
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.disable)
.then(next);
},
function testBestEffortCoveragePrecise(next)
{
Protocol.Runtime.enable()
.then(Protocol.Runtime.startPreciseCoverage)
.then(() => Protocol.Runtime.compileScript({ expression: source, sourceURL: "4", persistScript: true }))
.then((result) => Protocol.Runtime.runScript({ scriptId: result.result.scriptId }))
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.getBestEffortCoverage)
.then(InspectorTest.logMessage)
.then(Protocol.Runtime.getBestEffortCoverage)
.then(InspectorTest.logMessage)
.then(ClearAndGC)
.then(Protocol.Runtime.stopPreciseCoverage)
.then(Protocol.Runtime.disable)
.then(next);
},
]);
...@@ -8,31 +8,18 @@ ...@@ -8,31 +8,18 @@
function GetCoverage(source) { function GetCoverage(source) {
for (var script of %DebugCollectCoverage()) { for (var script of %DebugCollectCoverage()) {
if (script.script.source == source) return script.toplevel; if (script.script.source == source) return script;
} }
return undefined; return undefined;
} }
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;
}
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();
expectation = expectation.trim();
eval(source); eval(source);
var coverage = GetCoverage(source); var coverage = GetCoverage(source);
var result = ApplyCoverageToSource(source, coverage); var result = JSON.stringify(coverage);
print(result); print(result);
assertEquals(expectation, result, name + " failed"); assertEquals(JSON.stringify(expectation), result, name + " failed");
} }
TestCoverage( TestCoverage(
...@@ -42,11 +29,8 @@ function f() {} ...@@ -42,11 +29,8 @@ function f() {}
f(); f();
f(); f();
`, `,
` [{"start":0,"end":25,"count":1},
[[function f() {}](f:2) {"start":0,"end":15,"count":2}]
f();
f();](:1)
`
); );
TestCoverage( TestCoverage(
...@@ -56,11 +40,8 @@ var f = () => 1; ...@@ -56,11 +40,8 @@ var f = () => 1;
f(); f();
f(); f();
`, `,
` [{"start":0,"end":26,"count":1},
[var f = [() => 1](f:2); {"start":8,"end":15,"count":2}]
f();
f();](:1)
`
); );
TestCoverage( TestCoverage(
...@@ -74,16 +55,9 @@ function f() { ...@@ -74,16 +55,9 @@ function f() {
f(); f();
f(); f();
`, `,
` [{"start":0,"end":58,"count":1},
[[function f() { {"start":0,"end":48,"count":2},
[function g() {}](g:4) {"start":17,"end":32,"count":4}]
g();
g();
}](f:2)
f();
f();](:1)
`
); );
TestCoverage( TestCoverage(
...@@ -95,11 +69,6 @@ function fib(x) { ...@@ -95,11 +69,6 @@ function fib(x) {
} }
fib(5); fib(5);
`, `,
` [{"start":0,"end":80,"count":1},
[[function fib(x) { {"start":0,"end":72,"count":15}]
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}](fib:15)
fib(5);](:1)
`
); );
...@@ -8,39 +8,21 @@ ...@@ -8,39 +8,21 @@
function GetCoverage(source) { function GetCoverage(source) {
for (var script of %DebugCollectCoverage()) { for (var script of %DebugCollectCoverage()) {
if (script.script.source == source) return script.toplevel; if (script.script.source == source) return script;
} }
return undefined; return undefined;
} }
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;
}
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"); %CollectGarbage("collect dead objects");
var coverage = GetCoverage(source); var coverage = GetCoverage(source);
if (expectation === undefined) { var result = JSON.stringify(coverage);
assertEquals(undefined, coverage);
} else {
expectation = expectation.trim();
var result = ApplyCoverageToSource(source, coverage);
print(result); print(result);
assertEquals(expectation, result, name + " failed"); assertEquals(JSON.stringify(expectation), result, name + " failed");
}
} }
// Without precise coverage enabled, we lose coverage data to the GC. // Without precise coverage enabled, we lose coverage data to the GC.
TestCoverage( TestCoverage(
"call an IIFE", "call an IIFE",
...@@ -69,9 +51,7 @@ TestCoverage( ...@@ -69,9 +51,7 @@ TestCoverage(
` `
(function f() {})(); (function f() {})();
`, `,
` [{"start":0,"end":20,"count":1},{"start":1,"end":16,"count":1}]
[([function f() {}](f:1))();](:1)
`
); );
TestCoverage( TestCoverage(
...@@ -82,12 +62,7 @@ for (var i = 0; i < 10; i++) { ...@@ -82,12 +62,7 @@ for (var i = 0; i < 10; i++) {
i += f(); i += f();
} }
`, `,
` [{"start":0,"end":63,"count":1},{"start":41,"end":48,"count":5}]
[for (var i = 0; i < 10; i++) {
let f = [() => 1](f:5);
i += f();
}](:1)
`
); );
%DebugTogglePreciseCoverage(false); %DebugTogglePreciseCoverage(false);
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