Skip to content

Commit

Permalink
Bug 972045 - Add a compact representation for call stacks in SpiderMo…
Browse files Browse the repository at this point in the history
…nkey. r=jimb
  • Loading branch information
Nick Fitzgerald committed Apr 24, 2014
1 parent 2935429 commit 9f0fc42
Show file tree
Hide file tree
Showing 24 changed files with 1,115 additions and 5 deletions.
3 changes: 2 additions & 1 deletion js/public/MemoryMetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,8 @@ struct CompartmentStats
macro(Other, NotLiveGCThing, compartmentObject) \
macro(Other, NotLiveGCThing, crossCompartmentWrappersTable) \
macro(Other, NotLiveGCThing, regexpCompartment) \
macro(Other, NotLiveGCThing, debuggeesSet)
macro(Other, NotLiveGCThing, debuggeesSet) \
macro(Other, NotLiveGCThing, savedStacksSet)

CompartmentStats()
: FOR_EACH_SIZE(ZERO_SIZE)
Expand Down
29 changes: 29 additions & 0 deletions js/src/builtin/TestingFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/ProxyObject.h"
#include "vm/SavedStacks.h"
#include "vm/TraceLogging.h"

#include "jscntxtinlines.h"
Expand Down Expand Up @@ -850,6 +851,25 @@ CountHeap(JSContext *cx, unsigned argc, jsval *vp)
return true;
}

static bool
GetSavedFrameCount(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setNumber(cx->compartment()->savedStacks().count());
return true;
}

static bool
SaveStack(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<SavedFrame*> frame(cx);
if (!cx->compartment()->savedStacks().saveCurrentStack(cx, &frame))
return false;
args.rval().setObject(*frame.get());
return true;
}

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
static bool
OOMAfterAllocations(JSContext *cx, unsigned argc, jsval *vp)
Expand Down Expand Up @@ -1571,6 +1591,15 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
" then you can provide an extra argument with some specific traceable\n"
" thing to count.\n"),

JS_FN_HELP("getSavedFrameCount", GetSavedFrameCount, 0, 0,
"getSavedFrameCount()",
" Return the number of SavedFrame instances stored in this compartment's\n"
" SavedStacks cache."),

JS_FN_HELP("saveStack", SaveStack, 0, 0,
"saveStack()",
" Capture a stack.\n"),

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 1, 0,
"oomAfterAllocations(count)",
Expand Down
3 changes: 3 additions & 0 deletions js/src/jit-test/tests/saved-stacks/SavedFrame-constructor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// The SavedFrame constructor shouldn't have been exposed to JS on the global.

assertEq(typeof SavedFrame, "undefined");
38 changes: 38 additions & 0 deletions js/src/jit-test/tests/saved-stacks/evals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Test that we can save stacks with direct and indirect eval calls.

const directEval = (function iife() {
return eval("(" + function evalFrame() {
return saveStack();
} + "())");
}());

assertEq(directEval.source.contains("> eval"), true);
assertEq(directEval.functionDisplayName, "evalFrame");

assertEq(directEval.parent.source.contains("> eval"), true);

assertEq(directEval.parent.parent.source.contains("> eval"), false);
assertEq(directEval.parent.parent.functionDisplayName, "iife");

assertEq(directEval.parent.parent.parent.source.contains("> eval"), false);

assertEq(directEval.parent.parent.parent.parent, null);

const lave = eval;
const indirectEval = (function iife() {
return lave("(" + function evalFrame() {
return saveStack();
} + "())");
}());

assertEq(indirectEval.source.contains("> eval"), true);
assertEq(indirectEval.functionDisplayName, "evalFrame");

assertEq(indirectEval.parent.source.contains("> eval"), true);

assertEq(indirectEval.parent.parent.source.contains("> eval"), false);
assertEq(indirectEval.parent.parent.functionDisplayName, "iife");

assertEq(indirectEval.parent.parent.parent.source.contains("> eval"), false);

assertEq(indirectEval.parent.parent.parent.parent, null);
17 changes: 17 additions & 0 deletions js/src/jit-test/tests/saved-stacks/function-display-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Test the functionDisplayName of SavedFrame instances.

function uno() { return dos(); }
const dos = () => tres.quattro();
const tres = {
quattro: () => saveStack()
};

const frame = uno();

assertEq(frame.functionDisplayName, "tres.quattro");
assertEq(frame.parent.functionDisplayName, "dos");
assertEq(frame.parent.parent.functionDisplayName, "uno");
assertEq(frame.parent.parent.parent.functionDisplayName, null);

assertEq(frame.parent.parent.parent.parent, null);

76 changes: 76 additions & 0 deletions js/src/jit-test/tests/saved-stacks/gc-frame-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Test that SavedFrame instances get removed from the SavedStacks frames cache
// after a GC.

const FUZZ_FACTOR = 3;

function assertAboutEq(actual, expected) {
if (Math.abs(actual - expected) > FUZZ_FACTOR)
throw new Error("Assertion failed: expected about " + expected + ", got " + actual +
". FUZZ_FACTOR = " + FUZZ_FACTOR);
}

