Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 78972d4

Browse filesBrowse files
committed
worker: support more cases when (de)serializing errors
- error.cause is potentially an error, so is now handled recursively - best effort to serialize thrown symbols - handle thrown object with custom inspect PR-URL: #47925 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 2b65625 commit 78972d4
Copy full SHA for 78972d4

File tree

Expand file treeCollapse file tree

2 files changed

+111
-11
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+111
-11
lines changed
Open diff view settings
Collapse file

‎lib/internal/error_serdes.js‎

Copy file name to clipboardExpand all lines: lib/internal/error_serdes.js
+52-11Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,31 @@ const {
1313
ObjectGetOwnPropertyNames,
1414
ObjectGetPrototypeOf,
1515
ObjectKeys,
16+
ObjectPrototypeHasOwnProperty,
1617
ObjectPrototypeToString,
1718
RangeError,
1819
ReferenceError,
1920
SafeSet,
21+
StringFromCharCode,
22+
StringPrototypeSubstring,
2023
SymbolToStringTag,
2124
SyntaxError,
25+
SymbolFor,
2226
TypeError,
27+
TypedArrayPrototypeGetBuffer,
28+
TypedArrayPrototypeGetByteOffset,
29+
TypedArrayPrototypeGetByteLength,
2330
URIError,
2431
} = primordials;
32+
const { inspect: { custom: customInspectSymbol } } = require('util');
2533

2634
const kSerializedError = 0;
2735
const kSerializedObject = 1;
2836
const kInspectedError = 2;
37+
const kInspectedSymbol = 3;
38+
const kCustomInspectedObject = 4;
39+
40+
const kSymbolStringLength = 'Symbol('.length;
2941

