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
}