Skip to content

Instantly share code, notes, and snippets.

@sebmarkbage
Last active August 11, 2020 04:03
Show Gist options
  • Save sebmarkbage/bdefa100f19345229d526d0fdd22830f to your computer and use it in GitHub Desktop.
Save sebmarkbage/bdefa100f19345229d526d0fdd22830f to your computer and use it in GitHub Desktop.
Custom Stack Frames
type FrameScope = {
[key:string]: mixed
};
type StackFrame = {
name?: string,
fileName?: string,
lineNumber?: number,
columnNumber?: number,
scope?: FrameScope
};
type StackFrames = Array<StackFrame>;
type ExtendedConsoleAPI = {
...ConsoleAPI,
stack(frames: StackFrames) : void,
endStack() : void
};

The current execution context has a "custom stack" context with two internal slots [[Custom Stack Start]] and [[Custom Stack Override]].

console.stack - Assert that the first argument is an array. Store the current stack frame context at the VM level (same as new Error().stack) in the current execution context's [[Custom Stack Start]] slot. Store the first argument array in the current execution context's [[Custom Stack Override]] slot.

console.endStack - Clear the [[Custom Stack Start]] and [[Custom Stack Override]] stacks.

console.trace, console.warn, console.error etc. - For any function that logs stacks associated with the call. If there is something in the [[Custom Stack Start]] slot. Find the current stack from the current execution context. Find the common ancestor between [[Custom Stack Start]] and the current stack. Remove all the common ancestors. Replace them with [[Custom Stack Override]] in the root of the stack.

If a break point happens, e.g. due to an error being thrown, debugger call or custom break points. If there is something in the [[Custom Stack Start]] slot, use the same mechanism as console.log to determine the current stack to visualize in a debugger instead of the native stack. The scope property contains an object. Every "own" property is considered to be a variable with that name in scope of that stack frame. Useful for debugging.

TODO: Consider what should happen to new Error().stack if the error is created within a custom scope.

function foo(x) {
if (!x) {
console.warn('You must provide the x argument.');
}
}
function bar(x) {
return {
stack: [
{
name: 'bar',
fileName: 'Bar.js',
lineNumber: 10,
columnNumber: 5,
}
],
callback: foo.bind(null, x)
};
}
function invokeCallback(continuation) {
console.stack(continuation.stack);
continuation.callback();
console.endStack();
}
var continuation = bar(false);
invokeCallback(continuation);
// Logs:
// You must provide the x argument.
// at foo (Example.js:3:4)
// at bar (Bar.js:10:5)
@sebmarkbage
Copy link
Author

It should be easy to forward a stack from one place to another.

E.g. it would be nice if I could do something like:

function foo() {
  return new Error();
}
function bar() {
  return new Error();
}
var e1 = foo();
function later(e) {
  console.stack(e.stack);
  var nextStack = bar();
  console.endStack();
  return nextStack;
}
var e2 = later(e1);

function warn(e) {
  console.stack(e.stack);
  console.warn('Warning');
  console.endStack();
}

warn(e2);

And have that pick up the stack as:

Warning
    at bar (<unknown>:5:10)
    at foo (<unknown>:2:10)

@sebmarkbage
Copy link
Author

We could have console.stack(new Error().stack) accept a string of the same format as the engine specific stack format. That would require new Error().stack to be overridden by console.stack so that they can be chained like this.

Another alternative would be to have a different way of extracting a stack. E.g. console.snapshotStack() that would give you the same thing as new Error().stack but it would be override aware.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment