Commit 20c7d543 authored by yangguo's avatar yangguo Committed by Commit bot

[debugger] add lcov support to d8.

Use d8 with --lcov=<file> to append coverage info to a file.
The result can be fed into genhtml(1).

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

Review-Url: https://codereview.chromium.org/2695823003
Cr-Commit-Position: refs/heads/master@{#43207}
parent 65f0e958
......@@ -30,6 +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/interpreter/interpreter.h"
#include "src/list-inl.h"
#include "src/msan.h"
......@@ -1654,6 +1655,70 @@ void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
JSON::Stringify(context, dispatch_counters).ToLocalChecked());
}
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);
// 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);
// Invocation counts for non-boundary lines are overwritten.
for (int i = start.line + 1; i < end.line; 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;
} 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;
}
// Recurse over inner ranges.
for (const auto& inner : range->inner) ReadRange(s, lines, script, &inner);
}
} // 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);
std::vector<i::Coverage::ScriptData> data =
i::Coverage::Collect(reinterpret_cast<i::Isolate*>(isolate));
std::ofstream sink(file, std::ofstream::app);
for (const auto& script_data : data) {
i::Handle<i::Script> script = script_data.script;
// 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()))));
// 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);
// 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;
}
sink << "end_of_record" << std::endl;
}
}
void Shell::OnExit(v8::Isolate* isolate) {
if (i::FLAG_dump_counters || i::FLAG_dump_counters_nvp) {
......@@ -1714,7 +1779,6 @@ void Shell::OnExit(v8::Isolate* isolate) {
}
static FILE* FOpen(const char* path, const char* mode) {
#if defined(_MSC_VER) && (defined(_WIN32) || defined(_WIN64))
FILE* result;
......@@ -2396,6 +2460,9 @@ bool Shell::SetOptions(int argc, char* argv[]) {
} else if (strcmp(argv[i], "--enable-inspector") == 0) {
options.enable_inspector = true;
argv[i] = NULL;
} else if (strncmp(argv[i], "--lcov=", 7) == 0) {
options.lcov_file = argv[i] + 7;
argv[i] = NULL;
}
}
......@@ -2437,6 +2504,9 @@ 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));
}
HandleScope scope(isolate);
Local<Context> context = CreateEvaluationContext(isolate);
if (last_run && options.use_interactive_shell()) {
......@@ -2450,6 +2520,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[], bool last_run) {
options.isolate_sources[0].Execute(isolate);
}
DisposeModuleEmbedderData(context);
WriteLcovData(isolate, options.lcov_file);
}
CollectGarbage(isolate);
for (int i = 1; i < options.num_isolates; ++i) {
......
......@@ -304,7 +304,8 @@ class ShellOptions {
natives_blob(NULL),
snapshot_blob(NULL),
trace_enabled(false),
trace_config(NULL) {}
trace_config(NULL),
lcov_file(NULL) {}
~ShellOptions() {
delete[] isolate_sources;
......@@ -335,6 +336,7 @@ class ShellOptions {
const char* snapshot_blob;
bool trace_enabled;
const char* trace_config;
const char* lcov_file;
};
class Shell : public i::AllStatic {
......@@ -464,6 +466,8 @@ class Shell : public i::AllStatic {
static std::vector<ExternalizedContents> externalized_contents_;
static void WriteIgnitionDispatchCountersFile(v8::Isolate* isolate);
// Append LCOV coverage data to file.
static void WriteLcovData(v8::Isolate* isolate, const char* file);
static Counter* GetCounter(const char* name, bool is_histogram);
static Local<String> Stringify(Isolate* isolate, Local<Value> value);
static void Initialize(Isolate* isolate);
......
......@@ -43,9 +43,25 @@ class SharedToCounterMap
DisallowHeapAllocation no_gc;
};
namespace {
int StartPosition(SharedFunctionInfo* info) {
int start = info->function_token_position();
if (start == kNoSourcePosition) start = info->start_position();
return start;
}
bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
int a_start = StartPosition(a);
int b_start = StartPosition(b);
if (a_start == b_start) return a->end_position() > b->end_position();
return a_start < b_start;
}
} // anonymous namespace
std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
SharedToCounterMap counter_map;
// Feed invocation count into the counter map.
if (isolate->IsCodeCoverageEnabled()) {
// Feedback vectors are already listed to prevent losing them to GC.
Handle<ArrayList> list =
......@@ -61,9 +77,6 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// Iterate the heap to find all feedback vectors and accumulate the
// invocation counts into the map for each shared function info.
HeapIterator heap_iterator(isolate->heap());
// Initializing the heap iterator might have triggered a GC, which
// invalidates entries in the counter_map.
DCHECK_EQ(0, counter_map.occupancy());
while (HeapObject* current_obj = heap_iterator.next()) {
if (!current_obj->IsFeedbackVector()) continue;
FeedbackVector* vector = FeedbackVector::cast(current_obj);
......@@ -74,11 +87,6 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
}
}
// Make sure entries in the counter map is not invalidated by GC.
DisallowHeapAllocation no_gc;
std::vector<Range*> stack;
// Iterate shared function infos of every script and build a mapping
// between source ranges and invocation counts.
std::vector<Coverage::ScriptData> result;
......@@ -86,17 +94,29 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
while (Script* script = scripts.Next()) {
// Dismiss non-user scripts.
if (script->type() != Script::TYPE_NORMAL) continue;
DCHECK(stack.empty());
// Create and add new script data.
int source_length = String::cast(script->source())->length();
result.emplace_back(Handle<Script>(script, isolate), source_length);
std::vector<SharedFunctionInfo*> sorted;
{
// 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));
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);
// Iterate through the list of shared function infos, reconstruct the
// nesting, and compute the ranges covering different invocation counts.
HandleScope scope(isolate);
SharedFunctionInfo::ScriptIterator infos(Handle<Script>(script, isolate));
while (SharedFunctionInfo* info = infos.Next()) {
int start = info->function_token_position();
if (start == kNoSourcePosition) start = info->start_position();
// 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()) {
......@@ -109,7 +129,7 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
// 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();
while (start >= stack.back()->end) stack.pop_back();
Range* outer = stack.back();
// New nested function.
DCHECK_LE(end, outer->end);
......@@ -121,7 +141,6 @@ std::vector<Coverage::ScriptData> Coverage::Collect(Isolate* isolate) {
stack.push_back(&nested);
}
}
stack.clear();
}
return result;
}
......
......@@ -36,12 +36,12 @@ class Coverage : public AllStatic {
Range toplevel;
};
static std::vector<ScriptData> Collect(Isolate* isolate);
V8_EXPORT_PRIVATE static std::vector<ScriptData> Collect(Isolate* isolate);
// Enable precise code coverage. This disables optimization and makes sure
// invocation count is not affected by GC.
static void EnablePrecise(Isolate* isolate);
static void DisablePrecise(Isolate* isolate);
V8_EXPORT_PRIVATE static void EnablePrecise(Isolate* isolate);
V8_EXPORT_PRIVATE static void DisablePrecise(Isolate* isolate);
};
} // namespace internal
......
......@@ -6694,8 +6694,10 @@ class Script: public Struct {
// initializes the line ends array, avoiding expensive recomputations.
// The non-static version is not allocating and safe for unhandlified
// callsites.
static bool GetPositionInfo(Handle<Script> script, int position,
PositionInfo* info, OffsetFlag offset_flag);
V8_EXPORT_PRIVATE static bool GetPositionInfo(Handle<Script> script,
int position,
PositionInfo* info,
OffsetFlag offset_flag);
bool GetPositionInfo(int position, PositionInfo* info,
OffsetFlag offset_flag) const;
......
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