@samitouri / QOS-React-2 / commits / c0c37063e2

[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) {