Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/core/src/utils/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,21 @@ export function normalize(input: unknown, depth: number = 100, maxProperties: nu
// since we're at the outermost level, we don't provide a key
return visit('', input, depth, maxProperties);
} catch (err) {
return { ERROR: `**non-serializable** (${err})` };
return { ERROR: `**non-serializable** (${stringifyCaughtValue(err)})` };
}
}

/**
* Stringify a caught value for an error message. Template literals and other implicit
* conversions throw on symbols and on objects with no usable `toString`, which is how
* the values that end up here tend to be broken in the first place, so fall back to
* fixed text instead of throwing again.
*/
function stringifyCaughtValue(err: unknown): string {
try {
return String(err);
} catch {
return 'unknown error';
}
}

Expand Down Expand Up @@ -240,7 +254,7 @@ export function stringifyValue(

return `[object ${objName}]`;
} catch (err) {
return `**non-serializable** (${err})`;
return `**non-serializable** (${stringifyCaughtValue(err)})`;
}
}
/* eslint-enable complexity */
Expand Down
34 changes: 34 additions & 0 deletions packages/core/test/lib/utils/normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,40 @@ describe('normalize()', () => {
someFunc: '**non-serializable** (Error: Nope)',
});
});

test('does not throw when a getter throws a symbol', () => {
const obj = {
get bad(): unknown {
throw Symbol('s');
},
};

expect(normalize({ bad: obj }, 3)).toEqual({
ERROR: '**non-serializable** (Symbol(s))',
});
});

test('does not throw when a getter throws a null-prototype object', () => {
const obj = {
get bad(): unknown {
throw Object.create(null);
},
};

expect(normalize({ bad: obj }, 3)).toEqual({
ERROR: '**non-serializable** (unknown error)',
});
});

test('does not throw when stringifying a value fails with a symbol', () => {
vi.spyOn(stacktraceModule, 'getFunctionName').mockImplementationOnce(() => {
throw Symbol('s');
});

expect(normalize({ someFunc: () => {} })).toEqual({
someFunc: '**non-serializable** (Symbol(s))',
});
});
});

test('runs registered stringifier for each visited value', () => {
Expand Down
Loading