const stacks = [];

stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());
stacks.push(saveStack());

assertAboutEq(getSavedFrameCount(), 50);

while (stacks.length) {
stacks.pop();
}
gc();

stacks = null;
gc();

assertAboutEq(getSavedFrameCount(), 0);
15 changes: 15 additions & 0 deletions js/src/jit-test/tests/saved-stacks/generators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Test that we can save stacks which have generator frames.

const { value: frame } = (function iife1() {
return (function* generator() {
yield (function iife2() {
return saveStack();
}());
}()).next();
}());

assertEq(frame.functionDisplayName, "iife2");
assertEq(frame.parent.functionDisplayName, "generator");
assertEq(frame.parent.parent.functionDisplayName, "iife1");
assertEq(frame.parent.parent.parent.functionDisplayName, null);
assertEq(frame.parent.parent.parent.parent, null);
25 changes: 25 additions & 0 deletions js/src/jit-test/tests/saved-stacks/get-set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Test that we can save stacks with getter and setter function frames.

function assertStackLengthEq(stack, expectedLength) {
let actual = 0;
while (stack) {
actual++;
stack = stack.parent;
}
assertEq(actual, expectedLength);
}

const get = { get s() { return saveStack(); } }.s;
assertStackLengthEq(get, 2);

let set;
try {
({
set s(v) {
throw saveStack();
}
}).s = 1;
} catch (s) {
set = s;
}
assertStackLengthEq(set, 2);
21 changes: 21 additions & 0 deletions js/src/jit-test/tests/saved-stacks/getters-on-invalid-objects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Test that you can't call the SavedFrame constructor and can only use
// SavedFrame's getters on SavedFrame instances.

load(libdir + "asserts.js");

let proto = Object.getPrototypeOf(saveStack());

// Can't create new SavedFrame instances by hand.
assertThrowsInstanceOf(() => {
new proto.constructor();
}, TypeError);

for (let p of ["source", "line", "column", "functionDisplayName", "parent"]) {
// The getters shouldn't work on the prototype.
assertThrowsInstanceOf(() => proto[p], TypeError);

// Nor should they work on random objects.
let o = {};
Object.defineProperty(o, p, Object.getOwnPropertyDescriptor(proto, p));
assertThrowsInstanceOf(() => o[p], TypeError);
}
12 changes: 12 additions & 0 deletions js/src/jit-test/tests/saved-stacks/native-calls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Test that we can save stacks with native code on the stack.

// Unlike Array.prototype.map, Array.prototype.filter is not self-hosted.
const filter = (function iife() {
try {
[3].filter(n => { throw saveStack() });
} catch (s) {
return s;
}
}());

assertEq(filter.parent.functionDisplayName, "iife");
70 changes: 70 additions & 0 deletions js/src/jit-test/tests/saved-stacks/principals-01.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Test that SavedFrame.prototype.parent gives the next older frame whose
// principals are subsumed by the caller's principals.

// Given a string of letters |expected|, say "abc", assert that the stack
// contains calls to a series of functions named by the next letter from
// the string, say a, b, and then c. Younger frames appear earlier in
// |expected| than older frames.
function check(expected, stack) {
print("check(" + uneval(expected) + ") against:\n" + stack);
count++;

while (stack.length && expected.length) {
assertEq(stack.shift(), expected[0]);
expected = expected.slice(1);
}

if (expected.length > 0) {
throw new Error("Missing frames for: " + expected);
}
if (stack.length > 0 && !stack.every(s => s === null)) {
throw new Error("Unexpected extra frame(s):\n" + stack);
}
}

// Go from a SavedFrame linked list to an array of function display names.
function extract(stack) {
const results = [];
while (stack) {
results.push(stack.functionDisplayName);
stack = stack.parent;
}
return results;
}

const low = newGlobal({ principal: 0 });
const mid = newGlobal({ principal: 0xffff });
const high = newGlobal({ principal: 0xfffff });

var count = 0;

eval('function a() { check("a", extract(saveStack())); b(); }');
low .eval('function b() { check("b", extract(saveStack())); c(); }');
mid .eval('function c() { check("cba", extract(saveStack())); d(); }');
high.eval('function d() { check("dcba", extract(saveStack())); e(); }');
eval('function e() { check("edcba", extract(saveStack())); f(); }'); // no principal, so checks skipped
low .eval('function f() { check("fb", extract(saveStack())); g(); }');
mid .eval('function g() { check("gfecba", extract(saveStack())); h(); }');
high.eval('function h() { check("hgfedcba", extract(saveStack())); }');

// Make everyone's functions visible to each other, as needed.
b = low .b;
low .c = mid .c;
mid .d = high.d;
high.e = e;
f = low .f;
low .g = mid .g;
mid .h = high.h;

low.check = mid.check = high.check = check;

// They each must have their own extract so that CCWs don't mess up the
// principals when we ask for the parent property.
low. eval("" + extract);
mid. eval("" + extract);
high.eval("" + extract);

// Kick the whole process off.
a();

assertEq(count, 8);
Loading

0 comments on commit 9f0fc42

Please sign in to comment.