Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

worker: support more cases when (de)serializing errors #47925

Merged
merged 6 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
57 changes: 48 additions & 9 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 @@ -48,13 +56,17 @@ function TryGetAllProperties(object, target = object) {
if (getter && key !== '__proto__') {
try {
descriptor.value = FunctionPrototypeCall(getter, target);
delete descriptor.get;
delete descriptor.set;
MoLow marked this conversation as resolved.
Show resolved Hide resolved
} catch {
// Continue regardless of error.
}
}
if ('value' in descriptor && typeof descriptor.value !== 'function') {
delete descriptor.get;
delete descriptor.set;
if (key === 'cause') {
descriptor.value = serializeError(descriptor.value);
all[key] = descriptor;
} else if ('value' in descriptor &&
typeof descriptor.value !== 'function' && typeof descriptor.value !== 'symbol') {
all[key] = descriptor;
MoLow marked this conversation as resolved.
Show resolved Hide resolved
}
});
Expand Down Expand Up @@ -95,6 +107,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')]);
MoLow marked this conversation as resolved.
Show resolved Hide resolved
}
try {
if (typeof error === 'object' &&
ObjectPrototypeToString(error) === '[object Error]') {
Expand All @@ -113,6 +129,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 +148,12 @@ function serializeError(error) {
Buffer.from(inspect(error), 'utf8')]);
}

function fromBuffer(error) {
return Buffer.from(error.buffer,
error.byteOffset + 1,
error.byteLength - 1);
MoLow marked this conversation as resolved.
Show resolved Hide resolved
}

let deserialize;
function deserializeError(error) {
if (!deserialize) deserialize = require('v8').deserialize;
Expand All @@ -132,19 +163,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
36 changes: 36 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,29 @@ 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);
MoLow marked this conversation as resolved.
Show resolved Hide resolved
assert.strictEqual(Object.hasOwn(cycle(new Error('Error with cause', { cause: undefined })), 'cause'), true);
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'));
class ErrorWithCause extends Error {
get cause() {
return new Error('err');
}
}
assert.deepStrictEqual(cycle(new ErrorWithCause('Error with cause')).cause, new Error('err'));
MoLow marked this conversation as resolved.
Show resolved Hide resolved
class ErrorWithThowingCause extends Error {
get cause() {
throw new Error('err');
}
}
assert.strictEqual(cycle(new ErrorWithThowingCause('Error with cause')).cause, undefined);
assert.strictEqual(Object.hasOwn(cycle(new ErrorWithThowingCause('Error with cause')), 'cause'), false);

{
const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42);
assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/);
Expand All @@ -66,3 +93,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');