fix[devtools]: feature-check structure stack trace methods (#35293)
`Error.prepareStackTrace` is non-standard feature and not all JavaScript runtimes implement the methods that we are using in React DevTools backend. This PR adds additional checks for the presence of the methods that we are using.
Ruslan Lesiutin committed
Dec 10, 2025 at 19:21 UTC
5a970933c0aa5c5cfb9793cce49f3a282b191716
1 file changed
+47
-13
packages/react-devtools-shared/src/backend/utils/parseStackTrace.js
+47
-13
@@ -154,41 +154,73 @@ function collectStackTrace(
154
// We mirror how V8 serializes stack frames and how we later parse them.
155
for (let i = framesToSkip; i < structuredStackTrace.length; i++) {
156
const callSite = structuredStackTrace[i];
157
- let name = callSite.getFunctionName() || '<anonymous>';
157
+ let name =
158
+ // $FlowFixMe[method-unbinding]
159
+ typeof callSite.getFunctionName === 'function'
160
+ ? callSite.getFunctionName() || '<anonymous>'
161
+ : '';
162
if (
163
name.includes('react_stack_bottom_frame') ||
164
name.includes('react-stack-bottom-frame')
165
) {
166
// Skip everything after the bottom frame since it'll be internals.
167
break;
164
- } else if (callSite.isNative()) {
165
- // $FlowFixMe[prop-missing]
166
- const isAsync = callSite.isAsync();
168
+ // $FlowFixMe[method-unbinding]
169
+ } else if (typeof callSite.isNative === 'function' && callSite.isNative()) {
170
+ const isAsync =
171
+ // $FlowFixMe[prop-missing]
172
+ // $FlowFixMe[incompatible-use]
173
+ typeof callSite.isAsync === 'function' && callSite.isAsync();
174
result.push([name, '', 0, 0, 0, 0, isAsync]);
175
} else {
176
// We encode complex function calls as if they're part of the function
177
// name since we cannot simulate the complex ones and they look the same
178
// as function names in UIs on the client as well as stacks.
172
- if (callSite.isConstructor()) {
179
+ if (
180
+ // $FlowFixMe[method-unbinding]
181
+ typeof callSite.isConstructor === 'function' &&
182
+ callSite.isConstructor()
183
+ ) {
184
name = 'new ' + name;
174
- } else if (!callSite.isToplevel()) {
185
+ } else if (
186
+ // $FlowFixMe[method-unbinding]
187
+ typeof callSite.isToplevel === 'function' &&
188
+ !callSite.isToplevel()
189
+ ) {
190
name = getMethodCallName(callSite);
191
}
192
if (name === '<anonymous>') {
193
name = '';
194
}
180
- let filename = callSite.getScriptNameOrSourceURL() || '<anonymous>';
195
+ let filename =
196
+ // $FlowFixMe[method-unbinding]
197
+ typeof callSite.getScriptNameOrSourceURL === 'function'
198
+ ? callSite.getScriptNameOrSourceURL() || '<anonymous>'
199
+ : '';
200
if (filename === '<anonymous>') {
201
filename = '';
183
- if (callSite.isEval()) {
184
- const origin = callSite.getEvalOrigin();
202
+ // $FlowFixMe[method-unbinding]
203
+ if (typeof callSite.isEval === 'function' && callSite.isEval()) {
204
+ const origin =
205
+ // $FlowFixMe[method-unbinding]
206
+ typeof callSite.getEvalOrigin === 'function'
207
+ ? callSite.getEvalOrigin()
208
+ : null;
209
if (origin) {
210
filename = origin.toString() + ', <anonymous>';
211
}
212
}
213
}
190
- const line = callSite.getLineNumber() || 0;
191
- const col = callSite.getColumnNumber() || 0;
214
+ const line =
215
+ // $FlowFixMe[method-unbinding]
216
+ (typeof callSite.getLineNumber === 'function' &&
217
+ callSite.getLineNumber()) ||
218
+ 0;
219
+ const col =
220
+ // $FlowFixMe[method-unbinding]
221
+ (typeof callSite.getColumnNumber === 'function' &&
222
+ callSite.getColumnNumber()) ||
223
+ 0;
224
const enclosingLine: number =
225
// $FlowFixMe[prop-missing]
226
typeof callSite.getEnclosingLineNumber === 'function'
@@ -199,8 +231,10 @@ function collectStackTrace(
231
typeof callSite.getEnclosingColumnNumber === 'function'
232
? (callSite: any).getEnclosingColumnNumber() || 0
233
: 0;
202
- // $FlowFixMe[prop-missing]
203
- const isAsync = callSite.isAsync();
234
+ const isAsync =
235
+ // $FlowFixMe[prop-missing]
236
+ // $FlowFixMe[incompatible-use]
237
+ typeof callSite.isAsync === 'function' && callSite.isAsync();
238
result.push([
239
name,
240
filename,