@samitouri / QOS-React / commits / 91d097b2c5

fix: rename bottom stack frame (#33680)

`react-stack-bottom-frame` -> `react_stack_bottom_frame`. This survives `@babel/plugin-transform-function-name`, but now frames will be displayed as `at Object.react_stack_bottom_frame (...)` in V8. Checks that were relying on exact function name match were updated to use either `.indexOf()` or `.includes()` For backwards compatibility, both React DevTools and Flight Client will look for both options. I am not so sure about the latter and if React version is locked.

Ruslan Lesiutin committed Jul 1, 2025 at 18:06 UTC 91d097b2c588a0977a7a10ed12512dc8a34e3a5b
9 files changed +48 -41
packages/react-client/src/ReactFlightClient.js
+4 -4
@@ -2947,7 +2947,7 @@ function buildFakeTask(
2947 }
2948
2949 const createFakeJSXCallStack = {
2950 - 'react-stack-bottom-frame': function (
2950 + react_stack_bottom_frame: function (
2951 response: Response,
2952 stack: ReactStackTrace,
2953 environmentName: string,
@@ -2969,7 +2969,7 @@ const createFakeJSXCallStackInDEV: (
2969 environmentName: string,
2970 ) => Error = __DEV__
2971 ? // We use this technique to trick minifiers to preserve the function name.
2972 - (createFakeJSXCallStack['react-stack-bottom-frame'].bind(
2972 + (createFakeJSXCallStack.react_stack_bottom_frame.bind(
2973 createFakeJSXCallStack,
2974 ): any)
2975 : (null: any);
@@ -3083,7 +3083,7 @@ function getCurrentStackInDEV(): string {
3083 }
3084
3085 const replayConsoleWithCallStack = {
3086 - 'react-stack-bottom-frame': function (
3086 + react_stack_bottom_frame: function (
3087 response: Response,
3088 methodName: string,
3089 stackTrace: ReactStackTrace,
@@ -3135,7 +3135,7 @@ const replayConsoleWithCallStackInDEV: (
3135 args: Array<mixed>,
3136 ) => void = __DEV__
3137 ? // We use this technique to trick minifiers to preserve the function name.
3138 - (replayConsoleWithCallStack['react-stack-bottom-frame'].bind(
3138 + (replayConsoleWithCallStack.react_stack_bottom_frame.bind(
3139 replayConsoleWithCallStack,
3140 ): any)
3141 : (null: any);
packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js
+4 -1
@@ -29,7 +29,10 @@ export function formatOwnerStackString(stack: string): string {
29 // Pop the JSX frame.
30 stack = stack.slice(idx + 1);
31 }
32 - idx = stack.indexOf('react-stack-bottom-frame');
32 + idx = stack.indexOf('react_stack_bottom_frame');
33 + if (idx === -1) {
34 + idx = stack.indexOf('react-stack-bottom-frame');
35 + }
36 if (idx !== -1) {
37 idx = stack.lastIndexOf('\n', idx);
38 }
packages/react-devtools-shared/src/backend/utils/index.js
+5 -1
@@ -358,7 +358,11 @@ function collectStackTrace(
358 // We mirror how V8 serializes stack frames and how we later parse them.
359 for (let i = 0; i < structuredStackTrace.length; i++) {
360 const callSite = structuredStackTrace[i];
361 - if (callSite.getFunctionName() === 'react-stack-bottom-frame') {
361 + const name = callSite.getFunctionName();
362 + if (
363 + name.includes('react_stack_bottom_frame') ||
364 + name.includes('react-stack-bottom-frame')
365 + ) {
366 // We pick the last frame that matches before the bottom frame since
367 // that will be immediately inside the component as opposed to some helper.
368 // If we don't find a bottom frame then we bail to string parsing.
packages/react-reconciler/src/ReactFiberCallUserSpace.js
+18 -18
@@ -19,7 +19,7 @@ import {captureCommitPhaseError} from './ReactFiberWorkLoop';
19 // TODO: Consider marking the whole bundle instead of these boundaries.
20
21 const callComponent = {
22 - 'react-stack-bottom-frame': function <Props, Arg, R>(
22 + react_stack_bottom_frame: function <Props, Arg, R>(
23 Component: (p: Props, arg: Arg) => R,
24 props: Props,
25 secondArg: Arg,
@@ -41,7 +41,7 @@ export const callComponentInDEV: <Props, Arg, R>(
41 secondArg: Arg,
42 ) => R = __DEV__
43 ? // We use this technique to trick minifiers to preserve the function name.
44 - (callComponent['react-stack-bottom-frame'].bind(callComponent): any)
44 + (callComponent.react_stack_bottom_frame.bind(callComponent): any)
45 : (null: any);
46
47 interface ClassInstance<R> {
@@ -57,7 +57,7 @@ interface ClassInstance<R> {
57 }
58
59 const callRender = {
60 - 'react-stack-bottom-frame': function <R>(instance: ClassInstance<R>): R {
60 + react_stack_bottom_frame: function <R>(instance: ClassInstance<R>): R {
61 const wasRendering = isRendering;
62 setIsRendering(true);
63 try {
@@ -72,11 +72,11 @@ const callRender = {
72 export const callRenderInDEV: <R>(instance: ClassInstance<R>) => R => R =
73 __DEV__
74 ? // We use this technique to trick minifiers to preserve the function name.
75 - (callRender['react-stack-bottom-frame'].bind(callRender): any)
75 + (callRender.react_stack_bottom_frame.bind(callRender): any)
76 : (null: any);
77
78 const callComponentDidMount = {
79 - 'react-stack-bottom-frame': function (
79 + react_stack_bottom_frame: function (
80 finishedWork: Fiber,
81 instance: ClassInstance<any>,
82 ): void {
@@ -93,13 +93,13 @@ export const callComponentDidMountInDEV: (
93 instance: ClassInstance<any>,
94 ) => void = __DEV__
95 ? // We use this technique to trick minifiers to preserve the function name.
96 - (callComponentDidMount['react-stack-bottom-frame'].bind(
96 + (callComponentDidMount.react_stack_bottom_frame.bind(
97 callComponentDidMount,
98 ): any)
99 : (null: any);
100
101 const callComponentDidUpdate = {
102 - 'react-stack-bottom-frame': function (
102 + react_stack_bottom_frame: function (
103 finishedWork: Fiber,
104 instance: ClassInstance<any>,
105 prevProps: Object,
@@ -122,13 +122,13 @@ export const callComponentDidUpdateInDEV: (
122 snaphot: Object,
123 ) => void = __DEV__
124 ? // We use this technique to trick minifiers to preserve the function name.
125 - (callComponentDidUpdate['react-stack-bottom-frame'].bind(
125 + (callComponentDidUpdate.react_stack_bottom_frame.bind(
126 callComponentDidUpdate,
127 ): any)
128 : (null: any);
129
130 const callComponentDidCatch = {
131 - 'react-stack-bottom-frame': function (
131 + react_stack_bottom_frame: function (
132 instance: ClassInstance<any>,
133 errorInfo: CapturedValue<mixed>,
134 ): void {
@@ -145,13 +145,13 @@ export const callComponentDidCatchInDEV: (
145 errorInfo: CapturedValue<mixed>,
146 ) => void = __DEV__
147 ? // We use this technique to trick minifiers to preserve the function name.
148 - (callComponentDidCatch['react-stack-bottom-frame'].bind(
148 + (callComponentDidCatch.react_stack_bottom_frame.bind(
149 callComponentDidCatch,
150 ): any)
151 : (null: any);
152
153 const callComponentWillUnmount = {
154 - 'react-stack-bottom-frame': function (
154 + react_stack_bottom_frame: function (
155 current: Fiber,
156 nearestMountedAncestor: Fiber | null,
157 instance: ClassInstance<any>,
@@ -170,13 +170,13 @@ export const callComponentWillUnmountInDEV: (
170 instance: ClassInstance<any>,
171 ) => void = __DEV__
172 ? // We use this technique to trick minifiers to preserve the function name.
173 - (callComponentWillUnmount['react-stack-bottom-frame'].bind(
173 + (callComponentWillUnmount.react_stack_bottom_frame.bind(
174 callComponentWillUnmount,
175 ): any)
176 : (null: any);
177
178 const callCreate = {
179 - 'react-stack-bottom-frame': function (
179 + react_stack_bottom_frame: function (
180 effect: Effect,
181 ): (() => void) | {...} | void | null {
182 const create = effect.create;
@@ -189,11 +189,11 @@ const callCreate = {
189
190 export const callCreateInDEV: (effect: Effect) => (() => void) | void = __DEV__
191 ? // We use this technique to trick minifiers to preserve the function name.
192 - (callCreate['react-stack-bottom-frame'].bind(callCreate): any)
192 + (callCreate.react_stack_bottom_frame.bind(callCreate): any)
193 : (null: any);
194
195 const callDestroy = {
196 - 'react-stack-bottom-frame': function (
196 + react_stack_bottom_frame: function (
197 current: Fiber,
198 nearestMountedAncestor: Fiber | null,
199 destroy: () => void,
@@ -212,11 +212,11 @@ export const callDestroyInDEV: (
212 destroy: (() => void) | (({...}) => void),
213 ) => void = __DEV__
214 ? // We use this technique to trick minifiers to preserve the function name.
215 - (callDestroy['react-stack-bottom-frame'].bind(callDestroy): any)
215 + (callDestroy.react_stack_bottom_frame.bind(callDestroy): any)
216 : (null: any);
217
218 const callLazyInit = {
219 - 'react-stack-bottom-frame': function (lazy: LazyComponent<any, any>): any {
219 + react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any {
220 const payload = lazy._payload;
221 const init = lazy._init;
222 return init(payload);
@@ -225,5 +225,5 @@ const callLazyInit = {
225
226 export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__
227 ? // We use this technique to trick minifiers to preserve the function name.
228 - (callLazyInit['react-stack-bottom-frame'].bind(callLazyInit): any)
228 + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any)
229 : (null: any);
packages/react-server/src/ReactFizzCallUserSpace.js
+6 -6
@@ -13,7 +13,7 @@ import type {LazyComponent} from 'react/src/ReactLazy';
13 // TODO: Consider marking the whole bundle instead of these boundaries.
14
15 const callComponent = {
16 - 'react-stack-bottom-frame': function <Props, Arg, R>(
16 + react_stack_bottom_frame: function <Props, Arg, R>(
17 Component: (p: Props, arg: Arg) => R,
18 props: Props,
19 secondArg: Arg,
@@ -28,7 +28,7 @@ export const callComponentInDEV: <Props, Arg, R>(
28 secondArg: Arg,
29 ) => R = __DEV__
30 ? // We use this technique to trick minifiers to preserve the function name.
31 - (callComponent['react-stack-bottom-frame'].bind(callComponent): any)
31 + (callComponent.react_stack_bottom_frame.bind(callComponent): any)
32 : (null: any);
33
34 interface ClassInstance<R> {
@@ -36,7 +36,7 @@ interface ClassInstance<R> {
36 }
37
38 const callRender = {
39 - 'react-stack-bottom-frame': function <R>(instance: ClassInstance<R>): R {
39 + react_stack_bottom_frame: function <R>(instance: ClassInstance<R>): R {
40 return instance.render();
41 },
42 };
@@ -44,11 +44,11 @@ const callRender = {
44 export const callRenderInDEV: <R>(instance: ClassInstance<R>) => R => R =
45 __DEV__
46 ? // We use this technique to trick minifiers to preserve the function name.
47 - (callRender['react-stack-bottom-frame'].bind(callRender): any)
47 + (callRender.react_stack_bottom_frame.bind(callRender): any)
48 : (null: any);
49
50 const callLazyInit = {
51 - 'react-stack-bottom-frame': function (lazy: LazyComponent<any, any>): any {
51 + react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any {
52 const payload = lazy._payload;
53 const init = lazy._init;
54 return init(payload);
@@ -57,5 +57,5 @@ const callLazyInit = {
57
58 export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__
59 ? // We use this technique to trick minifiers to preserve the function name.
60 - (callLazyInit['react-stack-bottom-frame'].bind(callLazyInit): any)
60 + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any)
61 : (null: any);
packages/react-server/src/ReactFlightCallUserSpace.js
+6 -6
@@ -19,7 +19,7 @@ import {setCurrentOwner} from './flight/ReactFlightCurrentOwner';
19 // TODO: Consider marking the whole bundle instead of these boundaries.
20
21 const callComponent = {
22 - 'react-stack-bottom-frame': function <Props, R>(
22 + react_stack_bottom_frame: function <Props, R>(
23 Component: (p: Props, arg: void) => R,
24 props: Props,
25 componentDebugInfo: ReactComponentInfo,
@@ -41,11 +41,11 @@ export const callComponentInDEV: <Props, R>(
41 componentDebugInfo: ReactComponentInfo,
42 ) => R = __DEV__
43 ? // We use this technique to trick minifiers to preserve the function name.
44 - (callComponent['react-stack-bottom-frame'].bind(callComponent): any)
44 + (callComponent.react_stack_bottom_frame.bind(callComponent): any)
45 : (null: any);
46
47 const callLazyInit = {
48 - 'react-stack-bottom-frame': function (lazy: LazyComponent<any, any>): any {
48 + react_stack_bottom_frame: function (lazy: LazyComponent<any, any>): any {
49 const payload = lazy._payload;
50 const init = lazy._init;
51 return init(payload);
@@ -54,11 +54,11 @@ const callLazyInit = {
54
55 export const callLazyInitInDEV: (lazy: LazyComponent<any, any>) => any = __DEV__
56 ? // We use this technique to trick minifiers to preserve the function name.
57 - (callLazyInit['react-stack-bottom-frame'].bind(callLazyInit): any)
57 + (callLazyInit.react_stack_bottom_frame.bind(callLazyInit): any)
58 : (null: any);
59
60 const callIterator = {
61 - 'react-stack-bottom-frame': function (
61 + react_stack_bottom_frame: function (
62 iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,
63 progress: (
64 entry:
@@ -81,5 +81,5 @@ export const callIteratorInDEV: (
81 error: (reason: mixed) => void,
82 ) => void = __DEV__
83 ? // We use this technique to trick minifiers to preserve the function name.
84 - (callIterator['react-stack-bottom-frame'].bind(callIterator): any)
84 + (callIterator.react_stack_bottom_frame.bind(callIterator): any)
85 : (null: any);
packages/react-server/src/ReactFlightStackConfigV8.js
+2 -2
@@ -59,7 +59,7 @@ function collectStackTrace(
59 for (let i = framesToSkip; i < structuredStackTrace.length; i++) {
60 const callSite = structuredStackTrace[i];
61 let name = callSite.getFunctionName() || '<anonymous>';
62 - if (name === 'react-stack-bottom-frame') {
62 + if (name.includes('react_stack_bottom_frame')) {
63 // Skip everything after the bottom frame since it'll be internals.
64 break;
65 } else if (callSite.isNative()) {
@@ -174,7 +174,7 @@ export function parseStackTrace(
174 // don't want/need.
175 stack = stack.slice(29);
176 }
177 - let idx = stack.indexOf('react-stack-bottom-frame');
177 + let idx = stack.indexOf('react_stack_bottom_frame');
178 if (idx !== -1) {
179 idx = stack.lastIndexOf('\n', idx);
180 }
packages/react/src/jsx/ReactJSXElement.js
+2 -2
@@ -66,7 +66,7 @@ function UnknownOwner() {
66 return (() => Error('react-stack-top-frame'))();
67 }
68 const createFakeCallStack = {
69 - 'react-stack-bottom-frame': function (callStackForError) {
69 + react_stack_bottom_frame: function (callStackForError) {
70 return callStackForError();
71 },
72 };
@@ -81,7 +81,7 @@ if (__DEV__) {
81 didWarnAboutElementRef = {};
82
83 // We use this technique to trick minifiers to preserve the function name.
84 - unknownOwnerDebugStack = createFakeCallStack['react-stack-bottom-frame'].bind(
84 + unknownOwnerDebugStack = createFakeCallStack.react_stack_bottom_frame.bind(
85 createFakeCallStack,
86 UnknownOwner,
87 )();
packages/shared/ReactOwnerStackFrames.js
+1 -1
@@ -24,7 +24,7 @@ export function formatOwnerStack(error: Error): string {
24 // Pop the JSX frame.
25 stack = stack.slice(idx + 1);
26 }
27 - idx = stack.indexOf('react-stack-bottom-frame');
27 + idx = stack.indexOf('react_stack_bottom_frame');
28 if (idx !== -1) {
29 idx = stack.lastIndexOf('\n', idx);
30 }