@samitouri / QOS-React / commits / 6e29479bff

[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.

Henry Q. Dineen committed Nov 10, 2024 at 14:24 UTC 6e29479bffc188719e797801a9bf7859256b2ea0
2 files changed +21 -2
packages/react-devtools-shared/src/__tests__/utils-test.js
+6
@@ -155,6 +155,12 @@ describe('utils', () => {
155 'Symbol(abc) 123',
156 );
157 });
158 +
159 + it('should gracefully handle objects with no prototype', () => {
160 + expect(
161 + formatConsoleArgumentsToSingleString('%o', Object.create(null)),
162 + ).toEqual('%o [object Object]');
163 + });
164 });
165
166 describe('formatWithStyles', () => {
packages/react-devtools-shared/src/backend/utils/index.js
+15 -2
@@ -167,6 +167,19 @@ export function serializeToString(data: any): string {
167 );
168 }
169
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 +
183 // based on https://github.com/tmpfs/format-util/blob/0e62d430efb0a1c51448709abd3e2406c14d8401/format.js#L1
184 // based on https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions
185 // Implements s, d, i and f placeholders
@@ -176,7 +189,7 @@ export function formatConsoleArgumentsToSingleString(
189 ): string {
190 const args = inputArgs.slice();
191
179 - let formatted: string = String(maybeMessage);
192 + let formatted: string = safeToString(maybeMessage);
193
194 // If the first argument is a string, check for substitutions.
195 if (typeof maybeMessage === 'string') {
@@ -211,7 +224,7 @@ export function formatConsoleArgumentsToSingleString(
224 // Arguments that remain after formatting.
225 if (args.length) {
226 for (let i = 0; i < args.length; i++) {
214 - formatted += ' ' + String(args[i]);
227 + formatted += ' ' + safeToString(args[i]);
228 }
229 }
230