aboutsummaryrefslogtreecommitdiff
path: root/test/js/node/v8/capture-stack-trace.test.js
diff options
context:
space:
mode:
authorGravatar Keyhan Vakil <kvakil@sylph.kvakil.me> 2023-06-19 23:28:40 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-19 23:28:40 -0700
commitbdbb637b3d870f1955cadd342eeae0147f50c3de (patch)
tree41ef0eaadf53f5fbc9cce96bfec0bada3c53b13d /test/js/node/v8/capture-stack-trace.test.js
parente9e0e051569d3858cfc18b21a6aa6d1b7184f7e7 (diff)
downloadbun-bdbb637b3d870f1955cadd342eeae0147f50c3de.tar.gz
bun-bdbb637b3d870f1955cadd342eeae0147f50c3de.tar.zst
bun-bdbb637b3d870f1955cadd342eeae0147f50c3de.zip
implement more of V8's stack trace API (#3359)
- fix source map positions for getLineNumber / getColumnNumber - fix return value getting coerced to a string - implement CallFrame.p.toString - add tests for getFunction, getThis, isConstructor, isNative, toString, getLineNumber, getColumnNumber still not implemented: - isPromiseAll/getPromiseIndex - getEvalOrigin - getScriptHash - getPosition - getEnclosingColumnNumber/getEnclosingLineNumber - isAsync - accessing Error.stack should call prepareStackTrace still broken: - isEval: often returns false when it should return true - isToplevel: often returns true when it should return false Refs: https://v8.dev/docs/stack-trace-api Refs: v8/src/objects/call-site-info.cc Fixes: https://github.com/oven-sh/bun/issues/2883
Diffstat (limited to 'test/js/node/v8/capture-stack-trace.test.js')
-rw-r--r--test/js/node/v8/capture-stack-trace.test.js152
1 files changed, 152 insertions, 0 deletions
diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js
index 789503960..75947a001 100644
--- a/test/js/node/v8/capture-stack-trace.test.js
+++ b/test/js/node/v8/capture-stack-trace.test.js
@@ -301,3 +301,155 @@ test("prepare stack trace call sites", () => {
f1();
});
+
+test("sanity check", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ let e = new Error("bad error!");
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => {
+ // getThis returns undefined in strict mode
+ expect(s[0].getThis()).toBe(undefined);
+ expect(s[0].getTypeName()).toBe('undefined');
+ // getFunction returns undefined in strict mode
+ expect(s[0].getFunction()).toBe(undefined);
+ expect(s[0].getFunctionName()).toBe('f3');
+ expect(s[0].getMethodName()).toBe('f3');
+ expect(typeof s[0].getLineNumber()).toBe('number');
+ expect(typeof s[0].getColumnNumber()).toBe('number');
+ expect(s[0].getFileName().includes('capture-stack-trace.test.js')).toBe(true);
+
+ expect(s[0].getEvalOrigin()).toBe(undefined);
+ expect(s[0].isToplevel()).toBe(true);
+ expect(s[0].isEval()).toBe(false);
+ expect(s[0].isNative()).toBe(false);
+ expect(s[0].isConstructor()).toBe(false);
+ expect(s[0].isAsync()).toBe(false);
+ expect(s[0].isPromiseAll()).toBe(false);
+ expect(s[0].getPromiseIndex()).toBe(null);
+
+ };
+ Error.captureStackTrace(e);
+ expect(e.stack === undefined).toBe(true);
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ }
+
+ f1();
+});
+
+test("CallFrame.p.getThis\getFunction: works in sloppy mode", () => {
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ const sloppyFn = new Function('let e=new Error();Error.captureStackTrace(e);return e.stack');
+ sloppyFn.displayName = 'sloppyFnWow';
+ const that = {};
+
+ Error.prepareStackTrace = (e, s) => {
+ expect(s[0].getThis()).toBe(that);
+ expect(s[0].getFunction()).toBe(sloppyFn);
+ expect(s[0].getFunctionName()).toBe(sloppyFn.displayName);
+ expect(s[0].isToplevel()).toBe(false);
+ // TODO: This should be true.
+ expect(s[0].isEval()).toBe(false);
+
+ // Strict-mode functions shouldn't have getThis or getFunction
+ // available.
+ expect(s[1].getThis()).toBe(undefined);
+ expect(s[1].getFunction()).toBe(undefined);
+ };
+
+ sloppyFn.call(that);
+
+ Error.prepareStackTrace = prevPrepareStackTrace;
+});
+
+test("CallFrame.p.getThis\getFunction: strict/sloppy mode interaction", () => {
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+
+ const strictFn = new Function('"use strict";let e=new Error();Error.captureStackTrace(e);return e.stack');
+ const sloppyFn = new Function('x', 'x()');
+ const that = {};
+
+ Error.prepareStackTrace = (e, s) => {
+ // The first strict mode function encounted during stack unwinding
+ // stops subsequent frames from having getThis\getFunction.
+ for (const t of s) {
+ expect(t.getThis()).toBe(undefined);
+ expect(t.getFunction()).toBe(undefined);
+ }
+ };
+
+ sloppyFn.call(that, strictFn);
+
+ Error.prepareStackTrace = prevPrepareStackTrace;
+});
+
+test("CallFrame.p.isConstructor", () => {
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+
+ class C {
+ constructor() {
+ Error.captureStackTrace(new Error(''));
+ }
+ }
+
+ Error.prepareStackTrace = (e, s) => {
+ expect(s[0].isConstructor()).toBe(true);
+ // TODO: should be false: this is an instance of C
+ expect(s[0].isToplevel()).toBe(true);
+ // TODO: should return the class name
+ // expect(s[0].getTypeName()).toBe('C');
+
+ expect(s[1].isConstructor()).toBe(false);
+ expect(s[1].isToplevel()).toBe(true);
+ };
+ new C();
+ Error.prepareStackTrace = prevPrepareStackTrace;
+});
+
+test("CallFrame.p.isNative", () => {
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => {
+ expect(s[0].isNative()).toBe(false);
+ expect(s[1].isNative()).toBe(true);
+ };
+ [1, 2].sort(() => {
+ Error.captureStackTrace(new Error(''));
+ return 0;
+ });
+ Error.prepareStackTrace = prevPrepareStackTrace;
+});
+
+test("return non-strings from Error.prepareStackTrace", () => {
+ // This behavior is allowed by V8 and used by the node-depd npm package.
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => s;
+ const e = new Error();
+ Error.captureStackTrace(e);
+ expect(Array.isArray(e.stack)).toBe(true);
+ Error.prepareStackTrace = prevPrepareStackTrace;
+});
+
+test("CallFrame.p.toString", () => {
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => s;
+ const e = new Error();
+ Error.captureStackTrace(e);
+ expect(e.stack[0].toString().includes("<anonymous>")).toBe(true);
+});
+
+test.todo("err.stack should invoke prepareStackTrace", () => {
+ // This is V8's behavior.
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ let wasCalled = false;
+ Error.prepareStackTrace = (e, s) => { wasCalled = true; };
+ const e = new Error();
+ e.stack;
+ expect(wasCalled).toBe(true);
+});