[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}
diff --git a/src/api.cc b/src/api.cc
index f29a894..f64534c 100644
--- a/src/api.cc
+++ b/src/api.cc
@@ -9541,46 +9541,50 @@
}
}
-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(); }
-
-debug::Coverage::Range debug::Coverage::Range::GetNested(size_t i) {
- return Range(&range_->inner[i], script_);
+MaybeLocal<String> debug::Coverage::FunctionData::Name() {
+ return ToApiHandle<String>(function_->name);
}
-MaybeLocal<String> debug::Coverage::Range::Name() {
- return ToApiHandle<String>(range_->name);
+Local<debug::Script> debug::Coverage::ScriptData::GetScript() {
+ return ToApiHandle<debug::Script>(script_->script);
+}
+
+size_t debug::Coverage::ScriptData::FunctionCount() {
+ return script_->functions.size();
+}
+
+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::ScriptData debug::Coverage::GetScriptData(size_t i) {
+ 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) {
- 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) {
diff --git a/src/d8.cc b/src/d8.cc
index a37eb9f..360330b 100644
--- a/src/d8.cc
+++ b/src/d8.cc
@@ -1657,51 +1657,15 @@
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 @@
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;
diff --git a/src/debug/debug-coverage.cc b/src/debug/debug-coverage.cc
index 76a88b0..8a13b6c 100644
--- a/src/debug/debug-coverage.cc
+++ b/src/debug/debug-coverage.cc
@@ -58,12 +58,7 @@
}
} // 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 @@
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 @@
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 @@
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;
diff --git a/src/debug/debug-coverage.h b/src/debug/debug-coverage.h
index e54b4c3..36128bc 100644
--- a/src/debug/debug-coverage.h
+++ b/src/debug/debug-coverage.h
@@ -16,28 +16,28 @@
// 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.
diff --git a/src/debug/debug-interface.h b/src/debug/debug-interface.h
index ea89ce5..be8ed90 100644
--- a/src/debug/debug-interface.h
+++ b/src/debug/debug-interface.h
@@ -17,7 +17,8 @@
namespace v8 {
namespace internal {
-struct CoverageRange;
+struct CoverageFunction;
+struct CoverageScript;
class Coverage;
class Script;
}
@@ -211,33 +212,45 @@
*/
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;
};
- static Coverage Collect(Isolate* isolate);
+ 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, 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();
diff --git a/src/inspector/js_protocol.json b/src/inspector/js_protocol.json
index 8b08b0b..1aa840c 100644
--- a/src/inspector/js_protocol.json
+++ b/src/inspector/js_protocol.json
@@ -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": [
diff --git a/src/inspector/v8-runtime-agent-impl.cc b/src/inspector/v8-runtime-agent-impl.cc
index b40f08e..d324d34 100644
--- a/src/inspector/v8-runtime-agent-impl.cc
+++ b/src/inspector/v8-runtime-agent-impl.cc
@@ -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 @@
static const char customObjectFormatterEnabled[] =
"customObjectFormatterEnabled";
static const char runtimeEnabled[] = "runtimeEnabled";
+static const char preciseCoverageStarted[] = "preciseCoverageStarted";
};
using protocol::Runtime::RemoteObject;
@@ -645,6 +647,88 @@
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 @@
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 @@
reset();
m_inspector->client()->endEnsureAllContextsInGroup(
m_session->contextGroupId());
+ stopPreciseCoverage();
return Response::OK();
}
diff --git a/src/inspector/v8-runtime-agent-impl.h b/src/inspector/v8-runtime-agent-impl.h
index 9caa1fb..fb592bd 100644
--- a/src/inspector/v8-runtime-agent-impl.h
+++ b/src/inspector/v8-runtime-agent-impl.h
@@ -97,6 +97,14 @@
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*);
diff --git a/src/runtime/runtime-debug.cc b/src/runtime/runtime-debug.cc
index fb2e893..3649621 100644
--- a/src/runtime/runtime-debug.cc
+++ b/src/runtime/runtime-debug.cc
@@ -1895,49 +1895,16 @@
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 @@
// 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);
diff --git a/test/cctest/test-debug.cc b/test/cctest/test-debug.cc
index b30c6d9..79a18a0 100644
--- a/test/cctest/test-debug.cc
+++ b/test/cctest/test-debug.cc
@@ -6637,27 +6637,28 @@
"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());
+ 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());
- 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());
+ 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());
}
diff --git a/test/inspector/runtime/coverage-expected.txt b/test/inspector/runtime/coverage-expected.txt
new file mode 100644
index 0000000..1c45409
--- /dev/null
+++ b/test/inspector/runtime/coverage-expected.txt
@@ -0,0 +1,333 @@
+Test collecting code coverage data with Runtime.collectCoverage.
+
+Running test: testPreciseCoverage
+{
+ id : <messageId>
+ result : {
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ [0] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 0
+ endLineNumber : 9
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ [1] : {
+ functionName : fib
+ ranges : [
+ [0] : {
+ count : 15
+ endColumnNumber : 1
+ endLineNumber : 4
+ startColumnNumber : 0
+ startLineNumber : 1
+ }
+ ]
+ }
+ [2] : {
+ functionName : iife
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 1
+ endLineNumber : 7
+ startColumnNumber : 1
+ startLineNumber : 5
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url : 1
+ }
+ [1] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 11
+ endLineNumber : 0
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url :
+ }
+ ]
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ [0] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 0
+ endColumnNumber : 0
+ endLineNumber : 9
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ [1] : {
+ functionName : fib
+ ranges : [
+ [0] : {
+ count : 0
+ endColumnNumber : 1
+ endLineNumber : 4
+ startColumnNumber : 0
+ startLineNumber : 1
+ }
+ ]
+ }
+ [2] : {
+ functionName : iife
+ ranges : [
+ [0] : {
+ count : 0
+ endColumnNumber : 1
+ endLineNumber : 7
+ startColumnNumber : 1
+ startLineNumber : 5
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url : 1
+ }
+ [1] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 0
+ endColumnNumber : 11
+ endLineNumber : 0
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url :
+ }
+ ]
+ }
+}
+
+Running test: testPreciseCoverageFail
+{
+ id : <messageId>
+ result : {
+ result : {
+ description : 8
+ type : number
+ value : 8
+ }
+ }
+}
+{
+ error : {
+ code : -32000
+ message : Precise coverage has not been started.
+ }
+ id : <messageId>
+}
+
+Running test: testBestEffortCoverage
+{
+ id : <messageId>
+ result : {
+ result : {
+ description : 8
+ type : number
+ value : 8
+ }
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ ]
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ ]
+ }
+}
+
+Running test: testBestEffortCoveragePrecise
+{
+ id : <messageId>
+ result : {
+ result : {
+ description : 8
+ type : number
+ value : 8
+ }
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ [0] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 0
+ endLineNumber : 9
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ [1] : {
+ functionName : fib
+ ranges : [
+ [0] : {
+ count : 15
+ endColumnNumber : 1
+ endLineNumber : 4
+ startColumnNumber : 0
+ startLineNumber : 1
+ }
+ ]
+ }
+ [2] : {
+ functionName : iife
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 1
+ endLineNumber : 7
+ startColumnNumber : 1
+ startLineNumber : 5
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url : 4
+ }
+ [1] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 11
+ endLineNumber : 0
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url :
+ }
+ ]
+ }
+}
+{
+ id : <messageId>
+ result : {
+ result : [
+ [0] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 0
+ endLineNumber : 9
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ [1] : {
+ functionName : fib
+ ranges : [
+ [0] : {
+ count : 15
+ endColumnNumber : 1
+ endLineNumber : 4
+ startColumnNumber : 0
+ startLineNumber : 1
+ }
+ ]
+ }
+ [2] : {
+ functionName : iife
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 1
+ endLineNumber : 7
+ startColumnNumber : 1
+ startLineNumber : 5
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url : 4
+ }
+ [1] : {
+ functions : [
+ [0] : {
+ functionName :
+ ranges : [
+ [0] : {
+ count : 1
+ endColumnNumber : 11
+ endLineNumber : 0
+ startColumnNumber : 0
+ startLineNumber : 0
+ }
+ ]
+ }
+ ]
+ scriptId : <scriptId>
+ url :
+ }
+ ]
+ }
+}
diff --git a/test/inspector/runtime/coverage.js b/test/inspector/runtime/coverage.js
new file mode 100644
index 0000000..dff7eca
--- /dev/null
+++ b/test/inspector/runtime/coverage.js
@@ -0,0 +1,89 @@
+// 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);
+ },
+]);
diff --git a/test/mjsunit/code-coverage-ad-hoc.js b/test/mjsunit/code-coverage-ad-hoc.js
index 5dd2da2..9289c21 100644
--- a/test/mjsunit/code-coverage-ad-hoc.js
+++ b/test/mjsunit/code-coverage-ad-hoc.js
@@ -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 @@
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 @@
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 @@
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 @@
}
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}]
);
diff --git a/test/mjsunit/code-coverage-precise.js b/test/mjsunit/code-coverage-precise.js
index a25542d..03015f9 100644
--- a/test/mjsunit/code-coverage-precise.js
+++ b/test/mjsunit/code-coverage-precise.js
@@ -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 @@
`
(function f() {})();
`,
-`
-[([function f() {}](f:1))();](:1)
-`
+[{"start":0,"end":20,"count":1},{"start":1,"end":16,"count":1}]
);
TestCoverage(
@@ -82,12 +62,7 @@
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);