[Flight] Restore original function name in dev, server callstacks served with `unsafe-eval` (#35650)
Sebastian "Sebbie" Silbermann committed
Jan 28, 2026 at 18:41 UTC
c0c37063e2cd8976d839998573012d48d303ada0
2 files changed
+58
packages/react-client/src/ReactFlightClient.js
+8
@@ -3730,6 +3730,14 @@ function createFakeFunction<T>(
3730
fn = function (_) {
3731
return _();
3732
};
3733
+ // Using the usual {[name]: _() => _()}.bind() trick to avoid minifiers
3734
+ // doesn't work here since this will produce `Object.*` names.
3735
+ Object.defineProperty(
3736
+ fn,
3737
+ // $FlowFixMe[cannot-write] -- `name` is configurable though.
3738
+ 'name',
3739
+ {value: name},
3740
+ );
3741
}
3742
return fn;
3743
}
packages/react-client/src/__tests__/ReactFlight-test.js
+50
@@ -3677,6 +3677,56 @@ describe('ReactFlight', () => {
3677
expect(caughtError.digest).toBe('digest("my-error")');
3678
});
3679
3680
+ it('can transport function names in stackframes in dev even without eval', async () => {
3681
+ function a() {
3682
+ return b();
3683
+ }
3684
+ function b() {
3685
+ return c();
3686
+ }
3687
+ function c() {
3688
+ return new Error('boom');
3689
+ }
3690
+
3691
+ // eslint-disable-next-line no-eval
3692
+ const previousEval = globalThis.eval.bind(globalThis);
3693
+ // eslint-disable-next-line no-eval
3694
+ globalThis.eval = () => {
3695
+ throw new Error('eval is disabled');
3696
+ };
3697
+
3698
+ try {
3699
+ const transport = ReactNoopFlightServer.render(
3700
+ {model: a()},
3701
+ {onError: () => 'digest'},
3702
+ );
3703
+
3704
+ const root = await ReactNoopFlightClient.read(transport);
3705
+ const receivedError = await root.model;
3706
+
3707
+ if (__DEV__) {
3708
+ const normalizedErrorStack = normalizeCodeLocInfo(
3709
+ receivedError.stack.split('\n').slice(0, 4).join('\n'),
3710
+ );
3711
+
3712
+ expect(normalizedErrorStack).toEqual(
3713
+ 'Error: boom' +
3714
+ '\n in c (at **)' +
3715
+ '\n in b (at **)' +
3716
+ '\n in a (at **)',
3717
+ );
3718
+ } else {
3719
+ expect(receivedError.message).toEqual(
3720
+ 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
3721
+ );
3722
+ expect(receivedError).not.toHaveProperty('digest');
3723
+ }
3724
+ } finally {
3725
+ // eslint-disable-next-line no-eval
3726
+ globalThis.eval = previousEval;
3727
+ }
3728
+ });
3729
+
3730
// @gate __DEV__ && enableComponentPerformanceTrack
3731
it('can render deep but cut off JSX in debug info', async () => {
3732
function createDeepJSX(n) {