Commit c39123dd authored by yangguo's avatar yangguo Committed by Commit bot

[debugger] implement inspector-facing API for code coverage.

The inspector uses V8's API handles and should not access
V8 internals. This change makes sure it can use the coverage
data in an encapsulated way.

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

Review-Url: https://codereview.chromium.org/2696163002
Cr-Commit-Position: refs/heads/master@{#43231}
parent 277b8e93
......@@ -35,6 +35,7 @@
#include "src/contexts.h"
#include "src/conversions-inl.h"
#include "src/counters.h"
#include "src/debug/debug-coverage.h"
#include "src/debug/debug.h"
#include "src/deoptimizer.h"
#include "src/execution.h"
......@@ -9501,6 +9502,52 @@ Local<String> CpuProfileNode::GetFunctionName() const {
}
}
debug::Coverage::Range::Range(i::CoverageRange* range,
Local<debug::Script> script)
: range_(range), script_(script) {
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::WITH_OFFSET);
i::Script::GetPositionInfo(i_script, range->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; }
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::Range::Name() {
return ToApiHandle<String>(range_->name);
}
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 debug::Coverage::Collect(Isolate* isolate) {
return Coverage(i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate)));
}
void debug::Coverage::TogglePrecise(Isolate* isolate, bool enable) {
i::Coverage::TogglePrecise(reinterpret_cast<i::Isolate*>(isolate), enable);
}
const char* CpuProfileNode::GetFunctionNameStr() const {
const i::ProfileNode* node = reinterpret_cast<const i::ProfileNode*>(this);
return node->entry()->name();
......
......@@ -30,7 +30,7 @@
#include "src/base/platform/time.h"
#include "src/base/sys-info.h"
#include "src/basic-block-profiler.h"
#include "src/debug/debug-coverage.h"
#include "src/debug/debug-interface.h"
#include "src/interpreter/interpreter.h"
#include "src/list-inl.h"
#include "src/msan.h"
......@@ -1658,38 +1658,38 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
namespace {
void ReadRange(std::ofstream* s, std::vector<uint32_t>* lines,
i::Handle<i::Script> script, const i::Coverage::Range* range) {
// Compute line and column numbers from source position.
i::Script::PositionInfo start;
i::Script::PositionInfo end;
i::Script::GetPositionInfo(script, range->start, &start,
i::Script::NO_OFFSET);
i::Script::GetPositionInfo(script, range->end, &end, i::Script::NO_OFFSET);
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(start.line) = std::max(lines->at(start.line), range->count);
lines->at(end.line) = std::max(lines->at(end.line), range->count);
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.
for (int i = start.line + 1; i < end.line; i++) lines->at(i) = range->count;
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.
if (!range->name.empty()) {
// Truncate UC16 characters down to Latin1.
std::unique_ptr<char[]> name(new char[range->name.size() + 1]);
for (size_t i = 0; i < range->name.size(); i++) {
name[i] = static_cast<char>(range->name.data()[i]);
}
name[range->name.size()] = 0;
*s << "FN:" << (start.line + 1) << "," << name.get() << std::endl;
*s << "FNDA:" << range->count << "," << name.get() << std::endl;
// 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 {
// Anonymous function. Use line and column as name.
*s << "FN:" << (start.line + 1) << ",";
*s << "<" << (start.line + 1) << "-" << start.column << ">" << std::endl;
*s << "FNDA:" << range->count << ",";
*s << "<" << (start.line + 1) << "-" << start.column << ">" << std::endl;
name_stream << "<" << line_plus_one << "-";
name_stream << range.Start().GetColumnNumber() << ">";
}
// Recurse over inner ranges.
for (const auto& inner : range->inner) ReadRange(s, lines, script, &inner);
*s << "FN:" << line_plus_one << "," << name_stream.str() << std::endl;
*s << "FNDA:" << range.Count() << "," << name_stream.str() << std::endl;
}
} // anonymous namespace
......@@ -1697,22 +1697,20 @@ void ReadRange(std::ofstream* s, std::vector<uint32_t>* lines,
void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
if (!file) return;
HandleScope handle_scope(isolate);
std::vector<i::Coverage::ScriptData> data =
i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate));
debug::Coverage coverage = debug::Coverage::Collect(isolate);
std::ofstream sink(file, std::ofstream::app);
for (const auto& script_data : data) {
i::Handle<i::Script> script = script_data.script;
for (size_t i = 0; i < coverage.ScriptCount(); i++) {
Local<debug::Script> script = coverage.GetScript(i);
// Skip unnamed scripts.
if (!script->name()->IsString()) continue;
std::string file_name = ToSTLString(v8::Utils::ToLocal(
i::Handle<i::String>(i::String::cast(script->name()))));
Local<String> name;
if (!script->Name().ToLocal(&name)) continue;
std::string file_name = ToSTLString(name);
// Skip scripts not backed by a file.
if (!std::ifstream(file_name).good()) continue;
sink << "SF:";
sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
std::vector<uint32_t> lines;
lines.resize(i::FixedArray::cast(script->line_ends())->length(), 0);
ReadRange(&sink, &lines, script, &script_data.toplevel);
ReadRange(&sink, &lines, coverage.GetRange(i));
// 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;
......@@ -2513,9 +2511,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[], bool last_run) {
options.isolate_sources[i].StartExecuteInThread();
}
{
if (options.lcov_file) {
i::Coverage::EnablePrecise(reinterpret_cast<i::Isolate*>(isolate));
}
if (options.lcov_file) debug::Coverage::TogglePrecise(isolate, true);
HandleScope scope(isolate);
Local<Context> context = CreateEvaluationContext(isolate);
if (last_run && options.use_interactive_shell()) {
......
......@@ -58,7 +58,12 @@ bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
}
} // anonymous namespace
std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
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) {
SharedToCounterMap counter_map;
// Feed invocation count into the counter map.
......@@ -89,15 +94,16 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// Iterate shared function infos of every script and build a mapping
// between source ranges and invocation counts.
std::vector<Coverage::ScriptData> result;
Coverage* result = new Coverage();
Script::Iterator scripts(isolate);
while (Script* script = scripts.Next()) {
// Dismiss non-user scripts.
if (script->type() != Script::TYPE_NORMAL) continue;
// Create and add new script data.
int source_length = String::cast(script->source())->length();
result.emplace_back(Handle<Script>(script, isolate), source_length);
int source_end = String::cast(script->source())->length();
Handle<Script> script_handle(script, isolate);
result->emplace_back(isolate, script_handle, source_end);
std::vector<SharedFunctionInfo*> sorted;
......@@ -105,14 +111,13 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// 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.
HandleScope scope(isolate);
SharedFunctionInfo::ScriptIterator infos(Handle<Script>(script, isolate));
SharedFunctionInfo::ScriptIterator infos(script_handle);
while (SharedFunctionInfo* info = infos.Next()) sorted.push_back(info);
std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo);
}
std::vector<Range*> stack;
stack.push_back(&result.back().toplevel);
std::vector<CoverageRange*> stack;
stack.push_back(&result->back().toplevel);
// Use sorted list to reconstruct function nesting.
for (SharedFunctionInfo* info : sorted) {
......@@ -122,56 +127,53 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
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;
result->back().toplevel.start = start;
result->back().toplevel.end = end;
result->back().toplevel.count = count;
} else {
// The shared function infos are sorted by start.
DCHECK_LE(stack.back()->start, start);
// Drop the stack to the outer function.
while (start >= stack.back()->end) stack.pop_back();
Range* outer = stack.back();
CoverageRange* outer = stack.back();
// New nested function.
DCHECK_LE(end, outer->end);
outer->inner.emplace_back(start, end, count);
Range& nested = outer->inner.back();
String* name = info->DebugName();
nested.name.resize(name->length());
String::WriteToFlat(name, nested.name.data(), 0, name->length());
stack.push_back(&nested);
Handle<String> name(info->DebugName(), isolate);
outer->inner.emplace_back(start, end, count, name);
stack.push_back(&outer->inner.back());
}
}
}
return result;
}
void Coverage::EnablePrecise(Isolate* isolate) {
HandleScope scope(isolate);
// Remove all optimized function. Optimized and inlined functions do not
// increment invocation count.
Deoptimizer::DeoptimizeAll(isolate);
// Collect existing feedback vectors.
std::vector<Handle<FeedbackVector>> vectors;
{
HeapIterator heap_iterator(isolate->heap());
while (HeapObject* current_obj = heap_iterator.next()) {
if (!current_obj->IsFeedbackVector()) continue;
FeedbackVector* vector = FeedbackVector::cast(current_obj);
SharedFunctionInfo* shared = vector->shared_function_info();
if (!shared->IsSubjectToDebugging()) continue;
vector->clear_invocation_count();
vectors.emplace_back(vector, isolate);
void Coverage::TogglePrecise(Isolate* isolate, bool enable) {
if (enable) {
HandleScope scope(isolate);
// Remove all optimized function. Optimized and inlined functions do not
// increment invocation count.
Deoptimizer::DeoptimizeAll(isolate);
// Collect existing feedback vectors.
std::vector<Handle<FeedbackVector>> vectors;
{
HeapIterator heap_iterator(isolate->heap());
while (HeapObject* current_obj = heap_iterator.next()) {
if (!current_obj->IsFeedbackVector()) continue;
FeedbackVector* vector = FeedbackVector::cast(current_obj);
SharedFunctionInfo* shared = vector->shared_function_info();
if (!shared->IsSubjectToDebugging()) continue;
vector->clear_invocation_count();
vectors.emplace_back(vector, isolate);
}
}
// Add collected feedback vectors to the root list lest we lose them to GC.
Handle<ArrayList> list =
ArrayList::New(isolate, static_cast<int>(vectors.size()));
for (const auto& vector : vectors) list = ArrayList::Add(list, vector);
isolate->SetCodeCoverageList(*list);
} else {
isolate->SetCodeCoverageList(isolate->heap()->undefined_value());
}
// Add collected feedback vectors to the root list lest we lose them to GC.
Handle<ArrayList> list =
ArrayList::New(isolate, static_cast<int>(vectors.size()));
for (const auto& vector : vectors) list = ArrayList::Add(list, vector);
isolate->SetCodeCoverageList(*list);
}
void Coverage::DisablePrecise(Isolate* isolate) {
isolate->SetCodeCoverageList(isolate->heap()->undefined_value());
}
} // namespace internal
......
......@@ -7,7 +7,6 @@
#include <vector>
#include "src/allocation.h"
#include "src/debug/debug-interface.h"
#include "src/objects.h"
......@@ -17,31 +16,35 @@ namespace internal {
// Forward declaration.
class Isolate;
class Coverage : public AllStatic {
struct CoverageRange {
CoverageRange(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);
Handle<Script> script;
CoverageRange toplevel;
};
class Coverage : public std::vector<CoverageScript> {
public:
struct Range {
Range(int s, int e, uint32_t c) : start(s), end(e), count(c) {}
int start;
int end;
uint32_t count;
std::vector<uint16_t> name;
std::vector<Range> inner;
};
struct ScriptData {
// Initialize top-level function in case it has been garbage-collected.
ScriptData(Handle<Script> s, int source_length)
: script(s), toplevel(0, source_length, 1) {}
Handle<Script> script;
Range toplevel;
};
V8_EXPORT_PRIVATE static std::vector<ScriptData> Collect(Isolate* isolate);
// Allocate a new Coverage object and populate with result.
// The ownership is transferred to the caller.
static Coverage* Collect(Isolate* isolate);
// Enable precise code coverage. This disables optimization and makes sure
// invocation count is not affected by GC.
V8_EXPORT_PRIVATE static void EnablePrecise(Isolate* isolate);
V8_EXPORT_PRIVATE static void DisablePrecise(Isolate* isolate);
static void TogglePrecise(Isolate* isolate, bool enable);
private:
Coverage() {}
};
} // namespace internal
......
......@@ -12,8 +12,16 @@
#include "include/v8.h"
#include "src/debug/interface-types.h"
#include "src/globals.h"
namespace v8 {
namespace internal {
struct CoverageRange;
class Coverage;
class Script;
}
namespace debug {
/**
......@@ -109,7 +117,7 @@ void SetOutOfMemoryCallback(Isolate* isolate, OutOfMemoryCallback callback,
/**
* Native wrapper around v8::internal::Script object.
*/
class Script {
class V8_EXPORT_PRIVATE Script {
public:
v8::Isolate* GetIsolate() const;
......@@ -198,6 +206,45 @@ class GeneratorObject {
static v8::Local<debug::GeneratorObject> Cast(v8::Local<v8::Value> value);
};
/*
* Provide API layer between inspector and code coverage.
*/
class V8_EXPORT_PRIVATE Coverage {
public:
class V8_EXPORT_PRIVATE Range {
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_;
Location start_;
Location end_;
Local<debug::Script> script_;
friend class debug::Coverage;
};
static Coverage Collect(Isolate* isolate);
static void TogglePrecise(Isolate* isolate, bool enable);
size_t ScriptCount();
Local<debug::Script> GetScript(size_t i);
Range GetRange(size_t i);
~Coverage();
private:
explicit Coverage(i::Coverage* coverage) : coverage_(coverage) {}
i::Coverage* coverage_;
};
} // namespace debug
} // namespace v8
......
......@@ -9,6 +9,8 @@
#include <string>
#include <vector>
#include "src/globals.h"
namespace v8 {
namespace debug {
......@@ -16,7 +18,7 @@ namespace debug {
* Defines location inside script.
* Lines and columns are 0-based.
*/
class Location {
class V8_EXPORT_PRIVATE Location {
public:
Location(int line_number, int column_number);
/**
......
......@@ -6694,10 +6694,8 @@ class Script: public Struct {
// initializes the line ends array, avoiding expensive recomputations.
// The non-static version is not allocating and safe for unhandlified
// callsites.
V8_EXPORT_PRIVATE static bool GetPositionInfo(Handle<Script> script,
int position,
PositionInfo* info,
OffsetFlag offset_flag);
static bool GetPositionInfo(Handle<Script> script, int position,
PositionInfo* info, OffsetFlag offset_flag);
bool GetPositionInfo(int position, PositionInfo* info,
OffsetFlag offset_flag) const;
......
......@@ -1895,8 +1895,7 @@ RUNTIME_FUNCTION(Runtime_DebugBreakInOptimizedCode) {
}
namespace {
Handle<JSObject> CreateRangeObject(Isolate* isolate,
const Coverage::Range* range,
Handle<JSObject> CreateRangeObject(Isolate* isolate, const CoverageRange* range,
Handle<String> inner_string,
Handle<String> start_string,
Handle<String> end_string,
......@@ -1910,13 +1909,7 @@ Handle<JSObject> CreateRangeObject(Isolate* isolate,
factory->NewNumberFromInt(range->end), NONE);
JSObject::AddProperty(range_obj, count_string,
factory->NewNumberFromUint(range->count), NONE);
Handle<String> name = factory->anonymous_string();
if (!range->name.empty()) {
Vector<const uc16> name_vector(range->name.data(),
static_cast<int>(range->name.size()));
name = factory->NewStringFromTwoByte(name_vector).ToHandleChecked();
}
JSObject::AddProperty(range_obj, factory->name_string(), name, NONE);
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);
......@@ -1937,11 +1930,11 @@ Handle<JSObject> CreateRangeObject(Isolate* isolate,
RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
HandleScope scope(isolate);
// Collect coverage data.
std::vector<Coverage::ScriptData> script_data = Coverage::Collect(isolate);
std::unique_ptr<Coverage> coverage(Coverage::Collect(isolate));
Factory* factory = isolate->factory();
// Turn the returned data structure into JavaScript.
// Create an array of scripts.
int num_scripts = static_cast<int>(script_data.size());
int num_scripts = static_cast<int>(coverage->size());
// Prepare property keys.
Handle<FixedArray> scripts_array = factory->NewFixedArray(num_scripts);
Handle<String> script_string = factory->NewStringFromStaticChars("script");
......@@ -1952,7 +1945,7 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
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 = script_data[i];
const auto& data = coverage->at(i);
HandleScope inner_scope(isolate);
Handle<JSObject> script_obj = factory->NewJSObjectWithNullProto();
Handle<JSObject> wrapper = Script::GetWrapper(data.script);
......@@ -1969,11 +1962,7 @@ RUNTIME_FUNCTION(Runtime_DebugCollectCoverage) {
RUNTIME_FUNCTION(Runtime_DebugTogglePreciseCoverage) {
SealHandleScope shs(isolate);
CONVERT_BOOLEAN_ARG_CHECKED(enable, 0);
if (enable) {
Coverage::EnablePrecise(isolate);
} else {
Coverage::DisablePrecise(isolate);
}
Coverage::TogglePrecise(isolate, enable);
return isolate->heap()->undefined_value();
}
......
......@@ -6624,3 +6624,40 @@ UNINITIALIZED_TEST(DebugSetOutOfMemoryListener) {
}
isolate->Dispose();
}
TEST(DebugCoverage) {
i::FLAG_always_opt = false;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::debug::Coverage::TogglePrecise(isolate, true);
v8::Local<v8::String> source = v8_str(
"function f() {\n"
"}\n"
"f();\n"
"f();");
CompileRun(source);
v8::debug::Coverage coverage = v8::debug::Coverage::Collect(isolate);
CHECK_EQ(1u, coverage.ScriptCount());
v8::Local<v8::debug::Script> script = coverage.GetScript(0);
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());
}
......@@ -45,7 +45,7 @@ f();
`
[[function f() {}](f:2)
f();
f();](anonymous:1)
f();](:1)
`
);
......@@ -59,7 +59,7 @@ f();
`
[var f = [() => 1](f:2);
f();
f();](anonymous:1)
f();](:1)
`
);
......@@ -81,7 +81,7 @@ f();
g();
}](f:2)
f();
f();](anonymous:1)
f();](:1)
`
);
......@@ -100,6 +100,6 @@ fib(5);
if (x < 2) return 1;
return fib(x-1) + fib(x-2);
}](fib:15)
fib(5);](anonymous:1)
fib(5);](:1)
`
);
......@@ -70,7 +70,7 @@ TestCoverage(
(function f() {})();
`,
`
[([function f() {}](f:1))();](anonymous:1)
[([function f() {}](f:1))();](:1)
`
);
......@@ -86,7 +86,7 @@ for (var i = 0; i < 10; i++) {
[for (var i = 0; i < 10; i++) {
let f = [() => 1](f:5);
i += f();
}](anonymous:1)
}](:1)
`
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment