mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 19:25:43 +00:00
Bug 1204554 part 2 - Split LCov functions to make the aggregation of results incremental. r=terrence,bhackett
This commit is contained in:
parent
070ec8e3ee
commit
848c5c4b1c
@ -37,6 +37,7 @@
|
||||
#include "frontend/BytecodeCompiler.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "vm/CodeCoverage.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/ScopeObject.h"
|
||||
#include "vm/Shape.h"
|
||||
@ -1944,160 +1945,6 @@ js::GetPCCountScriptContents(JSContext* cx, size_t index)
|
||||
return buf.finishString();
|
||||
}
|
||||
|
||||
static bool
|
||||
LcovWriteScriptName(GenericPrinter& out, JSScript* script)
|
||||
{
|
||||
JSFunction* fun = script->functionNonDelazifying();
|
||||
if (fun && fun->displayAtom())
|
||||
return EscapedStringPrinter(out, fun->displayAtom(), 0);
|
||||
out.printf("top-level");
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LcovSourceFile
|
||||
{
|
||||
const char* filename;
|
||||
|
||||
LSprinter outFN;
|
||||
LSprinter outFNDA;
|
||||
size_t numFunctionsFound;
|
||||
size_t numFunctionsHit;
|
||||
|
||||
LSprinter outBRDA;
|
||||
size_t numBranchesFound;
|
||||
size_t numBranchesHit;
|
||||
|
||||
LSprinter outDA;
|
||||
size_t numLinesInstrumented;
|
||||
size_t numLinesHit;
|
||||
|
||||
LcovSourceFile(LifoAlloc* alloc, JSScript *script)
|
||||
: filename(script->filename()),
|
||||
outFN(alloc),
|
||||
outFNDA(alloc),
|
||||
numFunctionsFound(0),
|
||||
numFunctionsHit(0),
|
||||
outBRDA(alloc),
|
||||
numBranchesFound(0),
|
||||
numBranchesHit(0),
|
||||
outDA(alloc),
|
||||
numLinesInstrumented(0),
|
||||
numLinesHit(0)
|
||||
{ }
|
||||
};
|
||||
|
||||
static bool
|
||||
LcovWriteScript(LcovSourceFile& lsf, JSScript* script)
|
||||
{
|
||||
lsf.numFunctionsFound++;
|
||||
lsf.outFN.printf("FN:%d,", script->lineno());
|
||||
if (!LcovWriteScriptName(lsf.outFN, script))
|
||||
return false;
|
||||
lsf.outFN.put("\n", 1);
|
||||
|
||||
uint64_t hits = 0;
|
||||
ScriptCounts* sc = nullptr;
|
||||
if (script->hasScriptCounts()) {
|
||||
sc = &script->getScriptCounts();
|
||||
lsf.numFunctionsHit++;
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
|
||||
lsf.outFNDA.printf("FNDA:%" PRIu64 ",", counts->numExec());
|
||||
if (!LcovWriteScriptName(lsf.outFNDA, script))
|
||||
return false;
|
||||
lsf.outFNDA.put("\n", 1);
|
||||
|
||||
// Set the hit count of the pre-main code to 1, if the function ever got
|
||||
// visited.
|
||||
hits = 1;
|
||||
}
|
||||
|
||||
jsbytecode* snpc = script->code();
|
||||
jssrcnote* sn = script->notes();
|
||||
if (!SN_IS_TERMINATOR(sn))
|
||||
snpc += SN_DELTA(sn);
|
||||
|
||||
size_t lineno = script->lineno();
|
||||
jsbytecode* end = script->codeEnd();
|
||||
size_t blockId = 0;
|
||||
for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
|
||||
JSOp op = JSOp(*pc);
|
||||
bool jump = IsJumpOpcode(op);
|
||||
bool fallsthrough = BytecodeFallsThrough(op);
|
||||
|
||||
// If the current script & pc has a hit-count report, then update the
|
||||
// current number of hits.
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
|
||||
if (counts)
|
||||
hits = counts->numExec();
|
||||
}
|
||||
|
||||
// If we have additional source notes, walk all the source notes of the
|
||||
// current pc.
|
||||
if (snpc <= pc) {
|
||||
size_t oldLine = lineno;
|
||||
while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
|
||||
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
|
||||
if (type == SRC_SETLINE)
|
||||
lineno = size_t(GetSrcNoteOffset(sn, 0));
|
||||
else if (type == SRC_NEWLINE)
|
||||
lineno++;
|
||||
|
||||
sn = SN_NEXT(sn);
|
||||
snpc += SN_DELTA(sn);
|
||||
}
|
||||
|
||||
if (oldLine != lineno && fallsthrough) {
|
||||
lsf.outDA.printf("DA:%d,%" PRIu64 "\n", lineno, hits);
|
||||
|
||||
// Count the number of lines instrumented & hit.
|
||||
lsf.numLinesInstrumented++;
|
||||
if (hits)
|
||||
lsf.numLinesHit++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the current instruction has thrown, then decrement the hit counts
|
||||
// with the number of throws.
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
|
||||
if (counts)
|
||||
hits -= counts->numExec();
|
||||
}
|
||||
|
||||
// If the current pc corresponds to a conditional jump instruction, then reports
|
||||
// branch hits.
|
||||
if (jump && fallsthrough) {
|
||||
jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
|
||||
jsbytecode* fallthroughTarget = GetNextPc(pc);
|
||||
uint64_t fallthroughHits = 0;
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
|
||||
if (counts)
|
||||
fallthroughHits = counts->numExec();
|
||||
}
|
||||
|
||||
size_t targetId = script->pcToOffset(target);
|
||||
uint64_t taken = hits - fallthroughHits;
|
||||
lsf.outBRDA.printf("BRDA:%d,%d,%d,", lineno, blockId, targetId);
|
||||
if (hits)
|
||||
lsf.outBRDA.printf("%d\n", taken);
|
||||
else
|
||||
lsf.outBRDA.put("-\n", 2);
|
||||
|
||||
// Count the number of branches, and the number of branches hit.
|
||||
lsf.numBranchesFound++;
|
||||
if (hits)
|
||||
lsf.numBranchesHit++;
|
||||
|
||||
// Update the blockId when there is a discontinuity.
|
||||
blockId = script->pcToOffset(fallthroughTarget);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
|
||||
{
|
||||
@ -2108,17 +1955,7 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
|
||||
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
|
||||
for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
|
||||
JSScript* script = i.get<JSScript>();
|
||||
if (script->compartment() != comp)
|
||||
continue;
|
||||
|
||||
// If we evaluate some code which contains a syntax error, then we
|
||||
// might produce a JSScript which has no associated bytecode. This
|
||||
// line filters out this kind of scripts.
|
||||
if (!script->code())
|
||||
continue;
|
||||
|
||||
// Filter out any JSScript which is not the top-level of a file.
|
||||
if (script->functionNonDelazifying())
|
||||
if (script->compartment() != comp || !script->isTopLevel())
|
||||
continue;
|
||||
|
||||
if (!topScripts.append(script))
|
||||
@ -2132,47 +1969,17 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
|
||||
// Sort the information to avoid generating multiple file entries, and to
|
||||
// generate functions in the right order.
|
||||
auto lessFun = [](const JSScript* lhs, const JSScript* rhs) -> bool {
|
||||
int d = strcmp(lhs->filename(), rhs->filename());
|
||||
/*
|
||||
This should not be necessary as we are supposed to have only the
|
||||
top-level script.
|
||||
|
||||
d = (d != 0) ? d : lhs->lineno() - rhs->lineno();
|
||||
d = (d != 0) ? d : lhs->column() - rhs->column();
|
||||
*/
|
||||
return d < 0;
|
||||
return strcmp(lhs->filename(), rhs->filename()) < 0;
|
||||
};
|
||||
std::sort(topScripts.begin(), topScripts.end(), lessFun);
|
||||
|
||||
// lcov trace files are starting with an optional test case name, that we
|
||||
// recycle to be a compartment name.
|
||||
out.put("TN:");
|
||||
if (rt->compartmentNameCallback) {
|
||||
char name[1024];
|
||||
(*rt->compartmentNameCallback)(rt, comp, name, sizeof(name));
|
||||
for (char *s = name; s < name + sizeof(name) && *s; s++) {
|
||||
if (('a' <= *s && *s <= 'z') ||
|
||||
('A' <= *s && *s <= 'Z') ||
|
||||
('0' <= *s && *s <= '9'))
|
||||
{
|
||||
out.put(s, 1);
|
||||
continue;
|
||||
}
|
||||
out.printf("_%p", (void*) size_t(*s));
|
||||
}
|
||||
out.put("\n", 1);
|
||||
} else {
|
||||
out.printf("Compartment_%p%p\n", (void*) size_t('_'), comp);
|
||||
}
|
||||
|
||||
// For each source file
|
||||
LifoAlloc printerAlloc(4096);
|
||||
// Collect code coverage info for one compartment.
|
||||
coverage::LCovCompartment compCover;
|
||||
for (JSScript* topLevel: topScripts) {
|
||||
LifoAllocScope printerScope(&printerAlloc);
|
||||
LcovSourceFile lsf(&printerAlloc, topLevel);
|
||||
RootedScript topScript(cx, topLevel);
|
||||
|
||||
// We found the top-level script, visit all the functions reachable
|
||||
// from the top-level function.
|
||||
// from the top-level function, and delazify them.
|
||||
Rooted<ScriptVector> queue(cx, ScriptVector(cx));
|
||||
if (!queue.append(topLevel))
|
||||
return false;
|
||||
@ -2181,10 +1988,6 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
|
||||
do {
|
||||
script = queue.popCopy();
|
||||
|
||||
// Code the current script before pushing.
|
||||
if (!LcovWriteScript(lsf, script))
|
||||
return false;
|
||||
|
||||
// Iterate from the last to the first object in order to have
|
||||
// the functions them visited in the opposite order when popping
|
||||
// elements from the stack of remaining scripts, such that the
|
||||
@ -2211,33 +2014,10 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
|
||||
}
|
||||
} while (!queue.empty());
|
||||
|
||||
if (lsf.outFN.hadOutOfMemory() ||
|
||||
lsf.outFNDA.hadOutOfMemory() ||
|
||||
lsf.outBRDA.hadOutOfMemory() ||
|
||||
lsf.outDA.hadOutOfMemory())
|
||||
{
|
||||
out.reportOutOfMemory();
|
||||
return false;
|
||||
}
|
||||
|
||||
out.printf("SF:%s\n", lsf.filename);
|
||||
|
||||
lsf.outFN.exportInto(out);
|
||||
lsf.outFNDA.exportInto(out);
|
||||
out.printf("FNF:%d\n", lsf.numFunctionsFound);
|
||||
out.printf("FNH:%d\n", lsf.numFunctionsHit);
|
||||
|
||||
lsf.outBRDA.exportInto(out);
|
||||
out.printf("BRF:%d\n", lsf.numBranchesFound);
|
||||
out.printf("BRH:%d\n", lsf.numBranchesHit);
|
||||
|
||||
lsf.outDA.exportInto(out);
|
||||
out.printf("LF:%d\n", lsf.numLinesInstrumented);
|
||||
out.printf("LH:%d\n", lsf.numLinesHit);
|
||||
|
||||
out.put("end_of_record\n");
|
||||
compCover.collectCodeCoverageInfo(comp, topScript);
|
||||
}
|
||||
|
||||
compCover.exportInto(out);
|
||||
if (out.hadOutOfMemory())
|
||||
return false;
|
||||
return true;
|
||||
|
@ -1612,6 +1612,18 @@ class JSScript : public js::gc::TenuredCell
|
||||
/* Return whether this script was compiled for 'eval' */
|
||||
bool isForEval() { return isCachedEval() || isActiveEval(); }
|
||||
|
||||
/*
|
||||
* Return whether this script is a top-level script.
|
||||
*
|
||||
* If we evaluate some code which contains a syntax error, then we might
|
||||
* produce a JSScript which has no associated bytecode. Testing with
|
||||
* |code()| filters out this kind of scripts.
|
||||
*
|
||||
* If this script has a function associated to it, then it is not the
|
||||
* top-level of a file.
|
||||
*/
|
||||
bool isTopLevel() { return code() && !functionNonDelazifying(); }
|
||||
|
||||
/* Ensure the script has a TypeScript. */
|
||||
inline bool ensureHasTypes(JSContext* cx);
|
||||
|
||||
|
@ -288,6 +288,7 @@ UNIFIED_SOURCES += [
|
||||
'vm/ArrayBufferObject.cpp',
|
||||
'vm/CallNonGenericMethod.cpp',
|
||||
'vm/CharacterEncoding.cpp',
|
||||
'vm/CodeCoverage.cpp',
|
||||
'vm/Compression.cpp',
|
||||
'vm/DateTime.cpp',
|
||||
'vm/Debugger.cpp',
|
||||
|
365
js/src/vm/CodeCoverage.cpp
Normal file
365
js/src/vm/CodeCoverage.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "vm/CodeCoverage.h"
|
||||
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
|
||||
#include "jscompartment.h"
|
||||
#include "jsopcode.h"
|
||||
#include "jsscript.h"
|
||||
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
// This file contains a few functions which are used to produce files understood
|
||||
// by lcov tools. A detailed description of the format is available in the man
|
||||
// page for "geninfo" [1]. To make it short, the following paraphrases what is
|
||||
// commented in the man page by using curly braces prefixed by for-each to
|
||||
// express repeated patterns.
|
||||
//
|
||||
// TN:<compartment name>
|
||||
// for-each <source file> {
|
||||
// SN:<filename>
|
||||
// for-each <script> {
|
||||
// FN:<line>,<name>
|
||||
// }
|
||||
// for-each <script> {
|
||||
// FNDA:<hits>,<name>
|
||||
// }
|
||||
// FNF:<number of scripts>
|
||||
// FNH:<sum of scripts hits>
|
||||
// for-each <script> {
|
||||
// for-each <branch> {
|
||||
// BRDA:<line>,<block id>,<target id>,<taken>
|
||||
// }
|
||||
// }
|
||||
// BRF:<number of branches>
|
||||
// BRH:<sum of branches hits>
|
||||
// for-each <script> {
|
||||
// for-each <line> {
|
||||
// DA:<line>,<hits>
|
||||
// }
|
||||
// }
|
||||
// LF:<number of lines>
|
||||
// LH:<sum of lines hits>
|
||||
// }
|
||||
//
|
||||
// [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
|
||||
//
|
||||
namespace js {
|
||||
namespace coverage {
|
||||
|
||||
LCovSource::LCovSource(LifoAlloc* alloc)
|
||||
: outSF_(alloc),
|
||||
outFN_(alloc),
|
||||
outFNDA_(alloc),
|
||||
numFunctionsFound_(0),
|
||||
numFunctionsHit_(0),
|
||||
outBRDA_(alloc),
|
||||
numBranchesFound_(0),
|
||||
numBranchesHit_(0),
|
||||
outDA_(alloc),
|
||||
numLinesInstrumented_(0),
|
||||
numLinesHit_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
LCovSource::exportInto(GenericPrinter& out) const
|
||||
{
|
||||
outSF_.exportInto(out);
|
||||
|
||||
outFN_.exportInto(out);
|
||||
outFNDA_.exportInto(out);
|
||||
out.printf("FNF:%d\n", numFunctionsFound_);
|
||||
out.printf("FNH:%d\n", numFunctionsHit_);
|
||||
|
||||
outBRDA_.exportInto(out);
|
||||
out.printf("BRF:%d\n", numBranchesFound_);
|
||||
out.printf("BRH:%d\n", numBranchesHit_);
|
||||
|
||||
outDA_.exportInto(out);
|
||||
out.printf("LF:%d\n", numLinesInstrumented_);
|
||||
out.printf("LH:%d\n", numLinesHit_);
|
||||
|
||||
out.put("end_of_record\n");
|
||||
}
|
||||
|
||||
bool
|
||||
LCovSource::writeTopLevelScript(JSScript* script)
|
||||
{
|
||||
MOZ_ASSERT(script->isTopLevel());
|
||||
|
||||
if (!writeSourceFilename(outSF_, script))
|
||||
return false;
|
||||
|
||||
Vector<JSScript*, 8, SystemAllocPolicy> queue;
|
||||
if (!queue.append(script))
|
||||
return false;
|
||||
|
||||
do {
|
||||
script = queue.popCopy();
|
||||
|
||||
// Save the lcov output of the current script.
|
||||
if (!writeScript(script))
|
||||
return false;
|
||||
|
||||
// Iterate from the last to the first object in order to have
|
||||
// the functions them visited in the opposite order when popping
|
||||
// elements from the stack of remaining scripts, such that the
|
||||
// functions are listed with increasing line numbers.
|
||||
if (!script->hasObjects())
|
||||
continue;
|
||||
size_t idx = script->objects()->length;
|
||||
while (idx--) {
|
||||
JSObject* obj = script->getObject(idx);
|
||||
|
||||
// Only continue on JSFunction objects.
|
||||
if (!obj->is<JSFunction>())
|
||||
continue;
|
||||
JSFunction& fun = obj->as<JSFunction>();
|
||||
|
||||
// Let's skip asm.js for now.
|
||||
if (!fun.isInterpreted())
|
||||
continue;
|
||||
MOZ_ASSERT(!fun.isInterpretedLazy());
|
||||
|
||||
// Queue the script in the list of script associated to the
|
||||
// current source.
|
||||
if (!queue.append(fun.nonLazyScript()))
|
||||
return false;
|
||||
}
|
||||
} while (!queue.empty());
|
||||
|
||||
return !(outFN_.hadOutOfMemory() ||
|
||||
outFNDA_.hadOutOfMemory() ||
|
||||
outBRDA_.hadOutOfMemory() ||
|
||||
outDA_.hadOutOfMemory());
|
||||
}
|
||||
|
||||
bool
|
||||
LCovSource::writeSourceFilename(LSprinter& out, JSScript* script)
|
||||
{
|
||||
out.printf("SF:%s\n", script->filename());
|
||||
return !out.hadOutOfMemory();
|
||||
}
|
||||
|
||||
bool
|
||||
LCovSource::writeScriptName(LSprinter& out, JSScript* script)
|
||||
{
|
||||
JSFunction* fun = script->functionNonDelazifying();
|
||||
if (fun && fun->displayAtom())
|
||||
return EscapedStringPrinter(out, fun->displayAtom(), 0);
|
||||
out.printf("top-level");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
LCovSource::writeScript(JSScript* script)
|
||||
{
|
||||
numFunctionsFound_++;
|
||||
outFN_.printf("FN:%d,", script->lineno());
|
||||
if (!writeScriptName(outFN_, script))
|
||||
return false;
|
||||
outFN_.put("\n", 1);
|
||||
|
||||
uint64_t hits = 0;
|
||||
ScriptCounts* sc = nullptr;
|
||||
if (script->hasScriptCounts()) {
|
||||
sc = &script->getScriptCounts();
|
||||
numFunctionsHit_++;
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
|
||||
outFNDA_.printf("FNDA:%" PRIu64 ",", counts->numExec());
|
||||
if (!writeScriptName(outFNDA_, script))
|
||||
return false;
|
||||
outFNDA_.put("\n", 1);
|
||||
|
||||
// Set the hit count of the pre-main code to 1, if the function ever got
|
||||
// visited.
|
||||
hits = 1;
|
||||
}
|
||||
|
||||
jsbytecode* snpc = script->code();
|
||||
jssrcnote* sn = script->notes();
|
||||
if (!SN_IS_TERMINATOR(sn))
|
||||
snpc += SN_DELTA(sn);
|
||||
|
||||
size_t lineno = script->lineno();
|
||||
jsbytecode* end = script->codeEnd();
|
||||
size_t blockId = 0;
|
||||
for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
|
||||
JSOp op = JSOp(*pc);
|
||||
bool jump = IsJumpOpcode(op);
|
||||
bool fallsthrough = BytecodeFallsThrough(op);
|
||||
|
||||
// If the current script & pc has a hit-count report, then update the
|
||||
// current number of hits.
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
|
||||
if (counts)
|
||||
hits = counts->numExec();
|
||||
}
|
||||
|
||||
// If we have additional source notes, walk all the source notes of the
|
||||
// current pc.
|
||||
if (snpc <= pc) {
|
||||
size_t oldLine = lineno;
|
||||
while (!SN_IS_TERMINATOR(sn) && snpc <= pc) {
|
||||
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
|
||||
if (type == SRC_SETLINE)
|
||||
lineno = size_t(GetSrcNoteOffset(sn, 0));
|
||||
else if (type == SRC_NEWLINE)
|
||||
lineno++;
|
||||
|
||||
sn = SN_NEXT(sn);
|
||||
snpc += SN_DELTA(sn);
|
||||
}
|
||||
|
||||
if (oldLine != lineno && fallsthrough) {
|
||||
outDA_.printf("DA:%d,%" PRIu64 "\n", lineno, hits);
|
||||
|
||||
// Count the number of lines instrumented & hit.
|
||||
numLinesInstrumented_++;
|
||||
if (hits)
|
||||
numLinesHit_++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the current instruction has thrown, then decrement the hit counts
|
||||
// with the number of throws.
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
|
||||
if (counts)
|
||||
hits -= counts->numExec();
|
||||
}
|
||||
|
||||
// If the current pc corresponds to a conditional jump instruction, then reports
|
||||
// branch hits.
|
||||
if (jump && fallsthrough) {
|
||||
jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
|
||||
jsbytecode* fallthroughTarget = GetNextPc(pc);
|
||||
uint64_t fallthroughHits = 0;
|
||||
if (sc) {
|
||||
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
|
||||
if (counts)
|
||||
fallthroughHits = counts->numExec();
|
||||
}
|
||||
|
||||
size_t targetId = script->pcToOffset(target);
|
||||
uint64_t taken = hits - fallthroughHits;
|
||||
outBRDA_.printf("BRDA:%d,%d,%d,", lineno, blockId, targetId);
|
||||
if (hits)
|
||||
outBRDA_.printf("%d\n", taken);
|
||||
else
|
||||
outBRDA_.put("-\n", 2);
|
||||
|
||||
// Count the number of branches, and the number of branches hit.
|
||||
numBranchesFound_++;
|
||||
if (hits)
|
||||
numBranchesHit_++;
|
||||
|
||||
// Update the blockId when there is a discontinuity.
|
||||
blockId = script->pcToOffset(fallthroughTarget);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LCovCompartment::LCovCompartment()
|
||||
: alloc_(4096),
|
||||
outTN_(&alloc_),
|
||||
sources_(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(alloc_.isEmpty());
|
||||
}
|
||||
|
||||
void
|
||||
LCovCompartment::collectCodeCoverageInfo(JSCompartment* comp, JSScript* topLevel)
|
||||
{
|
||||
// Skip any operation if we already some out-of memory issues.
|
||||
if (outTN_.hadOutOfMemory())
|
||||
return;
|
||||
|
||||
// On the first call, write the compartment name, and allocate a LCovSource
|
||||
// vector in the LifoAlloc.
|
||||
if (!sources_) {
|
||||
if (!writeCompartmentName(comp))
|
||||
return;
|
||||
|
||||
LCovSourceVector* raw = alloc_.pod_malloc<LCovSourceVector>();
|
||||
if (!raw) {
|
||||
outTN_.reportOutOfMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
sources_ = new(raw) LCovSourceVector(alloc_);
|
||||
}
|
||||
|
||||
// Allocate a new LCovSource for the current top-level.
|
||||
if (!sources_->append(Move(LCovSource(&alloc_)))) {
|
||||
outTN_.reportOutOfMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write code coverage data into the allocated LCovSource.
|
||||
if (!sources_->back().writeTopLevelScript(topLevel)) {
|
||||
outTN_.reportOutOfMemory();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LCovCompartment::exportInto(GenericPrinter& out) const
|
||||
{
|
||||
if (!sources_ || outTN_.hadOutOfMemory())
|
||||
return;
|
||||
|
||||
outTN_.exportInto(out);
|
||||
for (const LCovSource& sc : *sources_)
|
||||
sc.exportInto(out);
|
||||
}
|
||||
|
||||
bool
|
||||
LCovCompartment::writeCompartmentName(JSCompartment* comp)
|
||||
{
|
||||
JSRuntime* rt = comp->runtimeFromMainThread();
|
||||
|
||||
// lcov trace files are starting with an optional test case name, that we
|
||||
// recycle to be a compartment name.
|
||||
//
|
||||
// Note: The test case name has some constraint in terms of valid character,
|
||||
// thus we escape invalid chracters with a "_" symbol in front of its
|
||||
// hexadecimal code.
|
||||
outTN_.put("TN:");
|
||||
if (rt->compartmentNameCallback) {
|
||||
char name[1024];
|
||||
{
|
||||
// Hazard analysis cannot tell that the callback does not GC.
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
(*rt->compartmentNameCallback)(rt, comp, name, sizeof(name));
|
||||
}
|
||||
for (char *s = name; s < name + sizeof(name) && *s; s++) {
|
||||
if (('a' <= *s && *s <= 'z') ||
|
||||
('A' <= *s && *s <= 'Z') ||
|
||||
('0' <= *s && *s <= '9'))
|
||||
{
|
||||
outTN_.put(s, 1);
|
||||
continue;
|
||||
}
|
||||
outTN_.printf("_%p", (void*) size_t(*s));
|
||||
}
|
||||
outTN_.put("\n", 1);
|
||||
} else {
|
||||
outTN_.printf("Compartment_%p%p\n", (void*) size_t('_'), comp);
|
||||
}
|
||||
|
||||
return !outTN_.hadOutOfMemory();
|
||||
}
|
||||
|
||||
|
||||
} // namespace coverage
|
||||
} // namespace js
|
108
js/src/vm/CodeCoverage.h
Normal file
108
js/src/vm/CodeCoverage.h
Normal file
@ -0,0 +1,108 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef vm_CodeCoverage_h
|
||||
#define vm_CodeCoverage_h
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "ds/LifoAlloc.h"
|
||||
|
||||
#include "vm/Printer.h"
|
||||
|
||||
struct JSCompartment;
|
||||
class JSScript;
|
||||
|
||||
namespace js {
|
||||
namespace coverage {
|
||||
|
||||
class LCovCompartment;
|
||||
|
||||
class LCovSource
|
||||
{
|
||||
public:
|
||||
explicit LCovSource(LifoAlloc* alloc);
|
||||
|
||||
// Visit all JSScript in pre-order, and collect the lcov output based on
|
||||
// the ScriptCounts counters.
|
||||
//
|
||||
// In case of the where this function is called during the finalization,
|
||||
// this assumes that all of the children scripts are still alive, and
|
||||
// not finalized yet.
|
||||
bool writeTopLevelScript(JSScript* script);
|
||||
|
||||
// Write the Lcov output in a buffer, such as the one associated with
|
||||
// the runtime code coverage trace file.
|
||||
void exportInto(GenericPrinter& out) const;
|
||||
|
||||
private:
|
||||
// Write the script name in out.
|
||||
bool writeSourceFilename(LSprinter& out, JSScript* script);
|
||||
|
||||
// Write the script name in out.
|
||||
bool writeScriptName(LSprinter& out, JSScript* script);
|
||||
|
||||
// Iterate over the bytecode and collect the lcov output based on the
|
||||
// ScriptCounts counters.
|
||||
bool writeScript(JSScript* script);
|
||||
|
||||
private:
|
||||
// LifoAlloc string which hold the filename of the source.
|
||||
LSprinter outSF_;
|
||||
|
||||
// LifoAlloc strings which hold the filename of each function as
|
||||
// well as the number of hits for each function.
|
||||
LSprinter outFN_;
|
||||
LSprinter outFNDA_;
|
||||
size_t numFunctionsFound_;
|
||||
size_t numFunctionsHit_;
|
||||
|
||||
// LifoAlloc string which hold branches statistics.
|
||||
LSprinter outBRDA_;
|
||||
size_t numBranchesFound_;
|
||||
size_t numBranchesHit_;
|
||||
|
||||
// LifoAlloc string which hold lines statistics.
|
||||
LSprinter outDA_;
|
||||
size_t numLinesInstrumented_;
|
||||
size_t numLinesHit_;
|
||||
};
|
||||
|
||||
class LCovCompartment
|
||||
{
|
||||
public:
|
||||
LCovCompartment();
|
||||
|
||||
// Collect code coverage information
|
||||
void collectCodeCoverageInfo(JSCompartment* comp, JSScript* topLevel);
|
||||
|
||||
// Write the Lcov output in a buffer, such as the one associated with
|
||||
// the runtime code coverage trace file.
|
||||
void exportInto(GenericPrinter& out) const;
|
||||
|
||||
private:
|
||||
// Write the script name in out.
|
||||
bool writeCompartmentName(JSCompartment* comp);
|
||||
|
||||
private:
|
||||
typedef Vector<LCovSource, 16, LifoAllocPolicy<Fallible>> LCovSourceVector;
|
||||
|
||||
// LifoAlloc backend for all temporary allocations needed to stash the
|
||||
// strings to be written in the file.
|
||||
LifoAlloc alloc_;
|
||||
|
||||
// LifoAlloc string which hold the name of the compartment.
|
||||
LSprinter outTN_;
|
||||
|
||||
// Vector of all sources which are used in this compartment.
|
||||
LCovSourceVector* sources_;
|
||||
};
|
||||
|
||||
} // namespace coverage
|
||||
} // namespace js
|
||||
|
||||
#endif // vm_Printer_h
|
||||
|
Loading…
Reference in New Issue
Block a user