Repository: facebook/react
Pull request: [Flight] Restore original function name in dev, server callstacks served with `unsafe-eval`

## Summary

Follow-up to https://github.com/facebook/react/pull/29671

React uses `eval` with React Server Components to create fake stacks (e.g. to reconstruct Server stacks). In CSP environments, `eval` may not be available unless `unsafe-eval` is set.

We already degraded gracefully without `eval` without restoring the function name. We can restore the function name though. Usually we use `{[name]: (_) => _()}` but that is badly minified by Closure. The usual trick to use `bind` produces bad names though e.g. `function a() {}` would now have the name `Object.a`. 

Instead we define the `name` on the function. This doesn't work everywhere either. This should be acceptable since we're already in fallback mode and try-catch in case of even more misconfigured environments.

## How did you test this change?

- added test


Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -3730,6 +3730,14 @@ function createFakeFunction<T>(
     fn = function (_) {
       return _();
     };
+    // Using the usual {[name]: _() => _()}.bind() trick to avoid minifiers
+    // doesn't work here since this will produce `Object.*` names.
+    Object.defineProperty(
+      fn,
+      // $FlowFixMe[cannot-write] -- `name` is configurable though.
+      'name',
+      {value: name},
+    );
   }
   return fn;
 }

filename: packages/react-client/src/__tests__/ReactFlight-test.js
patch: @@ -3677,6 +3677,56 @@ describe('ReactFlight', () => {
     expect(caughtError.digest).toBe('digest("my-error")');
   });
 
+  it('can transport function names in stackframes in dev even without eval', async () => {
+    function a() {
+      return b();
+    }
+    function b() {
+      return c();
+    }
+    function c() {
+      return new Error('boom');
+    }
+
+    // eslint-disable-next-line no-eval
+    const previousEval = globalThis.eval.bind(globalThis);
+    // eslint-disable-next-line no-eval
+    globalThis.eval = () => {
+      throw new Error('eval is disabled');
+    };
+
+    try {
+      const transport = ReactNoopFlightServer.render(
+        {model: a()},
+        {onError: () => 'digest'},
+      );
+
+      const root = await ReactNoopFlightClient.read(transport);
+      const receivedError = await root.model;
+
+      if (__DEV__) {
+        const normalizedErrorStack = normalizeCodeLocInfo(
+          receivedError.stack.split('\n').slice(0, 4).join('\n'),
+        );
+
+        expect(normalizedErrorStack).toEqual(
+          'Error: boom' +
+            '\n    in c (at **)' +
+            '\n    in b (at **)' +
+            '\n    in a (at **)',
+        );
+      } else {
+        expect(receivedError.message).toEqual(
+          '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.',
+        );
+        expect(receivedError).not.toHaveProperty('digest');
+      }
+    } finally {
+      // eslint-disable-next-line no-eval
+      globalThis.eval = previousEval;
+    }
+  });
+
   // @gate __DEV__  && enableComponentPerformanceTrack
   it('can render deep but cut off JSX in debug info', async () => {
     function createDeepJSX(n) {