diff options
author | 2023-06-19 23:28:40 -0700 | |
---|---|---|
committer | 2023-06-19 23:28:40 -0700 | |
commit | bdbb637b3d870f1955cadd342eeae0147f50c3de (patch) | |
tree | 41ef0eaadf53f5fbc9cce96bfec0bada3c53b13d /test/js/node/v8/capture-stack-trace.test.js | |
parent | e9e0e051569d3858cfc18b21a6aa6d1b7184f7e7 (diff) | |
download | bun-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.js | 152 |
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); +}); |