cjs to esm. start testing. reentrancy bug found and fixed
MODIFY
packages/error-stack-shim/src/getStack.js
packages/error-stack-shim/src/getStack.js
-/* global require module */
-
// Shim https://tc39.es/proposal-error-stacks
-// import harden from '@agoric/harden';
-const harden = Object.freeze;
-
-// import { getV8StackFramesUsing } from './v8StackFrames';
-// import { getScrapedStackFramesUsing } from './scrapedStackFrames';
-const { getV8StackFramesUsing } = require('./v8StackFrames');
-const { getScrapedStackFramesUsing } = require('./scrapedStackFrames');
+import harden from '@agoric/harden';
+import { getV8StackFramesUsing } from './v8StackFrames';
+import { getScrapedStackFramesUsing } from './scrapedStackFrames';
// Reconcile proposal:
// Is normative optional, but "absence" should be space-returning
@@ -133,5 +127,4 @@ function getStackString(error) {
return getStack(error).string;
}
-// export { getStack, getStackString };
-module.exports = { getStack, getStackString };
+export { getStack, getStackString };

MODIFY
packages/error-stack-shim/src/scrapedStackFrames.js
packages/error-stack-shim/src/scrapedStackFrames.js
-/* global module */
-
// Platform dependent portion of error stack shim, for unreliably
// scraping the error stack string to produce the proposed JSON
// representation.
@@ -186,5 +184,4 @@ function getScrapedStackFramesUsing(getRawStackString) {
return getScrapedStackFrames;
}
-// export { getScrapedStackFramesUsing, lineToFrame };
-module.exports = { getScrapedStackFramesUsing, lineToFrame };
+export { getScrapedStackFramesUsing, lineToFrame };

MODIFY
packages/error-stack-shim/src/v8StackFrames.js
packages/error-stack-shim/src/v8StackFrames.js
-/* global require module */
-
// v8 specific portion of error stack shim
// Assumes https://v8.dev/docs/stack-trace-api
-// import { lineToFrame } from './scrapedStackFrames';
-const { lineToFrame } = require('./scrapedStackFrames');
+import { lineToFrame } from './scrapedStackFrames';
const { getOwnPropertyDescriptor, apply } = Reflect;
@@ -16,11 +13,13 @@ function getV8StackFramesUsing(UnsafeError) {
delete UnsafeError.captureStackTrace;
const ssts = new WeakMap(); // error -> sst
+ const framesMemo = new WeakMap(); // error -> frames
UnsafeError.prepareStackTrace = (error, sst) => {
ssts.set(error, sst);
// See https://bugs.chromium.org/p/v8/issues/detail?id=9386
+ // See NOTE below about the reentrancy hazard we need to beware of.
const desc = getOwnPropertyDescriptor(Error.prototype, 'stack');
return apply(desc.get, error, []);
};
@@ -55,7 +54,7 @@ function getV8StackFramesUsing(UnsafeError) {
function getV8StackFrames(error) {
if (Object(error) !== error) {
- return undefined;
+ throw new TypeError('Error object expected');
}
let sst = ssts.get(error);
if (sst === undefined && error instanceof Error) {
@@ -64,13 +63,29 @@ function getV8StackFramesUsing(UnsafeError) {
const ignore = error.stack;
sst = ssts.get(error);
}
+ // NOTE: Because of our workaround of
+ // https://bugs.chromium.org/p/v8/issues/detail?id=9386 in
+ // prepareStackTrace above, the "error.stack" expression
+ // immediately above, which triggers prepareStackTrace, might
+ // call getStackString which calls ... which calls
+ // getV8StackFrames, reentering it within the outer
+ // execution. This is why we wait till this point to check the
+ // framesMemo. In this reentrancy case, the inner call will set
+ // the memo, returning to the outer call which proceeds at this
+ // point, finding that the error is already in the memo.
+ let frames = framesMemo.get(error);
+ if (frames) {
+ return frames;
+ }
if (sst === undefined) {
return [];
}
- return sst.map(callSiteToFrame);
+ frames = sst.map(callSiteToFrame);
+ framesMemo.set(error, frames);
+ ssts.delete(error);
+ return frames;
}
return getV8StackFrames;
}
-// export { getV8StackFramesUsing };
-module.exports = { getV8StackFramesUsing };
+export { getV8StackFramesUsing };

DELETE
packages/error-stack-shim/test/sample-test.js
-import { test } from 'tape-promise/tape';
-
-test('unique test description', t => {
- t.true('various tests');
- t.end();
-});

ADD
packages/error-stack-shim/test/test.js
+import { test } from 'tape-promise/tape';
+
+import { getStack, getStackString } from '../src/getStack';
+
+test('getStack well formed', t => {
+ const err1 = new Error('foo');
+ const stack = getStack(err1);
+
+ t.true(Array.isArray(stack.frames));
+ t.true(typeof stack.string === 'string');
+ t.equal(stack.string, getStackString(err1));
+ t.equal(stack.string, err1.stack);
+
+ console.log('error: ', err1);
+ console.log('error.stack: ', err1.stack);
+ console.log('trace: ', stack.string);
+ console.log('json: ', JSON.stringify(stack.frames[0], undefined, ' '));
+
+ t.end();
+});

cjs to esm. start testing. reentrancy bug found and fixed