Skip to content

Commit

Permalink
worker: support more cases when (de)serializing errors
Browse files Browse the repository at this point in the history
- error.cause is potentially an error, so is now handled recursively
- best effort to serialize thrown symbols
- handle thrown object with custom inspect
  • Loading branch information
MoLow committed May 8, 2023
1 parent 0b3fcfc commit 640f32b
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 7 deletions.
55 changes: 48 additions & 7 deletions lib/internal/error_serdes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,27 @@ const {
ObjectGetOwnPropertyNames,
ObjectGetPrototypeOf,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectPrototypeToString,
RangeError,
ReferenceError,
SafeSet,
StringPrototypeSubstring,
SymbolToStringTag,
SyntaxError,
SymbolFor,
TypeError,
URIError,
} = primordials;
const { inspect: { custom: customInspectSymbol } } = require('util');

const kSerializedError = 0;
const kSerializedObject = 1;
const kInspectedError = 2;
const kInspectedSymbol = 3;
const kCustomInspectedObject = 4;

const kSymbolStringLength = 'Symbol('.length;

const errors = {
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError,
Expand All @@ -52,7 +60,13 @@ function TryGetAllProperties(object, target = object) {
// Continue regardless of error.
}
}
if ('value' in descriptor && typeof descriptor.value !== 'function') {
if (key === 'cause') {
delete descriptor.get;
delete descriptor.set;
descriptor.value = serializeError(descriptor.value);
all[key] = descriptor;
} else if ('value' in descriptor &&
typeof descriptor.value !== 'function' && typeof descriptor.value !== 'symbol') {
delete descriptor.get;
delete descriptor.set;
all[key] = descriptor;
Expand Down Expand Up @@ -95,6 +109,10 @@ function inspect(...args) {
let serialize;
function serializeError(error) {
if (!serialize) serialize = require('v8').serialize;
if (typeof error === 'symbol') {
return Buffer.concat([Buffer.from([kInspectedSymbol]),
Buffer.from(inspect(error), 'utf8')]);
}
try {
if (typeof error === 'object' &&
ObjectPrototypeToString(error) === '[object Error]') {
Expand All @@ -113,6 +131,15 @@ function serializeError(error) {
} catch {
// Continue regardless of error.
}
try {
if (error != null &&
ObjectPrototypeHasOwnProperty(error, customInspectSymbol)) {
return Buffer.concat([Buffer.from([kCustomInspectedObject]),
Buffer.from(inspect(error), 'utf8')]);
}
} catch {
// Continue regardless of error.
}
try {
const serialized = serialize(error);
return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
Expand All @@ -123,6 +150,12 @@ function serializeError(error) {
Buffer.from(inspect(error), 'utf8')]);
}

function fromBuffer(error) {
return Buffer.from(error.buffer,
error.byteOffset + 1,
error.byteLength - 1);
}

let deserialize;
function deserializeError(error) {
if (!deserialize) deserialize = require('v8').deserialize;
Expand All @@ -132,19 +165,27 @@ function deserializeError(error) {
const ctor = errors[constructor];
ObjectDefineProperty(properties, SymbolToStringTag, {
__proto__: null,
value: { value: 'Error', configurable: true },
value: { __proto__: null, value: 'Error', configurable: true },
enumerable: true,
});
if ('cause' in properties && 'value' in properties.cause) {
properties.cause.value = deserializeError(properties.cause.value);
}
return ObjectCreate(ctor.prototype, properties);
}
case kSerializedObject:
return deserialize(error.subarray(1));
case kInspectedError: {
const buf = Buffer.from(error.buffer,
error.byteOffset + 1,
error.byteLength - 1);
return buf.toString('utf8');
case kInspectedError:
return fromBuffer(error).toString('utf8');
case kInspectedSymbol: {
const buf = fromBuffer(error);
return SymbolFor(StringPrototypeSubstring(buf.toString('utf8'), kSymbolStringLength, buf.length - 1));
}
case kCustomInspectedObject:
return {
__proto__: null,
[customInspectSymbol]: () => fromBuffer(error).toString('utf8'),
};
}
require('assert').fail('This should not happen');
}
Expand Down
23 changes: 23 additions & 0 deletions test/parallel/test-error-serdes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'use strict';
require('../common');
const assert = require('assert');
const { inspect } = require('util');
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
const { serializeError, deserializeError } = require('internal/error_serdes');

Expand All @@ -15,6 +16,9 @@ assert.strictEqual(cycle(1.4), 1.4);
assert.strictEqual(cycle(null), null);
assert.strictEqual(cycle(undefined), undefined);
assert.strictEqual(cycle('foo'), 'foo');
assert.strictEqual(cycle(Symbol.for('foo')), Symbol.for('foo'));
assert.strictEqual(cycle(Symbol('foo')).toString(), Symbol('foo').toString());


let err = new Error('foo');
for (let i = 0; i < 10; i++) {
Expand Down Expand Up @@ -43,6 +47,16 @@ assert.strictEqual(cycle(new SubError('foo')).name, 'Error');
assert.deepStrictEqual(cycle({ message: 'foo' }), { message: 'foo' });
assert.strictEqual(cycle(Function), '[Function: Function]');


assert.strictEqual(cycle(new Error('Error with cause', { cause: 0 })).cause, 0);
assert.strictEqual(cycle(new Error('Error with cause', { cause: -1 })).cause, -1);
assert.strictEqual(cycle(new Error('Error with cause', { cause: 1.4 })).cause, 1.4);
assert.strictEqual(cycle(new Error('Error with cause', { cause: null })).cause, null);
assert.strictEqual(cycle(new Error('Error with cause', { cause: undefined })).cause, undefined);
assert.strictEqual(cycle(new Error('Error with cause', { cause: 'foo' })).cause, 'foo');
assert.deepStrictEqual(cycle(new Error('Error with cause', { cause: new Error('err') })).cause, new Error('err'));


{
const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42);
assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/);
Expand All @@ -66,3 +80,12 @@ assert.strictEqual(cycle(Function), '[Function: Function]');
serializeError(new DynamicError());
assert.strictEqual(called, true);
}


const data = {
foo: 'bar',
[inspect.custom]() {
return 'barbaz';
}
};
assert.strictEqual(inspect(cycle(data)), 'barbaz');

0 comments on commit 640f32b

Please sign in to comment.