summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/saved-stacks
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/saved-stacks')
-rw-r--r--js/src/jit-test/tests/saved-stacks/SavedFrame-constructor.js4
-rw-r--r--js/src/jit-test/tests/saved-stacks/asm-frames.js37
-rw-r--r--js/src/jit-test/tests/saved-stacks/async-max-frame-count.js99
-rw-r--r--js/src/jit-test/tests/saved-stacks/async-principals.js247
-rw-r--r--js/src/jit-test/tests/saved-stacks/async.js24
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1004479-savedStacks-with-string-parameter.js4
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js10
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1012646-strlen-crasher.js4
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1031168-trace-sources.js9
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1149495.js6
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1225474.js6
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1260712.js7
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1289058.js13
-rw-r--r--js/src/jit-test/tests/saved-stacks/bug-1289073.js1
-rw-r--r--js/src/jit-test/tests/saved-stacks/caching-and-ccws.js35
-rw-r--r--js/src/jit-test/tests/saved-stacks/caching-and-frame-count.js38
-rw-r--r--js/src/jit-test/tests/saved-stacks/capture-first-frame-with-principals.js92
-rw-r--r--js/src/jit-test/tests/saved-stacks/display-url.js26
-rw-r--r--js/src/jit-test/tests/saved-stacks/evals.js38
-rw-r--r--js/src/jit-test/tests/saved-stacks/function-display-name.js17
-rw-r--r--js/src/jit-test/tests/saved-stacks/gc-frame-cache.js87
-rw-r--r--js/src/jit-test/tests/saved-stacks/generators.js19
-rw-r--r--js/src/jit-test/tests/saved-stacks/get-set.js25
-rw-r--r--js/src/jit-test/tests/saved-stacks/getters-on-invalid-objects.js23
-rw-r--r--js/src/jit-test/tests/saved-stacks/max-frame-count.js42
-rw-r--r--js/src/jit-test/tests/saved-stacks/native-calls.js12
-rw-r--r--js/src/jit-test/tests/saved-stacks/oom-in-save-stack.js4
-rw-r--r--js/src/jit-test/tests/saved-stacks/principals-01.js70
-rw-r--r--js/src/jit-test/tests/saved-stacks/principals-02.js56
-rw-r--r--js/src/jit-test/tests/saved-stacks/principals-03.js23
-rw-r--r--js/src/jit-test/tests/saved-stacks/principals-04.js15
-rw-r--r--js/src/jit-test/tests/saved-stacks/proxy-handlers.js10
-rw-r--r--js/src/jit-test/tests/saved-stacks/same-stack.js12
-rw-r--r--js/src/jit-test/tests/saved-stacks/self-hosted.js26
-rw-r--r--js/src/jit-test/tests/saved-stacks/shared-parent-frames.js19
-rw-r--r--js/src/jit-test/tests/saved-stacks/stacks-are-frozen.js17
-rw-r--r--js/src/jit-test/tests/saved-stacks/stringify-with-self-hosted.js8
37 files changed, 1185 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/saved-stacks/SavedFrame-constructor.js b/js/src/jit-test/tests/saved-stacks/SavedFrame-constructor.js
new file mode 100644
index 000000000..0dbafea98
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/SavedFrame-constructor.js
@@ -0,0 +1,4 @@
+// The SavedFrame constructor shouldn't have been exposed to JS on the global.
+
+saveStack();
+assertEq(typeof SavedFrame, "undefined");
diff --git a/js/src/jit-test/tests/saved-stacks/asm-frames.js b/js/src/jit-test/tests/saved-stacks/asm-frames.js
new file mode 100644
index 000000000..366712253
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/asm-frames.js
@@ -0,0 +1,37 @@
+function AsmModule(stdlib, foreign, heap) {
+ "use asm";
+ var ffi = foreign.t;
+
+ function doTest() {
+ ffi();
+ }
+
+ function test() {
+ doTest();
+ }
+
+ return { test: test };
+}
+
+let stack;
+
+function tester() {
+ stack = saveStack();
+}
+
+const buf = new ArrayBuffer(1024*8);
+const module = AsmModule(this, { t: tester }, buf);
+module.test();
+
+print(stack);
+assertEq(stack.functionDisplayName, "tester");
+
+assertEq(stack.parent.functionDisplayName, "doTest");
+assertEq(stack.parent.line, 6);
+
+assertEq(stack.parent.parent.functionDisplayName, "test");
+assertEq(stack.parent.parent.line, 10);
+
+assertEq(stack.parent.parent.parent.line, 24);
+
+assertEq(stack.parent.parent.parent.parent, null);
diff --git a/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
new file mode 100644
index 000000000..ebcbe13d3
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
@@ -0,0 +1,99 @@
+// Test that async stacks are limited on recursion.
+
+const defaultAsyncStackLimit = 60;
+
+function recur(n, limit) {
+ if (n > 0) {
+ return callFunctionWithAsyncStack(function recur() {return recur(n - 1, limit)},
+ saveStack(limit), "Recurse");
+ }
+ return saveStack(limit);
+}
+
+function checkRecursion(n, limit) {
+ print("checkRecursion(" + uneval(n) + ", " + uneval(limit) + ")");
+
+ try {
+ var stack = recur(n, limit);
+ } catch (e) {
+ // Some platforms, like ASAN builds, can end up overrecursing. Tolerate
+ // these failures.
+ assertEq(/too much recursion/.test("" + e), true);
+ return;
+ }
+
+ // Async stacks are limited even if we didn't ask for a limit. There is a
+ // default limit on frames attached on top of any synchronous frames, and
+ // every time the limit is reached when capturing, half of the frames are
+ // truncated from the old end of the async stack.
+ if (limit == 0) {
+ // Always add one synchronous frame that is the last call to `recur`.
+ if (n + 1 < defaultAsyncStackLimit) {
+ limit = defaultAsyncStackLimit + 1;
+ } else {
+ limit = n + 2 - (defaultAsyncStackLimit / 2);
+ }
+ }
+
+ // The first `n` or `limit` frames should have `recur` as their `asyncParent`.
+ for (var i = 0; i < Math.min(n, limit); i++) {
+ assertEq(stack.functionDisplayName, "recur");
+ assertEq(stack.parent, null);
+ stack = stack.asyncParent;
+ }
+
+ // This frame should be the first call to `recur`.
+ if (limit > n) {
+ assertEq(stack.functionDisplayName, "recur");
+ assertEq(stack.asyncParent, null);
+ stack = stack.parent;
+ } else {
+ assertEq(stack, null);
+ }
+
+ // This frame should be the call to `checkRecursion`.
+ if (limit > n + 1) {
+ assertEq(stack.functionDisplayName, "checkRecursion");
+ assertEq(stack.asyncParent, null);
+ stack = stack.parent;
+ } else {
+ assertEq(stack, null);
+ }
+
+ // We should be at the top frame, which is the test script itself.
+ if (limit > n + 2) {
+ assertEq(stack.functionDisplayName, null);
+ assertEq(stack.asyncParent, null);
+ assertEq(stack.parent, null);
+ } else {
+ assertEq(stack, null);
+ }
+}
+
+// Check capturing with no limit, which should still apply a default limit.
+checkRecursion(0, 0);
+checkRecursion(1, 0);
+checkRecursion(2, 0);
+checkRecursion(defaultAsyncStackLimit - 10, 0);
+checkRecursion(defaultAsyncStackLimit, 0);
+checkRecursion(defaultAsyncStackLimit + 10, 0);
+
+// Limit of 1 frame.
+checkRecursion(0, 1);
+checkRecursion(1, 1);
+checkRecursion(2, 1);
+
+// Limit of 2 frames.
+checkRecursion(0, 2);
+checkRecursion(1, 2);
+checkRecursion(2, 2);
+
+// Limit of 3 frames.
+checkRecursion(0, 3);
+checkRecursion(1, 3);
+checkRecursion(2, 3);
+
+// Limit higher than the default limit.
+checkRecursion(defaultAsyncStackLimit + 10, defaultAsyncStackLimit + 10);
+checkRecursion(defaultAsyncStackLimit + 11, defaultAsyncStackLimit + 10);
+checkRecursion(defaultAsyncStackLimit + 12, defaultAsyncStackLimit + 10);
diff --git a/js/src/jit-test/tests/saved-stacks/async-principals.js b/js/src/jit-test/tests/saved-stacks/async-principals.js
new file mode 100644
index 000000000..09b8b5ce3
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async-principals.js
@@ -0,0 +1,247 @@
+// Test cases when a SavedFrame or one of its ancestors have a principal that is
+// not subsumed by the caller's principal, when async parents are present.
+
+function checkVisibleStack(stackFrame, expectedFrames) {
+ // We check toString separately from properties like asyncCause to check that
+ // it walks the physical SavedFrame chain consistently with the properties.
+ var stringFrames = stackFrame.toString().split("\n");
+
+ while (expectedFrames.length) {
+ let expectedFrame = expectedFrames.shift();
+ let stringFrame = stringFrames.shift();
+
+ // Check the frame properties.
+ assertEq(stackFrame.functionDisplayName, expectedFrame.name);
+ assertEq(stackFrame.asyncCause, expectedFrame.asyncCause);
+
+ // Check the stringified version.
+ let expectedStart =
+ (expectedFrame.asyncCause ? expectedFrame.asyncCause + "*" : "") +
+ expectedFrame.name;
+ assertEq(stringFrame.replace(/@.*$/, ""), expectedStart);
+
+ // If the next frame has an asyncCause, it should be an asyncParent.
+ if (expectedFrames.length && expectedFrames[0].asyncCause) {
+ assertEq(stackFrame.parent, null);
+ stackFrame = stackFrame.asyncParent;
+ } else {
+ assertEq(stackFrame.asyncParent, null);
+ stackFrame = stackFrame.parent;
+ }
+ }
+}
+
+var low = newGlobal({ principal: 0 });
+var high = newGlobal({ principal: 0xfffff });
+
+// Test with synchronous cross-compartment calls.
+//
+// With arrows representing child-to-parent links, and fat arrows representing
+// child-to-async-parent links, create a SavedFrame stack like this:
+//
+// low.e -> high.d => high.c => high.b -> low.a
+//
+// This stack captured in function `e` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.e => low.a
+//
+// The asyncCause seen on `low.a` above should not leak information about the
+// real asyncCause on `high.c` and `high.d`.
+//
+// The stack captured in function `d` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.a
+
+// We'll move these functions into the right globals below before invoking them.
+function a() {
+ b();
+}
+function b() {
+ callFunctionWithAsyncStack(c, saveStack(), "BtoC");
+}
+function c() {
+ callFunctionWithAsyncStack(d, saveStack(), "CtoD");
+}
+function d() {
+ let stackD = saveStack();
+
+ print("high.checkVisibleStack(stackD)");
+ checkVisibleStack(stackD, [
+ { name: "d", asyncCause: null },
+ { name: "c", asyncCause: "CtoD" },
+ { name: "b", asyncCause: "BtoC" },
+ { name: "a", asyncCause: null },
+ ]);
+
+ let stackE = e(saveStack(0, low));
+
+ print("high.checkVisibleStack(stackE)");
+ checkVisibleStack(stackE, [
+ { name: "e", asyncCause: null },
+ { name: "d", asyncCause: null },
+ { name: "c", asyncCause: "CtoD" },
+ { name: "b", asyncCause: "BtoC" },
+ { name: "a", asyncCause: null },
+ ]);
+}
+function e(stackD) {
+ print("low.checkVisibleStack(stackD)");
+ checkVisibleStack(stackD, [
+ { name: "a", asyncCause: "Async" },
+ ]);
+
+ let stackE = saveStack();
+
+ print("low.checkVisibleStack(stackE)");
+ checkVisibleStack(stackE, [
+ { name: "e", asyncCause: null },
+ { name: "a", asyncCause: "Async" },
+ ]);
+
+ return saveStack(0, high);
+}
+
+// Test with asynchronous cross-compartment calls and shared frames.
+//
+// With arrows representing child-to-parent links, and fat arrows representing
+// child-to-async-parent links, create a SavedFrame stack like this:
+//
+// low.x => high.v => low.u
+// low.y -> high.v => low.u
+// low.z => high.w -> low.u
+//
+// This stack captured in functions `x`, `y`, and `z` would be seen in its
+// complete version if accessed by `high`'s compartment, while in `low`'s
+// compartment it would look like this:
+//
+// low.x => low.u
+// low.y => low.u
+// low.z => low.u
+//
+// The stack captured in function `v` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.u
+
+// We'll move these functions into the right globals below before invoking them.
+function u() {
+ callFunctionWithAsyncStack(v, saveStack(), "UtoV");
+ w();
+}
+function v() {
+ let stackV = saveStack();
+ print("high.checkVisibleStack(stackV)");
+ checkVisibleStack(stackV, [
+ { name: "v", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let stack = saveStack(0, low);
+ function xToCall() { return x(stack);};
+
+ let stackX = callFunctionWithAsyncStack(xToCall, saveStack(), "VtoX");
+
+ print("high.checkVisibleStack(stackX)");
+ checkVisibleStack(stackX, [
+ { name: "x", asyncCause: null },
+ { name: "xToCall", asyncCause: null },
+ { name: "v", asyncCause: "VtoX" },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let stackY = y();
+
+ print("high.checkVisibleStack(stackY)");
+ checkVisibleStack(stackY, [
+ { name: "y", asyncCause: null },
+ { name: "v", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+}
+function w() {
+ let stackZ = callFunctionWithAsyncStack(z, saveStack(), "WtoZ");
+
+ print("high.checkVisibleStack(stackZ)");
+ checkVisibleStack(stackZ, [
+ { name: "z", asyncCause: null },
+ { name: "w", asyncCause: "WtoZ" },
+ { name: "u", asyncCause: null },
+ ]);
+}
+function x(stackV) {
+ print("low.checkVisibleStack(stackV)");
+ checkVisibleStack(stackV, [
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let stackX = saveStack();
+
+ print("low.checkVisibleStack(stackX)");
+ checkVisibleStack(stackX, [
+ { name: "x", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ return saveStack(0, high);
+}
+
+function y() {
+ let stackY = saveStack();
+
+ print("low.checkVisibleStack(stackY)");
+ checkVisibleStack(stackY, [
+ { name: "y", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ return saveStack(0, high);
+}
+function z() {
+ let stackZ = saveStack();
+
+ print("low.checkVisibleStack(stackZ)");
+ checkVisibleStack(stackZ, [
+ { name: "z", asyncCause: null },
+ { name: "u", asyncCause: "Async" },
+ ]);
+
+ return saveStack(0, high);
+}
+
+// Split the functions in their respective globals.
+low .eval(a.toSource());
+high.eval(b.toSource());
+high.eval(c.toSource());
+high.eval(d.toSource());
+low .eval(e.toSource());
+
+low .b = high.b;
+high.e = low .e;
+
+low .eval(u.toSource());
+high.eval(v.toSource());
+high.eval(w.toSource());
+low .eval(x.toSource());
+low .eval(y.toSource());
+low .eval(z.toSource());
+
+low .v = high.v;
+low .w = high.w;
+high.x = low .x;
+high.y = low .y;
+high.z = low .z;
+
+low .high = high;
+high.low = low;
+
+low .eval(checkVisibleStack.toSource());
+high.eval(checkVisibleStack.toSource());
+
+// Execute the tests.
+low.a();
+low.u();
diff --git a/js/src/jit-test/tests/saved-stacks/async.js b/js/src/jit-test/tests/saved-stacks/async.js
new file mode 100644
index 000000000..6ab4546a7
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async.js
@@ -0,0 +1,24 @@
+// Test calling a function using a previously captured stack as an async stack.
+
+function getAsyncStack() {
+ return saveStack();
+}
+
+// asyncCause may contain non-ASCII characters.
+let testAsyncCause = "Tes" + String.fromCharCode(355) + "String";
+
+callFunctionWithAsyncStack(function asyncCallback() {
+ let stack = saveStack();
+
+ assertEq(stack.functionDisplayName, "asyncCallback");
+ assertEq(stack.parent, null);
+ assertEq(stack.asyncCause, null);
+
+ assertEq(stack.asyncParent.functionDisplayName, "getAsyncStack");
+ assertEq(stack.asyncParent.asyncCause, testAsyncCause);
+ assertEq(stack.asyncParent.asyncParent, null);
+
+ assertEq(stack.asyncParent.parent.asyncCause, null);
+ assertEq(stack.asyncParent.parent.asyncParent, null);
+ assertEq(stack.asyncParent.parent.parent, null);
+}, getAsyncStack(), testAsyncCause);
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1004479-savedStacks-with-string-parameter.js b/js/src/jit-test/tests/saved-stacks/bug-1004479-savedStacks-with-string-parameter.js
new file mode 100644
index 000000000..785c02494
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1004479-savedStacks-with-string-parameter.js
@@ -0,0 +1,4 @@
+// This test case was found by the fuzzer and crashed the js shell.
+
+Object.preventExtensions(this);
+saveStack();
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js b/js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js
new file mode 100644
index 000000000..593c4b40b
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1006876-too-much-recursion.js
@@ -0,0 +1,10 @@
+// |jit-test| exitstatus: 3
+
+// This test case was found by the fuzzer and crashed the js shell. It should
+// throw a "too much recursion" error, but was crashing instead.
+
+enableTrackAllocations();
+function f() {
+ f();
+}
+f();
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1012646-strlen-crasher.js b/js/src/jit-test/tests/saved-stacks/bug-1012646-strlen-crasher.js
new file mode 100644
index 000000000..95a63aafd
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1012646-strlen-crasher.js
@@ -0,0 +1,4 @@
+// |jit-test| exitstatus: 3
+
+enableTrackAllocations();
+evaluate("throw Error();", {fileName: null});
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1031168-trace-sources.js b/js/src/jit-test/tests/saved-stacks/bug-1031168-trace-sources.js
new file mode 100644
index 000000000..3162855a8
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1031168-trace-sources.js
@@ -0,0 +1,9 @@
+loadFile("\
+saveStack();\
+gcPreserveCode = function() {};\
+gc();\
+saveStack() == 3\
+");
+function loadFile(lfVarx) {
+ evaluate(lfVarx);
+}
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1149495.js b/js/src/jit-test/tests/saved-stacks/bug-1149495.js
new file mode 100644
index 000000000..aedd7726d
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1149495.js
@@ -0,0 +1,6 @@
+try {
+ offThreadCompileScript('Error()', { lineNumber: (4294967295)});
+ runOffThreadScript().stack;
+} catch (e) {
+ // Ignore "Error: Can't use offThreadCompileScript with --no-threads"
+}
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1225474.js b/js/src/jit-test/tests/saved-stacks/bug-1225474.js
new file mode 100644
index 000000000..ca0dcaa31
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1225474.js
@@ -0,0 +1,6 @@
+// setSavedStacksRNGState shouldn't crash regardless of the seed value passed.
+
+setSavedStacksRNGState(0);
+setSavedStacksRNGState({});
+setSavedStacksRNGState(false);
+setSavedStacksRNGState(NaN);
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1260712.js b/js/src/jit-test/tests/saved-stacks/bug-1260712.js
new file mode 100644
index 000000000..5b1319cd1
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1260712.js
@@ -0,0 +1,7 @@
+low = high = newGlobal({
+ principal: 5
+})
+high.low = low
+high.eval("function a() { return saveStack(1, low) }")
+set = eval("high.a()")
+serialize(set)
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1289058.js b/js/src/jit-test/tests/saved-stacks/bug-1289058.js
new file mode 100644
index 000000000..1968f5850
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1289058.js
@@ -0,0 +1,13 @@
+const g1 = newGlobal({});
+const g2 = newGlobal(newGlobal);
+g1.g2obj = g2.eval("new Object");
+g1.evaluate(`
+ const global = this;
+ function capture(shouldIgnoreSelfHosted = true) {
+ return captureFirstSubsumedFrame(global.g2obj, shouldIgnoreSelfHosted);
+ }
+ (function iife1() {
+ const captureTrueStack = capture(true);
+ }());
+`, {
+});
diff --git a/js/src/jit-test/tests/saved-stacks/bug-1289073.js b/js/src/jit-test/tests/saved-stacks/bug-1289073.js
new file mode 100644
index 000000000..2d0a9919f
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/bug-1289073.js
@@ -0,0 +1 @@
+saveStack(0.2);
diff --git a/js/src/jit-test/tests/saved-stacks/caching-and-ccws.js b/js/src/jit-test/tests/saved-stacks/caching-and-ccws.js
new file mode 100644
index 000000000..b61e8c325
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/caching-and-ccws.js
@@ -0,0 +1,35 @@
+// Test that the SavedFrame caching doesn't get messed up in the presence of
+// cross-compartment calls.
+
+const funcSource = "function call(f) { return f(); }";
+
+const g1 = newGlobal();
+const g2 = newGlobal();
+
+g1.eval(funcSource);
+g2.eval(funcSource);
+eval(funcSource);
+
+function doSaveStack() {
+ return saveStack();
+}
+
+const captureStacksAcrossCompartmens = () =>
+ [this, g1, g2].map(g => g.call(doSaveStack));
+
+(function f0() {
+ const stacks = [];
+
+ for (var i = 0; i < 2; i++)
+ stacks.push(...captureStacksAcrossCompartmens());
+
+ const [s1, s2, s3, s4, s5, s6] = stacks;
+
+ assertEq(s1 != s2, true);
+ assertEq(s2 != s3, true);
+ assertEq(s3 != s1, true);
+
+ assertEq(s1, s4);
+ assertEq(s2, s5);
+ assertEq(s3, s6);
+}());
diff --git a/js/src/jit-test/tests/saved-stacks/caching-and-frame-count.js b/js/src/jit-test/tests/saved-stacks/caching-and-frame-count.js
new file mode 100644
index 000000000..a751d0e3c
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/caching-and-frame-count.js
@@ -0,0 +1,38 @@
+// Test that the SavedFrame caching doesn't mess up counts. Specifically, that
+// if we capture only the first n frames of a stack, we don't cache that stack
+// and return it for when someone else captures another stack and asks for more
+// frames than that last time.
+
+function stackLength(stack) {
+ return stack === null
+ ? 0
+ : 1 + stackLength(stack.parent);
+}
+
+(function f0() {
+ (function f1() {
+ (function f2() {
+ (function f3() {
+ (function f4() {
+ (function f5() {
+ (function f6() {
+ (function f7() {
+ (function f8() {
+ (function f9() {
+ const s1 = saveStack(3);
+ const s2 = saveStack(5);
+ const s3 = saveStack(0 /* no limit */);
+
+ assertEq(stackLength(s1), 3);
+ assertEq(stackLength(s2), 5);
+ assertEq(stackLength(s3), 11);
+ }());
+ }());
+ }());
+ }());
+ }());
+ }());
+ }());
+ }());
+ }());
+}());
diff --git a/js/src/jit-test/tests/saved-stacks/capture-first-frame-with-principals.js b/js/src/jit-test/tests/saved-stacks/capture-first-frame-with-principals.js
new file mode 100644
index 000000000..6b126cb09
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/capture-first-frame-with-principals.js
@@ -0,0 +1,92 @@
+// Create two different globals whose compartments have two different
+// principals. Test getting the first frame on the stack with some given
+// principals in various configurations of JS stack and of wanting self-hosted
+// frames or not.
+
+const g1 = newGlobal({
+ principal: 0xffff
+});
+
+const g2 = newGlobal({
+ principal: 0xff
+});
+
+// Introduce everyone to themselves and each other.
+g1.g2 = g2.g2 = g2;
+g1.g1 = g2.g1 = g1;
+
+g1.g2obj = g2.eval("new Object");
+
+g1.evaluate(`
+ const global = this;
+
+ // Capture the stack back to the first frame in the g2 global.
+ function capture(shouldIgnoreSelfHosted = true) {
+ return captureFirstSubsumedFrame(global.g2obj, shouldIgnoreSelfHosted);
+ }
+`, {
+ fileName: "script1.js"
+});
+
+g2.evaluate(`
+ const capture = g1.capture;
+
+ // Use our Function.prototype.bind, not capture.bind (which is ===
+ // g1.Function.prototype.bind) so that the generated bound function is in our
+ // compartment and has our principals.
+ const boundTrue = Function.prototype.bind.call(capture, null, true);
+ const boundFalse = Function.prototype.bind.call(capture, null, false);
+
+ function getOldestFrame(stack) {
+ while (stack.parent) {
+ stack = stack.parent;
+ }
+ return stack;
+ }
+
+ function dumpStack(name, stack) {
+ print("Stack " + name + " =");
+ while (stack) {
+ print(" " + stack.functionDisplayName + " @ " + stack.source);
+ stack = stack.parent;
+ }
+ print();
+ }
+
+ // When the youngest frame is not self-hosted, it doesn't matter whether or not
+ // we specify that we should ignore self hosted frames when capturing the first
+ // frame with the given principals.
+ //
+ // Stack: iife1 (g2) <- capture (g1)
+
+ (function iife1() {
+ const captureTrueStack = capture(true);
+ dumpStack("captureTrueStack", captureTrueStack);
+ assertEq(getOldestFrame(captureTrueStack).functionDisplayName, "iife1");
+ assertEq(getOldestFrame(captureTrueStack).source, "script2.js");
+
+ const captureFalseStack = capture(false);
+ dumpStack("captureFalseStack", captureFalseStack);
+ assertEq(getOldestFrame(captureFalseStack).functionDisplayName, "iife1");
+ assertEq(getOldestFrame(captureFalseStack).source, "script2.js");
+ }());
+
+ // When the youngest frame is a self hosted frame, we get two different
+ // captured stacks depending on whether or not we ignore self-hosted frames.
+ //
+ // Stack: iife2 (g2) <- bound function (g2) <- capture (g1)
+
+ (function iife2() {
+ const boundTrueStack = boundTrue();
+ dumpStack("boundTrueStack", boundTrueStack);
+ assertEq(getOldestFrame(boundTrueStack).functionDisplayName, "iife2");
+ assertEq(getOldestFrame(boundTrueStack).source, "script2.js");
+
+ const boundFalseStack = boundFalse();
+ dumpStack("boundFalseStack", boundFalseStack);
+ assertEq(getOldestFrame(boundFalseStack).functionDisplayName !== "iife2", true);
+ assertEq(getOldestFrame(boundFalseStack).source, "self-hosted");
+ }());
+`, {
+ fileName: "script2.js"
+});
diff --git a/js/src/jit-test/tests/saved-stacks/display-url.js b/js/src/jit-test/tests/saved-stacks/display-url.js
new file mode 100644
index 000000000..4688fefe0
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/display-url.js
@@ -0,0 +1,26 @@
+eval(`
+ function a() {
+ return b();
+ }
+ //# sourceURL=source-a.js
+`);
+
+eval(`
+ function b() {
+ return c();
+ }
+ //# sourceURL=source-b.js
+`);
+
+eval(`
+ function c() {
+ return saveStack();
+ }
+ //# sourceURL=source-c.js
+`);
+
+let stack = a();
+print(stack);
+assertEq(stack.source, "source-c.js");
+assertEq(stack.parent.source, "source-b.js");
+assertEq(stack.parent.parent.source, "source-a.js");
diff --git a/js/src/jit-test/tests/saved-stacks/evals.js b/js/src/jit-test/tests/saved-stacks/evals.js
new file mode 100644
index 000000000..41a0f9111
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/evals.js
@@ -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.includes("> eval"), true);
+assertEq(directEval.functionDisplayName, "evalFrame");
+
+assertEq(directEval.parent.source.includes("> eval"), true);
+
+assertEq(directEval.parent.parent.source.includes("> eval"), false);
+assertEq(directEval.parent.parent.functionDisplayName, "iife");
+
+assertEq(directEval.parent.parent.parent.source.includes("> 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.includes("> eval"), true);
+assertEq(indirectEval.functionDisplayName, "evalFrame");
+
+assertEq(indirectEval.parent.source.includes("> eval"), true);
+
+assertEq(indirectEval.parent.parent.source.includes("> eval"), false);
+assertEq(indirectEval.parent.parent.functionDisplayName, "iife");
+
+assertEq(indirectEval.parent.parent.parent.source.includes("> eval"), false);
+
+assertEq(indirectEval.parent.parent.parent.parent, null);
diff --git a/js/src/jit-test/tests/saved-stacks/function-display-name.js b/js/src/jit-test/tests/saved-stacks/function-display-name.js
new file mode 100644
index 000000000..cfe175758
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/function-display-name.js
@@ -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);
+
diff --git a/js/src/jit-test/tests/saved-stacks/gc-frame-cache.js b/js/src/jit-test/tests/saved-stacks/gc-frame-cache.js
new file mode 100644
index 000000000..cf2646f47
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/gc-frame-cache.js
@@ -0,0 +1,87 @@
+// Test that SavedFrame instances get removed from the SavedStacks frames cache
+// after a GC.
+
+const FUZZ_FACTOR = 3;
+
+function isAboutEq(actual, expected) {
+ return Math.abs(actual - expected) <= FUZZ_FACTOR;
+}
+
+var stacks = [];
+
+(function () {
+ // Use an IIFE here so that we don't keep these saved stacks alive in the
+ // frame cache when we test that they all go away at the end of the test.
+
+ var startCount = getSavedFrameCount();
+ print("startCount = " + startCount);
+
+ 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());
+
+ gc();
+
+ var endCount = getSavedFrameCount();
+ print("endCount = " + endCount);
+
+ assertEq(isAboutEq(endCount - startCount, 50), true);
+}());
+
+while (stacks.length) {
+ stacks.pop();
+}
+gc();
+
+stacks = null;
+gc();
+
+assertEq(isAboutEq(getSavedFrameCount(), 0), true);
diff --git a/js/src/jit-test/tests/saved-stacks/generators.js b/js/src/jit-test/tests/saved-stacks/generators.js
new file mode 100644
index 000000000..287899758
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/generators.js
@@ -0,0 +1,19 @@
+// Test that we can save stacks which have generator frames.
+
+const { value: frame } = (function iife1() {
+ return (function* generator() {
+ yield (function iife2() {
+ return saveStack();
+ }());
+ }()).next();
+}());
+
+// Bug 1102498 - toString does not include self-hosted frames, which can appear
+// depending on GC timing. This may end up changing in the future, see
+// bug 1103155.
+
+var lines = frame.toString().split("\n");
+assertEq(lines[0].startsWith("iife2@"), true);
+assertEq(lines[1].startsWith("generator@"), true);
+assertEq(lines[2].startsWith("iife1@"), true);
+assertEq(lines[3].startsWith("@"), true);
diff --git a/js/src/jit-test/tests/saved-stacks/get-set.js b/js/src/jit-test/tests/saved-stacks/get-set.js
new file mode 100644
index 000000000..be2e20739
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/get-set.js
@@ -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);
diff --git a/js/src/jit-test/tests/saved-stacks/getters-on-invalid-objects.js b/js/src/jit-test/tests/saved-stacks/getters-on-invalid-objects.js
new file mode 100644
index 000000000..9a892f20b
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/getters-on-invalid-objects.js
@@ -0,0 +1,23 @@
+// 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.
+print("Testing constructor");
+assertThrowsInstanceOf(() => {
+ new proto.constructor();
+}, TypeError);
+
+for (let p of ["source", "line", "column", "functionDisplayName", "parent"]) {
+ print("Testing getter: " + p);
+ // 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);
+}
diff --git a/js/src/jit-test/tests/saved-stacks/max-frame-count.js b/js/src/jit-test/tests/saved-stacks/max-frame-count.js
new file mode 100644
index 000000000..17c375176
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/max-frame-count.js
@@ -0,0 +1,42 @@
+// Test that we can capture only the N newest frames.
+// This is the maxFrameCount argument to JS::CaptureCurrentStack.
+
+load(libdir + 'asserts.js');
+
+function recur(n, limit) {
+ if (n > 0)
+ return recur(n - 1, limit);
+ return saveStack(limit);
+}
+
+// Negative values are rejected.
+assertThrowsInstanceOf(() => saveStack(-1), TypeError);
+
+// Zero means 'no limit'.
+assertEq(saveStack(0).parent, null);
+assertEq(recur(0, 0).parent !== null, true);
+assertEq(recur(0, 0).parent.parent, null);
+assertEq(recur(1, 0).parent.parent.parent, null);
+assertEq(recur(2, 0).parent.parent.parent.parent, null);
+assertEq(recur(3, 0).parent.parent.parent.parent.parent, null);
+
+// limit of 1
+assertEq(saveStack(1).parent, null);
+assertEq(recur(0, 1).parent, null);
+assertEq(recur(0, 1).parent, null);
+assertEq(recur(1, 1).parent, null);
+assertEq(recur(2, 1).parent, null);
+
+// limit of 2
+assertEq(saveStack(2).parent, null);
+assertEq(recur(0, 2).parent !== null, true);
+assertEq(recur(0, 2).parent.parent, null);
+assertEq(recur(1, 2).parent.parent, null);
+assertEq(recur(2, 2).parent.parent, null);
+
+// limit of 3
+assertEq(saveStack(3).parent, null);
+assertEq(recur(0, 3).parent !== null, true);
+assertEq(recur(0, 3).parent.parent, null);
+assertEq(recur(1, 3).parent.parent.parent, null);
+assertEq(recur(2, 3).parent.parent.parent, null);
diff --git a/js/src/jit-test/tests/saved-stacks/native-calls.js b/js/src/jit-test/tests/saved-stacks/native-calls.js
new file mode 100644
index 000000000..4b12ad738
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/native-calls.js
@@ -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 {
+ callFunctionFromNativeFrame(n => { throw saveStack() });
+ } catch (s) {
+ return s;
+ }
+}());
+
+assertEq(filter.parent.functionDisplayName, "iife");
diff --git a/js/src/jit-test/tests/saved-stacks/oom-in-save-stack.js b/js/src/jit-test/tests/saved-stacks/oom-in-save-stack.js
new file mode 100644
index 000000000..ba36df057
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/oom-in-save-stack.js
@@ -0,0 +1,4 @@
+if (!('oomTest' in this))
+ quit();
+let s = saveStack();
+oomTest(() => { saveStack(); });
diff --git a/js/src/jit-test/tests/saved-stacks/principals-01.js b/js/src/jit-test/tests/saved-stacks/principals-01.js
new file mode 100644
index 000000000..62640472d
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/principals-01.js
@@ -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("ecba", extract(saveStack())); f(); }');
+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);
diff --git a/js/src/jit-test/tests/saved-stacks/principals-02.js b/js/src/jit-test/tests/saved-stacks/principals-02.js
new file mode 100644
index 000000000..24ffa82a8
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/principals-02.js
@@ -0,0 +1,56 @@
+// Test that SavedFrame.prototype.toString only shows frames whose principal is
+// subsumed by the caller's principal.
+
+var count = 0;
+
+// 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++;
+
+ // Extract only the function names from the stack trace. Omit the frames
+ // for the top-level evaluation, if it is present.
+ const frames = stack
+ .split("\n")
+ .filter(f => f.match(/^.@/))
+ .map(f => f.replace(/@.*$/g, ""));
+
+ // Check the function names against the expected sequence.
+ assertEq(frames.length, expected.length);
+ for (var i = 0; i < expected.length; i++) {
+ assertEq(frames[i], expected[i]);
+ }
+}
+
+var low = newGlobal({ principal: 0 });
+var mid = newGlobal({ principal: 0xffff });
+var high = newGlobal({ principal: 0xfffff });
+
+ eval('function a() { check("a", saveStack().toString()); b(); }');
+low .eval('function b() { check("b", saveStack().toString()); c(); }');
+mid .eval('function c() { check("cba", saveStack().toString()); d(); }');
+high.eval('function d() { check("dcba", saveStack().toString()); e(); }');
+ eval('function e() { check("ecba", saveStack().toString()); f(); }');
+low .eval('function f() { check("fb", saveStack().toString()); g(); }');
+mid .eval('function g() { check("gfecba", saveStack().toString()); h(); }');
+high.eval('function h() { check("hgfedcba", saveStack().toString()); }');
+
+// 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;
+
+// Kick the whole process off.
+a();
+
+assertEq(count, 8);
+
diff --git a/js/src/jit-test/tests/saved-stacks/principals-03.js b/js/src/jit-test/tests/saved-stacks/principals-03.js
new file mode 100644
index 000000000..006b4477c
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/principals-03.js
@@ -0,0 +1,23 @@
+// With arrows representing child-to-parent links, create a SavedFrame stack
+// like this:
+//
+// high.a -> low.b
+//
+// in `low`'s compartment and give `low` a reference to this stack. Assert the
+// stack's youngest frame's properties doesn't leak information about `high.a`
+// that `low` shouldn't have access to, and instead returns information about
+// `low.b`.
+
+var low = newGlobal({ principal: 0 });
+var high = newGlobal({ principal: 0xfffff });
+
+low.high = high;
+high.low = low;
+
+high.eval("function a() { return saveStack(0, low); }");
+low.eval("function b() { return high.a(); }")
+
+var stack = low.b();
+
+assertEq(stack.functionDisplayName, "b");
+assertEq(stack.parent, null);
diff --git a/js/src/jit-test/tests/saved-stacks/principals-04.js b/js/src/jit-test/tests/saved-stacks/principals-04.js
new file mode 100644
index 000000000..3a9b57800
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/principals-04.js
@@ -0,0 +1,15 @@
+// Test what happens when a compartment gets a SavedFrame that it doesn't have
+// the principals to access any of its frames.
+
+var low = newGlobal({ principal: 0 });
+var high = newGlobal({ principal: 0xfffff });
+
+low.high = high;
+high.low = low;
+
+high.eval("function a() { return saveStack(1, low); }");
+var stack = low.eval("high.a();")
+
+assertEq(stack.functionDisplayName, null);
+assertEq(stack.parent, null);
+assertEq(stack.toString(), "");
diff --git a/js/src/jit-test/tests/saved-stacks/proxy-handlers.js b/js/src/jit-test/tests/saved-stacks/proxy-handlers.js
new file mode 100644
index 000000000..7ad1f6dc6
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/proxy-handlers.js
@@ -0,0 +1,10 @@
+// Test that we can save stacks with proxy handler frames.
+
+const stack = (function iife() {
+ return (new Proxy({}, {
+ get: function get(t, n, r) { return saveStack(); }
+ })).stack;
+}());
+
+assertEq(stack.functionDisplayName, "get");
+assertEq(stack.parent.functionDisplayName, "iife");
diff --git a/js/src/jit-test/tests/saved-stacks/same-stack.js b/js/src/jit-test/tests/saved-stacks/same-stack.js
new file mode 100644
index 000000000..b82ba1c04
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/same-stack.js
@@ -0,0 +1,12 @@
+// Test that the same saved stack is only ever allocated once.
+
+const stacks = [];
+
+for (let i = 0; i < 10; i++) {
+ stacks.push(saveStack());
+}
+
+const s = stacks.pop();
+for (let stack of stacks) {
+ assertEq(s, stack);
+}
diff --git a/js/src/jit-test/tests/saved-stacks/self-hosted.js b/js/src/jit-test/tests/saved-stacks/self-hosted.js
new file mode 100644
index 000000000..88f8ce200
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/self-hosted.js
@@ -0,0 +1,26 @@
+// Test that we can save stacks with self-hosted function frames in them.
+
+const map = (function () {
+ return [3].map(n => saveStack()).pop();
+}());
+
+assertEq(map.parent.functionDisplayName, "map");
+assertEq(map.parent.source, "self-hosted");
+
+const reduce = (function () {
+ return [3].reduce(() => saveStack(), 3);
+}());
+
+assertEq(reduce.parent.functionDisplayName, "reduce");
+assertEq(reduce.parent.source, "self-hosted");
+
+const forEach = (function () {
+ try {
+ [3].forEach(n => { throw saveStack() });
+ } catch (s) {
+ return s;
+ }
+}());
+
+assertEq(forEach.parent.functionDisplayName, "forEach");
+assertEq(forEach.parent.source, "self-hosted");
diff --git a/js/src/jit-test/tests/saved-stacks/shared-parent-frames.js b/js/src/jit-test/tests/saved-stacks/shared-parent-frames.js
new file mode 100644
index 000000000..c6b4332dd
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/shared-parent-frames.js
@@ -0,0 +1,19 @@
+// Test that parent frames are shared when the older portions of two stacks are
+// the same.
+
+let f1, f2;
+
+function dos() {
+ f1 = saveStack();
+ f2 = saveStack();
+}
+
+(function uno() {
+ dos();
+}());
+
+
+// Different youngest frames.
+assertEq(f1 == f2, false);
+// However the parents should be the same.
+assertEq(f1.parent, f2.parent);
diff --git a/js/src/jit-test/tests/saved-stacks/stacks-are-frozen.js b/js/src/jit-test/tests/saved-stacks/stacks-are-frozen.js
new file mode 100644
index 000000000..b2d125aa4
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/stacks-are-frozen.js
@@ -0,0 +1,17 @@
+// Test that SavedFrame instances are frozen and can't be messed with.
+
+// Strict mode so that mutating frozen objects doesn't silently fail.
+"use strict";
+
+const s = saveStack();
+
+load(libdir + 'asserts.js');
+
+assertThrowsInstanceOf(() => s.source = "fake.url",
+ TypeError);
+
+assertThrowsInstanceOf(() => {
+ Object.defineProperty(s.__proto__, "line", {
+ get: () => 0
+ })
+}, TypeError);
diff --git a/js/src/jit-test/tests/saved-stacks/stringify-with-self-hosted.js b/js/src/jit-test/tests/saved-stacks/stringify-with-self-hosted.js
new file mode 100644
index 000000000..2f867d8f3
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/stringify-with-self-hosted.js
@@ -0,0 +1,8 @@
+// Test that stringify'ing a saved frame with self-hosted parent frames doesn't
+// include the self-hosted parent frame in the output.
+
+const map = (function () {
+ return [3].map(n => saveStack()).pop();
+}());
+
+assertEq(map.toString().includes("@self-hosted:"), false);