3042
const errors = {
3143
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError,
@@ -42,19 +54,24 @@ function TryGetAllProperties(object, target = object) {
4254
ArrayPrototypeForEach(keys, (key) => {
4355
let descriptor;
4456
try {
57+
// TODO: create a null-prototype descriptor with needed properties only
4558
descriptor = ObjectGetOwnPropertyDescriptor(object, key);
4659
} catch { return; }
4760
const getter = descriptor.get;
4861
if (getter && key !== '__proto__') {
4962
try {
5063
descriptor.value = FunctionPrototypeCall(getter, target);
64+
delete descriptor.get;
65+
delete descriptor.set;
5166
} catch {
5267
// Continue regardless of error.
5368
}
5469
}
55-
if ('value' in descriptor && typeof descriptor.value !== 'function') {
56-
delete descriptor.get;
57-
delete descriptor.set;
70+
if (key === 'cause') {
71+
descriptor.value = serializeError(descriptor.value);
72+
all[key] = descriptor;
73+
} else if ('value' in descriptor &&
74+
typeof descriptor.value !== 'function' && typeof descriptor.value !== 'symbol') {
5875
all[key] = descriptor;
5976
}
6077
});
@@ -95,6 +112,9 @@ function inspect(...args) {
95112
let serialize;
96113
function serializeError(error) {
97114
if (!serialize) serialize = require('v8').serialize;
115+
if (typeof error === 'symbol') {
116+
return Buffer.from(StringFromCharCode(kInspectedSymbol) + inspect(error), 'utf8');
117+
}
98118
try {
99119
if (typeof error === 'object' &&
100120
ObjectPrototypeToString(error) === '[object Error]') {
@@ -113,14 +133,27 @@ function serializeError(error) {
113133
} catch {
114134
// Continue regardless of error.
115135
}
136+
try {
137+
if (error != null &&
138+
ObjectPrototypeHasOwnProperty(error, customInspectSymbol)) {
139+
return Buffer.from(StringFromCharCode(kCustomInspectedObject) + inspect(error), 'utf8');
140+
}
141+
} catch {
142+
// Continue regardless of error.
143+
}
116144
try {
117145
const serialized = serialize(error);
118146
return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
119147
} catch {
120148
// Continue regardless of error.
121149
}
122-
return Buffer.concat([Buffer.from([kInspectedError]),
123-
Buffer.from(inspect(error), 'utf8')]);
150+
return Buffer.from(StringFromCharCode(kInspectedError) + inspect(error), 'utf8');
151+
}
152+
153+
function fromBuffer(error) {
154+
return Buffer.from(TypedArrayPrototypeGetBuffer(error),
155+
TypedArrayPrototypeGetByteOffset(error) + 1,
156+
TypedArrayPrototypeGetByteLength(error) - 1);
124157
}
125158

126159
let deserialize;
@@ -132,19 +165,27 @@ function deserializeError(error) {
132165
const ctor = errors[constructor];
133166
ObjectDefineProperty(properties, SymbolToStringTag, {
134167
__proto__: null,
135-
value: { value: 'Error', configurable: true },
168+
value: { __proto__: null, value: 'Error', configurable: true },
136169
enumerable: true,
137170
});
171+
if ('cause' in properties && 'value' in properties.cause) {
172+
properties.cause.value = deserializeError(properties.cause.value);
173+
}
138174
return ObjectCreate(ctor.prototype, properties);
139175
}
140176
case kSerializedObject:
141177
return deserialize(error.subarray(1));
142-
case kInspectedError: {
143-
const buf = Buffer.from(error.buffer,
144-
error.byteOffset + 1,
145-
error.byteLength - 1);
146-
return buf.toString('utf8');
178+
case kInspectedError:
179+
return fromBuffer(error).toString('utf8');
180+
case kInspectedSymbol: {
181+
const buf = fromBuffer(error);
182+
return SymbolFor(StringPrototypeSubstring(buf.toString('utf8'), kSymbolStringLength, buf.length - 1));
147183
}
184+
case kCustomInspectedObject:
185+
return {
186+
__proto__: null,
187+
[customInspectSymbol]: () => fromBuffer(error).toString('utf8'),
188+
};
148189
}
149190
require('assert').fail('This should not happen');
150191
}
Collapse file

‎test/parallel/test-error-serdes.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-error-serdes.js
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'use strict';
33
require('../common');
44
const assert = require('assert');
5+
const { inspect } = require('util');
56
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
67
const { serializeError, deserializeError } = require('internal/error_serdes');
78

@@ -15,6 +16,9 @@ assert.strictEqual(cycle(1.4), 1.4);
1516
assert.strictEqual(cycle(null), null);
1617
assert.strictEqual(cycle(undefined), undefined);
1718
assert.strictEqual(cycle('foo'), 'foo');
19+
assert.strictEqual(cycle(Symbol.for('foo')), Symbol.for('foo'));
20+
assert.strictEqual(cycle(Symbol('foo')).toString(), Symbol('foo').toString());
21+
1822

1923
let err = new Error('foo');
2024
for (let i = 0; i < 10; i++) {
@@ -43,6 +47,52 @@ assert.strictEqual(cycle(new SubError('foo')).name, 'Error');
4347
assert.deepStrictEqual(cycle({ message: 'foo' }), { message: 'foo' });
4448
assert.strictEqual(cycle(Function), '[Function: Function]');
4549

50+
class ErrorWithCause extends Error {
51+
get cause() {
52+
return new Error('err');
53+
}
54+
}
55+
class ErrorWithThowingCause extends Error {
56+
get cause() {
57+
throw new Error('err');
58+
}
59+
}
60+
class ErrorWithCyclicCause extends Error {
61+
get cause() {
62+
return new ErrorWithCyclicCause();
63+
}
64+
}
65+
const errorWithCause = Object
66+
.defineProperty(new Error('Error with cause'), 'cause', { get() { return { foo: 'bar' }; } });
67+
const errorWithThrowingCause = Object
68+
.defineProperty(new Error('Error with cause'), 'cause', { get() { throw new Error('err'); } });
69+
const errorWithCyclicCause = Object
70+
.defineProperty(new Error('Error with cause'), 'cause', { get() { return errorWithCyclicCause; } });
71+
72+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 0 })).cause, 0);
73+
assert.strictEqual(cycle(new Error('Error with cause', { cause: -1 })).cause, -1);
74+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 1.4 })).cause, 1.4);
75+
assert.strictEqual(cycle(new Error('Error with cause', { cause: null })).cause, null);
76+
assert.strictEqual(cycle(new Error('Error with cause', { cause: undefined })).cause, undefined);
77+
assert.strictEqual(Object.hasOwn(cycle(new Error('Error with cause', { cause: undefined })), 'cause'), true);
78+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 'foo' })).cause, 'foo');
79+
assert.deepStrictEqual(cycle(new Error('Error with cause', { cause: new Error('err') })).cause, new Error('err'));
80+
assert.deepStrictEqual(cycle(errorWithCause).cause, { foo: 'bar' });
81+
assert.strictEqual(Object.hasOwn(cycle(errorWithThrowingCause), 'cause'), false);
82+
assert.strictEqual(Object.hasOwn(cycle(errorWithCyclicCause), 'cause'), true);
83+
assert.deepStrictEqual(cycle(new ErrorWithCause('Error with cause')).cause, new Error('err'));
84+
assert.strictEqual(cycle(new ErrorWithThowingCause('Error with cause')).cause, undefined);
85+
assert.strictEqual(Object.hasOwn(cycle(new ErrorWithThowingCause('Error with cause')), 'cause'), false);
86+
// When the cause is cyclic, it is serialized until Maxiumum call stack size is reached
87+
let depth = 0;
88+
let e = cycle(new ErrorWithCyclicCause('Error with cause'));
89+
while (e.cause) {
90+
e = e.cause;
91+
depth++;
92+
}
93+
assert(depth > 1);
94+
95+
4696
{
4797
const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42);
4898
assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/);
@@ -66,3 +116,12 @@ assert.strictEqual(cycle(Function), '[Function: Function]');
66116
serializeError(new DynamicError());
67117
assert.strictEqual(called, true);
68118
}
119+
120+
121+
const data = {
122+
foo: 'bar',
123+
[inspect.custom]() {
124+
return 'barbaz';
125+
}
126+
};
127+
assert.strictEqual(inspect(cycle(data)), 'barbaz');

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.