Bug 1176880 part 2 - Add Debugger.Script.getOffsetsCoverage. r=shu

--HG--
rename : js/src/jit-test/tests/coverage/simple.js => js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
This commit is contained in:
Nicolas B. Pierron 2015-09-16 21:11:35 +02:00
Родитель b2aebab5f5
Коммит e9e0015186
3 изменённых файлов: 388 добавлений и 0 удалений

Просмотреть файл

@ -11,6 +11,15 @@ if (getJitCompilerOptions()["ion.warmup.trigger"] != 30)
if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10)
setJitCompilerOption("baseline.warmup.trigger", 10);
/*
* These test cases are annotated with the output produced by LCOV [1]. Comment
* starting with //<key> without any spaces are used as a reference for the code
* coverage output. Any "$" in these line comments are replaced by the current
* line number, and any "%" are replaced with the current function name (defined
* by the FN key).
*
* [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
*/
function checkLcov(fun) {
var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ];
function startsWithKey(s) {
@ -213,3 +222,6 @@ checkLcov(function () { //FN:$,top-level //FNDA:1,%
//BRF:0
//BRH:0
});
// If you add a test case here, do the same in
// jit-test/tests/debug/Script-getOffsetsCoverage-01.js

Просмотреть файл

@ -0,0 +1,294 @@
// Currently the Jit integration has a few issues, let's keep this test
// case deterministic.
//
// - Baseline OSR increments the loop header twice.
// - Ion is not updating any counter yet.
//
if (getJitCompilerOptions()["ion.warmup.trigger"] != 30)
setJitCompilerOption("ion.warmup.trigger", 30);
if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10)
setJitCompilerOption("baseline.warmup.trigger", 10);
/*
* These test cases are annotated with the output produced by LCOV [1]. Comment
* starting with //<key> without any spaces are used as a reference for the code
* coverage output. Any "$" in these line comments are replaced by the current
* line number, and any "%" are replaced with the current function name (defined
* by the FN key).
*
* [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
*/
function checkGetOffsetsCoverage(fun) {
var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ];
function startsWithKey(s) {
for (k of keys) {
if (s.startsWith(k))
return true;
}
return false;
};
// Extract the body of the function, as the code to be executed.
var source = fun.toSource();
source = source.slice(source.indexOf('{') + 1, source.lastIndexOf('}'));
// Extract comment starting with the previous keys, as a reference.
var lcovRef = [];
var currLine = 0;
var currFun = [{name: "top-level", braces: 1}];
for (var line of source.split('\n')) {
currLine++;
for (var comment of line.split("//").slice(1)) {
if (!startsWithKey(comment))
continue;
comment = comment.trim();
if (comment.startsWith("FN:"))
currFun.push({ name: comment.split(',')[1], braces: 0 });
var name = currFun[currFun.length - 1].name;
if (!comment.startsWith("DA:"))
continue;
comment = {
offset: null,
lineNumber: currLine,
columnNumber: null,
count: comment.split(",")[1] | 0,
script: (name == "top-level" ? undefined : name)
};
lcovRef.push(comment);
}
var deltaBraces = line.split('{').length - line.split('}').length;
currFun[currFun.length - 1].braces += deltaBraces;
if (currFun[currFun.length - 1].braces == 0)
currFun.pop();
}
// Create a new global and instrument it with a debugger, to find all scripts,
// created in the current global.
var g = newGlobal();
var dbg = Debugger(g);
dbg.collectCoverageInfo = true;
var topLevel = null;
dbg.onNewScript = function (s) {
topLevel = s;
dbg.onNewScript = function () {};
};
// Evaluate the code, and collect the hit counts for each scripts / lines.
g.eval(source);
var coverageRes = [];
function collectCoverage(s) {
var res = s.getOffsetsCoverage();
if (res == null)
res = [{
offset: null,
lineNumber: null,
columnNumber: null,
script: s.displayName,
count: 0
}];
else {
res.map(function (e) {
e.script = s.displayName;
return e;
});
}
coverageRes.push(res);
s.getChildScripts().forEach(collectCoverage);
};
collectCoverage(topLevel);
coverageRes = [].concat(...coverageRes);
// Check that all the lines are present the result.
function match(ref) {
return function (entry) {
return ref.lineNumber == entry.lineNumber && ref.script == entry.script;
}
}
function ppObj(entry) {
var str = "{";
for (var k in entry) {
if (entry[k] != null)
str += " '" + k + "': " + entry[k] + ",";
}
str += "}";
return str;
}
for (ref of lcovRef) {
var res = coverageRes.find(match(ref));
if (!res) {
// getOffsetsCoverage returns null if we have no result for the
// script. We added a fake entry with an undefined lineNumber, which is
// used to match against the modified reference.
var missRef = Object.create(ref);
missRef.lineNumber = null;
res = coverageRes.find(match(missRef));
}
if (!res || res.count != ref.count) {
print("Cannot find `" + ppObj(ref) + "` in the following results:\n", coverageRes.map(ppObj).join("\n"));
print("In the following source:\n", source);
assertEq(true, false);
}
}
}
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
",".split(','); //DA:$,1
//FNF:1
//FNH:1
//LF:1
//LH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
function f() { //FN:$,f
",".split(','); //DA:$,0
}
",".split(','); //DA:$,1
//FNF:2
//FNH:1
//LF:2
//LH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
function f() { //FN:$,f //FNDA:1,%
",".split(','); //DA:$,1
}
f(); //DA:$,1
//FNF:2
//FNH:2
//LF:2
//LH:2
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
var l = ",".split(','); //DA:$,1
if (l.length == 3) //DA:$,1
l.push(''); //DA:$,0
else
l.pop(); //DA:$,1
//FNF:1
//FNH:1
//LF:4
//LH:3
//BRF:1
//BRH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
var l = ",".split(','); //DA:$,1
if (l.length == 2) //DA:$,1
l.push(''); //DA:$,1
else
l.pop(); //DA:$,0
//FNF:1
//FNH:1
//LF:4
//LH:3
//BRF:1
//BRH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
var l = ",".split(','); //DA:$,1
if (l.length == 2) //DA:$,1
l.push(''); //DA:$,1
else {
if (l.length == 1) //DA:$,0
l.pop(); //DA:$,0
}
//FNF:1
//FNH:1
//LF:5
//LH:3
//BRF:2
//BRH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
function f(i) { //FN:$,f //FNDA:2,%
var x = 0; //DA:$,2
while (i--) { // Currently OSR wrongly count the loop header twice.
// So instead of DA:$,12 , we have DA:$,13 .
x += i; //DA:$,10
x = x / 2; //DA:$,10
}
return x; //DA:$,2
//BRF:1
//BRH:1
}
f(5); //DA:$,1
f(5); //DA:$,1
//FNF:2
//FNH:2
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
try { //DA:$,1
var l = ",".split(','); //DA:$,1
if (l.length == 2) { //DA:$,1 // BRDA:$,0
l.push(''); //DA:$,1
throw l; //DA:$,1
}
l.pop(); //DA:$,0
} catch (x) { //DA:$,1
x.pop(); //DA:$,1
}
//FNF:1
//FNH:1
//LF:9 // Expected LF:8 , Apparently if the first statement is a try, the
// statement following the "try{" statement is visited twice.
//LH:8 // Expected LH:7
//BRF:1
//BRH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
var l = ",".split(','); //DA:$,1
try { //DA:$,1
try { //DA:$,1
if (l.length == 2) { //DA:$,1 // BRDA:$,0
l.push(''); //DA:$,1
throw l; //DA:$,1
}
l.pop(); //DA:$,0 // BRDA:$,-
} finally { //DA:$,1
l.pop(); //DA:$,1
}
} catch (x) { //DA:$,1
}
//FNF:1
//FNH:1
//LF:10
//LH:9
//BRF:2
//BRH:1
});
checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
function f() { //FN:$,f //FNDA:1,%
throw 1; //DA:$,1
f(); //DA:$,0
}
var l = ",".split(','); //DA:$,1
try { //DA:$,1
f(); //DA:$,1
f(); //DA:$,0
} catch (x) { //DA:$,1
}
//FNF:2
//FNH:2
//LF:7
//LH:5
//BRF:0
//BRH:0
});
// If you add a test case here, do the same in
// jit-test/tests/coverage/simple.js

