Skip to content

Commit 640f32b

Browse 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
1 parent 0b3fcfc commit 640f32b

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

lib/internal/error_serdes.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,27 @@ const {
1313
ObjectGetOwnPropertyNames,
1414
ObjectGetPrototypeOf,
1515
ObjectKeys,
16+
ObjectPrototypeHasOwnProperty,
1617
ObjectPrototypeToString,
1718
RangeError,
1819
ReferenceError,
1920
SafeSet,
21+
StringPrototypeSubstring,
2022
SymbolToStringTag,
2123
SyntaxError,
24+
SymbolFor,
2225
TypeError,
2326
URIError,
2427
} = primordials;
28+
const { inspect: { custom: customInspectSymbol } } = require('util');
2529

2630
const kSerializedError = 0;
2731
const kSerializedObject = 1;
2832
const kInspectedError = 2;
33+
const kInspectedSymbol = 3;
34+
const kCustomInspectedObject = 4;
35+
36+
const kSymbolStringLength = 'Symbol('.length;
2937

3038
const errors = {
3139
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError,
@@ -52,7 +60,13 @@ function TryGetAllProperties(object, target = object) {
5260
// Continue regardless of error.
5361
}
5462
}
55-
if ('value' in descriptor && typeof descriptor.value !== 'function') {
63+
if (key === 'cause') {
64+
delete descriptor.get;
65+
delete descriptor.set;
66+
descriptor.value = serializeError(descriptor.value);
67+
all[key] = descriptor;
68+
} else if ('value' in descriptor &&
69+
typeof descriptor.value !== 'function' && typeof descriptor.value !== 'symbol') {
5670
delete descriptor.get;
5771
delete descriptor.set;
5872
all[key] = descriptor;
@@ -95,6 +109,10 @@ function inspect(...args) {
95109
let serialize;
96110
function serializeError(error) {
97111
if (!serialize) serialize = require('v8').serialize;
112+
if (typeof error === 'symbol') {
113+
return Buffer.concat([Buffer.from([kInspectedSymbol]),
114+
Buffer.from(inspect(error), 'utf8')]);
115+
}
98116
try {
99117
if (typeof error === 'object' &&
100118
ObjectPrototypeToString(error) === '[object Error]') {
@@ -113,6 +131,15 @@ function serializeError(error) {
113131
} catch {
114132
// Continue regardless of error.
115133
}
134+
try {
135+
if (error != null &&
136+
ObjectPrototypeHasOwnProperty(error, customInspectSymbol)) {
137+
return Buffer.concat([Buffer.from([kCustomInspectedObject]),
138+
Buffer.from(inspect(error), 'utf8')]);
139+
}
140+
} catch {
141+
// Continue regardless of error.
142+
}
116143
try {
117144
const serialized = serialize(error);
118145
return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
@@ -123,6 +150,12 @@ function serializeError(error) {
123150
Buffer.from(inspect(error), 'utf8')]);
124151
}
125152

153+
function fromBuffer(error) {
154+
return Buffer.from(error.buffer,
155+
error.byteOffset + 1,
156+
error.byteLength - 1);
157+
}
158+
126159
let deserialize;
127160
function deserializeError(error) {
128161
if (!deserialize) deserialize = require('v8').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
}

test/parallel/test-error-serdes.js

Lines changed: 23 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,16 @@ 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+
51+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 0 })).cause, 0);
52+
assert.strictEqual(cycle(new Error('Error with cause', { cause: -1 })).cause, -1);
53+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 1.4 })).cause, 1.4);
54+
assert.strictEqual(cycle(new Error('Error with cause', { cause: null })).cause, null);
55+
assert.strictEqual(cycle(new Error('Error with cause', { cause: undefined })).cause, undefined);
56+
assert.strictEqual(cycle(new Error('Error with cause', { cause: 'foo' })).cause, 'foo');
57+
assert.deepStrictEqual(cycle(new Error('Error with cause', { cause: new Error('err') })).cause, new Error('err'));
58+
59+
4660
{
4761
const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42);
4862
assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/);
@@ -66,3 +80,12 @@ assert.strictEqual(cycle(Function), '[Function: Function]');
6680
serializeError(new DynamicError());
6781
assert.strictEqual(called, true);
6882
}
83+
84+
85+
const data = {
86+
foo: 'bar',
87+
[inspect.custom]() {
88+
return 'barbaz';
89+
}
90+
};
91+
assert.strictEqual(inspect(cycle(data)), 'barbaz');

0 commit comments

Comments
 (0)