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 {
}
}
debug::Coverage::Range::Range(i::CoverageRange* range,
Local<debug::Script> script)
: range_(range), script_(script) {
debug::Coverage::FunctionData::FunctionData(i::CoverageFunction* function,
Local<debug::Script> script)
: function_(function) {
i::Handle<i::Script> i_script = v8::Utils::OpenHandle(*script);
i::Script::PositionInfo start;
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::GetPositionInfo(i_script, range->end, &end,
i::Script::GetPositionInfo(i_script, function->end, &end,
i::Script::WITH_OFFSET);
start_ = Location(start.line, start.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) {
return Range(&range_->inner[i], script_);
size_t debug::Coverage::ScriptData::FunctionCount() {
return script_->functions.size();
}
MaybeLocal<String> debug::Coverage::Range::Name() {
return ToApiHandle<String>(range_->name);
debug::Coverage::FunctionData debug::Coverage::ScriptData::GetFunctionData(
size_t i) {
return FunctionData(&script_->functions.at(i), GetScript());
}
debug::Coverage::~Coverage() { delete coverage_; }
size_t debug::Coverage::ScriptCount() { return coverage_->size(); }
Local<debug::Script> debug::Coverage::GetScript(size_t i) {
return ToApiHandle<debug::Script>(coverage_->at(i).script);
}
debug::Coverage::Range debug::Coverage::GetRange(size_t i) {
return Range(&coverage_->at(i).toplevel, GetScript(i));
debug::Coverage::ScriptData debug::Coverage::GetScriptData(size_t i) {
return ScriptData(&coverage_->at(i));
}
debug::Coverage debug::Coverage::Collect(Isolate* isolate) {
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate)));
debug::Coverage debug::Coverage::Collect(Isolate* isolate, bool reset_count) {
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate),
reset_count));
}
void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) {
......
......@@ -1657,51 +1657,15 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
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).
void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
if (!file) return;
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);
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.
Local<String> name;
if (!script->Name().ToLocal(&name)) continue;
......@@ -1711,7 +1675,33 @@ void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
sink << "SF:";
sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
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.
for (size_t i = 0; i < lines.size(); i++) {
sink << "DA:" << (i + 1) << "," << lines[i] << std::endl;
......
......@@ -58,12 +58,7 @@ bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
}
} // anonymous namespace
CoverageScript::CoverageScript(Isolate* isolate, Handle<Script> s,
int source_length)
: script(s),
toplevel(0, source_length, 1, isolate->factory()->empty_string()) {}
Coverage* Coverage::Collect(Isolate* isolate) {
Coverage* Coverage::Collect(Isolate* isolate, bool reset_count) {
SharedToCounterMap counter_map;
// Feed invocation count into the counter map.
......@@ -76,6 +71,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
SharedFunctionInfo* shared = vector->shared_function_info();
DCHECK(shared->IsSubjectToDebugging());
uint32_t count = static_cast<uint32_t>(vector->invocation_count());
if (reset_count) vector->clear_invocation_count();
counter_map.Add(shared, count);
}
} else {
......@@ -88,6 +84,7 @@ Coverage* Coverage::Collect(Isolate* isolate) {
SharedFunctionInfo* shared = vector->shared_function_info();
if (!shared->IsSubjectToDebugging()) continue;
uint32_t count = static_cast<uint32_t>(vector->invocation_count());
if (reset_count) vector->clear_invocation_count();
counter_map.Add(shared, count);
}
}
......@@ -101,47 +98,39 @@ Coverage* Coverage::Collect(Isolate* isolate) {
if (script->type() != Script::TYPE_NORMAL) continue;
// Create and add new script data.
int source_end = String::cast(script->source())->length();
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;
bool has_toplevel = false;
{
// Collect a list of shared function infos sorted by start position.
// Shared function infos are usually already sorted. Except for classes.
// If the start position is the same, sort from outer to inner function.
// Sort functions by start position, from outer to inner functions.
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::vector<CoverageRange*> stack;
stack.push_back(&result->back().toplevel);
functions->reserve(sorted.size() + (has_toplevel ? 0 : 1));
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.
for (SharedFunctionInfo* info : sorted) {
int start = StartPosition(info);
int end = info->end_position();
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);
outer->inner.emplace_back(start, end, count, name);
stack.push_back(&outer->inner.back());
}
Handle<String> name(info->DebugName(), isolate);
functions->emplace_back(start, end, count, name);
}
}
return result;
......
......@@ -16,28 +16,28 @@ namespace internal {
// Forward declaration.
class Isolate;
struct CoverageRange {
CoverageRange(int s, int e, uint32_t c, Handle<String> n)
struct CoverageFunction {
CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
: start(s), end(e), count(c), name(n) {}
int start;
int end;
uint32_t count;
Handle<String> name;
std::vector<CoverageRange> inner;
};
struct CoverageScript {
// 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;
CoverageRange toplevel;
// Functions are sorted by start position, from outer to inner function.
std::vector<CoverageFunction> functions;
};
class Coverage : public std::vector<CoverageScript> {
public:
// Allocate a new Coverage object and populate with result.
// 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
// invocation count is not affected by GC.
......
......@@ -17,7 +17,8 @@
namespace v8 {
namespace internal {
struct CoverageRange;
struct CoverageFunction;
struct CoverageScript;
class Coverage;
class Script;
}
......@@ -211,33 +212,45 @@ class GeneratorObject {
*/
class V8_EXPORT_PRIVATE Coverage {
public:
class V8_EXPORT_PRIVATE Range {
class ScriptData; // Forward declaration.
class V8_EXPORT_PRIVATE FunctionData {
public:
// 0-based line and colum numbers.
Location Start() { return start_; }
Location End() { return end_; }
uint32_t Count();
size_t NestedCount();
Range GetNested(size_t i);
MaybeLocal<String> Name();
private:
Range(i::CoverageRange* range, Local<debug::Script> script);
i::CoverageRange* range_;
FunctionData(i::CoverageFunction* function, Local<debug::Script> script);
i::CoverageFunction* function_;
Location start_;
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);
size_t ScriptCount();
Local<debug::Script> GetScript(size_t i);
Range GetRange(size_t i);
ScriptData GetScriptData(size_t i);
bool IsEmpty() { return coverage_ == nullptr; }
~Coverage();
......
......@@ -204,6 +204,38 @@
{ "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." }
]
},
{ "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": [
......@@ -343,6 +375,32 @@
{ "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."}
],
"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": [
......
......@@ -30,6 +30,7 @@
#include "src/inspector/v8-runtime-agent-impl.h"
#include "src/debug/debug-interface.h"
#include "src/inspector/injected-script.h"
#include "src/inspector/inspected-context.h"
#include "src/inspector/protocol/Protocol.h"
......@@ -51,6 +52,7 @@ namespace V8RuntimeAgentImplState {
static const char customObjectFormatterEnabled[] =
"customObjectFormatterEnabled";
static const char runtimeEnabled[] = "runtimeEnabled";
static const char preciseCoverageStarted[] = "preciseCoverageStarted";
};
using protocol::Runtime::RemoteObject;
......@@ -645,6 +647,88 @@ void V8RuntimeAgentImpl::runScript(
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() {
if (!m_state->booleanProperty(V8RuntimeAgentImplState::runtimeEnabled, false))
return;
......@@ -653,6 +737,9 @@ void V8RuntimeAgentImpl::restore() {
if (m_state->booleanProperty(
V8RuntimeAgentImplState::customObjectFormatterEnabled, false))
m_session->setCustomObjectFormatterEnabled(true);
if (m_state->booleanProperty(V8RuntimeAgentImplState::preciseCoverageStarted,
false))
startPreciseCoverage();
}
Response V8RuntimeAgentImpl::enable() {
......@@ -680,6 +767,7 @@ Response V8RuntimeAgentImpl::disable() {
reset();
m_inspector->client()->endEnsureAllContextsInGroup(
m_session->contextGroupId());
stopPreciseCoverage();
return Response::OK();
}
......
......@@ -97,6 +97,14 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Maybe<bool> includeCommandLineAPI, Maybe<bool> returnByValue,
Maybe<bool> generatePreview, Maybe<bool> awaitPromise,
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 reportExecutionContextCreated(InspectedContext*);
......
......@@ -1895,49 +1895,16 @@ RUNTIME_FUNCTION(Runtime_DebugIsActive) {
return Smi::FromInt(isolate->debug()->is_active());
}
RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
UNIMPLEMENTED();
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) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
// Collect coverage data.
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate));
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate, false));
Factory* factory = isolate->factory();
// Turn the returned data structure into JavaScript.
// Create an array of scripts.
......@@ -1945,22 +1912,31 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
// Prepare property keys.
Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
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++) {
const auto& data = coverage->at(i);
const auto& script_data = coverage->at(i);
HandleScope inner_scope(isolate);
Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto();
Handle<JSObject> wrapper = Script::GetWrapper(data.script);
int num_functions = static_cast<int>(script_data.functions.size());
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);
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);
......
......@@ -6637,27 +6637,28 @@ TEST(DebugCoverage) {
"f();\n"
"f();");
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());
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()
.ToLocalChecked()
->Equals(env.local(), source)
.FromMaybe(false));
v8::debug::Coverage::Range range = coverage.GetRange(0);
CHECK_EQ(0, range.Start().GetLineNumber());
CHECK_EQ(0, range.Start().GetColumnNumber());
CHECK_EQ(3, range.End().GetLineNumber());
CHECK_EQ(4, range.End().GetColumnNumber());
CHECK_EQ(1, range.Count());
CHECK_EQ(1u, range.NestedCount());
range = range.GetNested(0);
CHECK_EQ(0, range.Start().GetLineNumber());
CHECK_EQ(0, range.Start().GetColumnNumber());
CHECK_EQ(1, range.End().GetLineNumber());
CHECK_EQ(1, range.End().GetColumnNumber());
CHECK_EQ(2, range.Count());
CHECK_EQ(0, range.NestedCount());
CHECK_EQ(2u, script_data.FunctionCount());
v8::debug::Coverage::FunctionData function_data =
script_data.GetFunctionData(0);
CHECK_EQ(0, function_data.Start().GetLineNumber());
CHECK_EQ(0, function_data.Start().GetColumnNumber());
CHECK_EQ(3, function_data.End().GetLineNumber());
CHECK_EQ(4, function_data.End().GetColumnNumber());
CHECK_EQ(1, function_data.Count());
function_data = script_data.GetFunctionData(1);
CHECK_EQ(0, function_data.Start().GetLineNumber());
CHECK_EQ(0, function_data.Start().GetColumnNumber());
CHECK_EQ(1, function_data.End().GetLineNumber());
CHECK_EQ(1, function_data.End().GetColumnNumber());
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 @@
function GetCoverage(source) {
for (var script of %DebugCollectCoverage()) {
if (script.script.source == source) return script.toplevel;
if (script.script.source == source) return script;
}
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) {
source = source.trim();
expectation = expectation.trim();
eval(source);
var coverage = GetCoverage(source);
var result = ApplyCoverageToSource(source, coverage);
var result = JSON.stringify(coverage);
print(result);
assertEquals(expectation, result, name + " failed");
assertEquals(JSON.stringify(expectation), result, name + " failed");
}
TestCoverage(
......@@ -42,11 +29,8 @@ function f() {}
f();
f();
`,
`
[[function f() {}](f:2)
f();
f();](:1)
`
[{"start":0,"end":25,"count":1},
{"start":0,"end":15,"count":2}]
);
TestCoverage(
......@@ -56,11 +40,8 @@ var f = () => 1;
f();
f();
`,
`
[var f = [() => 1](f:2);
f();
f();](:1)
`
[{"start":0,"end":26,"count":1},
{"start":8,"end":15,"count":2}]
);
TestCoverage(
......@@ -74,16 +55,9 @@ function f() {
f();
f();
`,
`
[[function f() {
[function g() {}](g:4)
g();
g();
}](f:2)
f();
f();](:1)
`
[{"start":0,"end":58,"count":1},
{"start":0,"end":48,"count":2},
{"start":17,"end":32,"count":4}]
);
TestCoverage(
......@@ -95,11 +69,6 @@ function fib(x) {
}
fib(5);
`,
`
[[function fib(x) {
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}](fib:15)
fib(5);](:1)
`
[{"start":0,"end":80,"count":1},
{"start":0,"end":72,"count":15}]
);
......@@ -8,39 +8,21 @@
function GetCoverage(source) {
for (var script of %DebugCollectCoverage()) {
if (script.script.source == source) return script.toplevel;
if (script.script.source == source) return script;
}
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) {
source = source.trim();
eval(source);
%CollectGarbage("remove dead objects");
%CollectGarbage("collect dead objects");
var coverage = GetCoverage(source);
if (expectation === undefined) {
assertEquals(undefined, coverage);
} else {
expectation = expectation.trim();
var result = ApplyCoverageToSource(source, coverage);
print(result);
assertEquals(expectation, result, name + " failed");
}
var result = JSON.stringify(coverage);
print(result);
assertEquals(JSON.stringify(expectation), result, name + " failed");
}
// Without precise coverage enabled, we lose coverage data to the GC.
TestCoverage(
"call an IIFE",
......@@ -69,9 +51,7 @@ TestCoverage(
`
(function f() {})();
`,
`
[([function f() {}](f:1))();](:1)
`
[{"start":0,"end":20,"count":1},{"start":1,"end":16,"count":1}]
);
TestCoverage(
......@@ -82,12 +62,7 @@ for (var i = 0; i < 10; i++) {
i += f();
}
`,
`
[for (var i = 0; i < 10; i++) {
let f = [() => 1](f:5);
i += f();
}](:1)
`
[{"start":0,"end":63,"count":1},{"start":41,"end":48,"count":5}]
);
%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