Просмотреть файл

@ -5523,6 +5523,87 @@ DebuggerScript_isInCatchScope(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
DebuggerScript_getOffsetsCoverage(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetsCoverage", args, obj, script);
// If the script has no coverage information, then skip this and return null
// instead.
if (!script->hasScriptCounts()) {
args.rval().setNull();
return true;
}
ScriptCounts* sc = &script->getScriptCounts();
// If the main ever got visited, then assume that any code before main got
// visited once.
uint64_t hits = 0;
const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main()));
if (counts->numExec())
hits = 1;
// Build an array of objects which are composed of 4 properties:
// - offset PC offset of the current opcode.
// - lineNumber Line of the current opcode.
// - columnNumber Column of the current opcode.
// - count Number of times the instruction got executed.
RootedObject result(cx, NewDenseEmptyArray(cx));
if (!result)
return false;
RootedId offsetId(cx, AtomToId(cx->names().offset));
RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber));
RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber));
RootedId countId(cx, AtomToId(cx->names().count));
RootedObject item(cx);
RootedValue offsetValue(cx);
RootedValue lineNumberValue(cx);
RootedValue columnNumberValue(cx);
RootedValue countValue(cx);
// Iterate linearly over the bytecode.
for (BytecodeRangeWithPosition r(cx, script); !r.empty(); r.popFront()) {
size_t offset = r.frontOffset();
// The beginning of each non-branching sequences of instruction set the
// number of execution of the current instruction and any following
// instruction.
counts = sc->maybeGetPCCounts(offset);
if (counts)
hits = counts->numExec();
offsetValue.setNumber(double(offset));
lineNumberValue.setNumber(double(r.frontLineNumber()));
columnNumberValue.setNumber(double(r.frontColumnNumber()));
countValue.setNumber(double(hits));
// Create a new object with the offset, line number, column number, the
// number of hit counts, and append it to the array.
item = NewObjectWithGivenProto<PlainObject>(cx, nullptr);
if (!item ||
!DefineProperty(cx, item, offsetId, offsetValue) ||
!DefineProperty(cx, item, lineNumberId, lineNumberValue) ||
!DefineProperty(cx, item, columnNumberId, columnNumberValue) ||
!DefineProperty(cx, item, countId, countValue) ||
!NewbornArrayPush(cx, result, ObjectValue(*item)))
{
return false;
}
// If the current instruction has thrown, then decrement the hit counts
// with the number of throws.
counts = sc->maybeGetThrowCounts(offset);
if (counts)
hits -= counts->numExec();
}
args.rval().setObject(*result);
return true;
}
static bool
DebuggerScript_construct(JSContext* cx, unsigned argc, Value* vp)
{
@ -5554,6 +5635,7 @@ static const JSFunctionSpec DebuggerScript_methods[] = {
JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0),
JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0),
JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0),
JS_FN("getOffsetsCoverage", DebuggerScript_getOffsetsCoverage, 0, 0),
JS_FS_END
};