summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoshe Atlow <moshe@atlow.co.il>2023-05-12 14:46:32 +0300
committerGitHub <noreply@github.com>2023-05-12 11:46:32 +0000
commit7984af69a090dd6d1f60ffe7e194d5e69bce0c20 (patch)
tree62a8d74d743c798b0a8d98b4db3a9172258b8a33
parent78123275c2561260368eeebaa84b805830d189f1 (diff)
downloadnode-new-7984af69a090dd6d1f60ffe7e194d5e69bce0c20.tar.gz
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: https://github.com/nodejs/node/pull/47925 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
-rw-r--r--lib/internal/error_serdes.js63
-rw-r--r--test/parallel/test-error-serdes.js59
2 files changed, 111 insertions, 11 deletions
diff --git a/lib/internal/error_serdes.js b/lib/internal/error_serdes.js
index 13f3f8b35f..e4850c63aa 100644
--- a/lib/internal/error_serdes.js
+++ b/lib/internal/error_serdes.js
@@ -13,19 +13,31 @@ const {
ObjectGetOwnPropertyNames,
ObjectGetPrototypeOf,
ObjectKeys,
+ ObjectPrototypeHasOwnProperty,
ObjectPrototypeToString,
RangeError,
ReferenceError,
SafeSet,
+ StringFromCharCode,
+ StringPrototypeSubstring,
SymbolToStringTag,
SyntaxError,
+ SymbolFor,
TypeError,
+ TypedArrayPrototypeGetBuffer,
+ TypedArrayPrototypeGetByteOffset,
+ TypedArrayPrototypeGetByteLength,
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,
@@ -42,19 +54,24 @@ function TryGetAllProperties(object, target = object) {
ArrayPrototypeForEach(keys, (key) => {
let descriptor;
try {
+ // TODO: create a null-prototype descriptor with needed properties only
descriptor = ObjectGetOwnPropertyDescriptor(object, key);
} catch { return; }
const getter = descriptor.get;
if (getter && key !== '__proto__') {
try {
descriptor.value = FunctionPrototypeCall(getter, target);
+ delete descriptor.get;
+ delete descriptor.set;
} 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;
}
});
@@ -95,6 +112,9 @@ function inspect(...args) {
let serialize;
function serializeError(error) {
if (!serialize) serialize = require('v8').serialize;
+ if (typeof error === 'symbol') {
+ return Buffer.from(StringFromCharCode(kInspectedSymbol) + inspect(error), 'utf8');
+ }
try {
if (typeof error === 'object' &&
ObjectPrototypeToString(error) === '[object Error]') {
@@ -114,13 +134,26 @@ function serializeError(error) {
// Continue regardless of error.
}
try {
+ if (error != null &&
+ ObjectPrototypeHasOwnProperty(error, customInspectSymbol)) {
+ return Buffer.from(StringFromCharCode(kCustomInspectedObject) + inspect(error), 'utf8');
+ }
+ } catch {
+ // Continue regardless of error.
+ }
+ try {
const serialized = serialize(error);
return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
} catch {
// Continue regardless of error.
}
- return Buffer.concat([Buffer.from([kInspectedError]),
- Buffer.from(inspect(error), 'utf8')]);
+ return Buffer.from(StringFromCharCode(kInspectedError) + inspect(error), 'utf8');
+}
+
+function fromBuffer(error) {
+ return Buffer.from(TypedArrayPrototypeGetBuffer(error),
+ TypedArrayPrototypeGetByteOffset(error) + 1,
+ TypedArrayPrototypeGetByteLength(error) - 1);
}
let deserialize;
@@ -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');
}
diff --git a/test/parallel/test-error-serdes.js b/test/parallel/test-error-serdes.js
index 92d0864348..3b61d3a4b9 100644
--- a/test/parallel/test-error-serdes.js
+++ b/test/parallel/test-error-serdes.js
@@ -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');
@@ -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++) {
@@ -43,6 +47,52 @@ assert.strictEqual(cycle(new SubError('foo')).name, 'Error');
assert.deepStrictEqual(cycle({ message: 'foo' }), { message: 'foo' });
assert.strictEqual(cycle(Function), '[Function: Function]');
+class ErrorWithCause extends Error {
+ get cause() {
+ return new Error('err');
+ }
+}
+class ErrorWithThowingCause extends Error {
+ get cause() {
+ throw new Error('err');
+ }
+}
+class ErrorWithCyclicCause extends Error {
+ get cause() {
+ return new ErrorWithCyclicCause();
+ }
+}
+const errorWithCause = Object
+ .defineProperty(new Error('Error with cause'), 'cause', { get() { return { foo: 'bar' }; } });
+const errorWithThrowingCause = Object
+ .defineProperty(new Error('Error with cause'), 'cause', { get() { throw new Error('err'); } });
+const errorWithCyclicCause = Object
+ .defineProperty(new Error('Error with cause'), 'cause', { get() { return errorWithCyclicCause; } });
+
+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(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'));
+assert.deepStrictEqual(cycle(errorWithCause).cause, { foo: 'bar' });
+assert.strictEqual(Object.hasOwn(cycle(errorWithThrowingCause), 'cause'), false);
+assert.strictEqual(Object.hasOwn(cycle(errorWithCyclicCause), 'cause'), true);
+assert.deepStrictEqual(cycle(new ErrorWithCause('Error with cause')).cause, 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);
+// When the cause is cyclic, it is serialized until Maxiumum call stack size is reached
+let depth = 0;
+let e = cycle(new ErrorWithCyclicCause('Error with cause'));
+while (e.cause) {
+ e = e.cause;
+ depth++;
+}
+assert(depth > 1);
+
+
{
const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42);
assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/);
@@ -66,3 +116,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');