Skip to content

Commit 6e29479

Browse files
authored
[devtools] allow non-coercible objects in formatConsoleArgumentsToSingleString (#31444)
## Summary We have been getting unhandled `TypeError: Cannot convert object to primitive value` errors in development that only occur when using devtools. I tracked it down to `console.error()` calls coming from Apollo Client where one of the arguments is an object without a prototype (created with `Object.create(null)`). This causes `formatConsoleArgumentsToSingleString()` in React's devtools to error as the function does not defend against `String()` throwing an error. My attempted fix is to introduce a `safeToString` function (naming suggestions appreciated) which expects `String()` to throw on certain object and in that case falls back to returning `[object Object]`, which is what `String({})` would return. ## How did you test this change? Added a new unit test.
1 parent ff595de commit 6e29479

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

packages/react-devtools-shared/src/__tests__/utils-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ describe('utils', () => {
155155
'Symbol(abc) 123',
156156
);
157157
});
158+
159+
it('should gracefully handle objects with no prototype', () => {
160+
expect(
161+
formatConsoleArgumentsToSingleString('%o', Object.create(null)),
162+
).toEqual('%o [object Object]');
163+
});
158164
});
159165

160166
describe('formatWithStyles', () => {

packages/react-devtools-shared/src/backend/utils/index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ export function serializeToString(data: any): string {
167167
);
168168
}
169169

170+
function safeToString(val: any): string {
171+
try {
172+
return String(val);
173+
} catch (err) {
174+
if (typeof val === 'object') {
175+
// An object with no prototype and no `[Symbol.toPrimitive]()`, `toString()`, and `valueOf()` methods would throw.
176+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion
177+
return '[object Object]';
178+
}
179+
throw err;
180+
}
181+
}
182+
170183
// based on https://github.com/tmpfs/format-util/blob/0e62d430efb0a1c51448709abd3e2406c14d8401/format.js#L1
171184
// based on https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions
172185
// Implements s, d, i and f placeholders
@@ -176,7 +189,7 @@ export function formatConsoleArgumentsToSingleString(
176189
): string {
177190
const args = inputArgs.slice();
178191

179-
let formatted: string = String(maybeMessage);
192+
let formatted: string = safeToString(maybeMessage);
180193

181194
// If the first argument is a string, check for substitutions.
182195
if (typeof maybeMessage === 'string') {
@@ -211,7 +224,7 @@ export function formatConsoleArgumentsToSingleString(
211224
// Arguments that remain after formatting.
212225
if (args.length) {
213226
for (let i = 0; i < args.length; i++) {
214-
formatted += ' ' + String(args[i]);
227+
formatted += ' ' + safeToString(args[i]);
215228
}
216229
}
217230

0 commit comments

Comments
 (0)