| // 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 xToCall = x.bind(undefined, saveStack(0, low)); |
| |
| let stackX = callFunctionWithAsyncStack(xToCall, saveStack(), "VtoX"); |
| |
| print("high.checkVisibleStack(stackX)"); |
| checkVisibleStack(stackX, [ |
| { name: "x", 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(); |