GeezerVison is showing github.com — worker: support more cases when (de)serializing errors · nodejs/node@47fea13
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/error_serdes.js +52-11Lines changed: 52 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -13,19 +13,31 @@ const { | 13 | 13 | ObjectGetOwnPropertyNames, | 14 | 14 | ObjectGetPrototypeOf, | 15 | 15 | ObjectKeys, | | 16 | + ObjectPrototypeHasOwnProperty, | 16 | 17 | ObjectPrototypeToString, | 17 | 18 | RangeError, | 18 | 19 | ReferenceError, | 19 | 20 | SafeSet, | | 21 | + StringFromCharCode, | | 22 | + StringPrototypeSubstring, | 20 | 23 | SymbolToStringTag, | 21 | 24 | SyntaxError, | | 25 | + SymbolFor, | 22 | 26 | TypeError, | | 27 | + TypedArrayPrototypeGetBuffer, | | 28 | + TypedArrayPrototypeGetByteOffset, | | 29 | + TypedArrayPrototypeGetByteLength, | 23 | 30 | URIError, | 24 | 31 | } = primordials; | | 32 | +const { inspect: { custom: customInspectSymbol } } = require('util'); | 25 | 33 |
| 26 | 34 | const kSerializedError = 0; | 27 | 35 | const kSerializedObject = 1; | 28 | 36 | const kInspectedError = 2; | | 37 | +const kInspectedSymbol = 3; | | 38 | +const kCustomInspectedObject = 4; | | 39 | + | | 40 | +const kSymbolStringLength = 'Symbol('.length; | 29 | 41 |
| 30 | 42 | const errors = { | 31 | 43 | Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError, | @@ -42,19 +54,24 @@ function TryGetAllProperties(object, target = object) { | 42 | 54 | ArrayPrototypeForEach(keys, (key) => { | 43 | 55 | let descriptor; | 44 | 56 | try { | | 57 | + // TODO: create a null-prototype descriptor with needed properties only | 45 | 58 | descriptor = ObjectGetOwnPropertyDescriptor(object, key); | 46 | 59 | } catch { return; } | 47 | 60 | const getter = descriptor.get; | 48 | 61 | if (getter && key !== '__proto__') { | 49 | 62 | try { | 50 | 63 | descriptor.value = FunctionPrototypeCall(getter, target); | | 64 | + delete descriptor.get; | | 65 | + delete descriptor.set; | 51 | 66 | } catch { | 52 | 67 | // Continue regardless of error. | 53 | 68 | } | 54 | 69 | } | 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') { | 58 | 75 | all[key] = descriptor; | 59 | 76 | } | 60 | 77 | }); | @@ -95,6 +112,9 @@ function inspect(...args) { | 95 | 112 | let serialize; | 96 | 113 | function serializeError(error) { | 97 | 114 | if (!serialize) serialize = require('v8').serialize; | | 115 | + if (typeof error === 'symbol') { | | 116 | + return Buffer.from(StringFromCharCode(kInspectedSymbol) + inspect(error), 'utf8'); | | 117 | + } | 98 | 118 | try { | 99 | 119 | if (typeof error === 'object' && | 100 | 120 | ObjectPrototypeToString(error) === '[object Error]') { | @@ -113,14 +133,27 @@ function serializeError(error) { | 113 | 133 | } catch { | 114 | 134 | // Continue regardless of error. | 115 | 135 | } | | 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 | + } | 116 | 144 | try { | 117 | 145 | const serialized = serialize(error); | 118 | 146 | return Buffer.concat([Buffer.from([kSerializedObject]), serialized]); | 119 | 147 | } catch { | 120 | 148 | // Continue regardless of error. | 121 | 149 | } | 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); | 124 | 157 | } | 125 | 158 |
| 126 | 159 | let deserialize; | @@ -132,19 +165,27 @@ function deserializeError(error) { | 132 | 165 | const ctor = errors[constructor]; | 133 | 166 | ObjectDefineProperty(properties, SymbolToStringTag, { | 134 | 167 | __proto__: null, | 135 | | - value: { value: 'Error', configurable: true }, | | 168 | + value: { __proto__: null, value: 'Error', configurable: true }, | 136 | 169 | enumerable: true, | 137 | 170 | }); | | 171 | + if ('cause' in properties && 'value' in properties.cause) { | | 172 | + properties.cause.value = deserializeError(properties.cause.value); | | 173 | + } | 138 | 174 | return ObjectCreate(ctor.prototype, properties); | 139 | 175 | } | 140 | 176 | case kSerializedObject: | 141 | 177 | 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)); | 147 | 183 | } | | 184 | + case kCustomInspectedObject: | | 185 | + return { | | 186 | + __proto__: null, | | 187 | + [customInspectSymbol]: () => fromBuffer(error).toString('utf8'), | | 188 | + }; | 148 | 189 | } | 149 | 190 | require('assert').fail('This should not happen'); | 150 | 191 | } | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-error-serdes.js +59Lines changed: 59 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | 'use strict'; | 3 | 3 | require('../common'); | 4 | 4 | const assert = require('assert'); | | 5 | +const { inspect } = require('util'); | 5 | 6 | const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; | 6 | 7 | const { serializeError, deserializeError } = require('internal/error_serdes'); | 7 | 8 |
| @@ -15,6 +16,9 @@ assert.strictEqual(cycle(1.4), 1.4); | 15 | 16 | assert.strictEqual(cycle(null), null); | 16 | 17 | assert.strictEqual(cycle(undefined), undefined); | 17 | 18 | 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 | + | 18 | 22 |
| 19 | 23 | let err = new Error('foo'); | 20 | 24 | for (let i = 0; i < 10; i++) { | @@ -43,6 +47,52 @@ assert.strictEqual(cycle(new SubError('foo')).name, 'Error'); | 43 | 47 | assert.deepStrictEqual(cycle({ message: 'foo' }), { message: 'foo' }); | 44 | 48 | assert.strictEqual(cycle(Function), '[Function: Function]'); | 45 | 49 |
| | 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 | + | 46 | 96 | { | 47 | 97 | const err = new ERR_INVALID_ARG_TYPE('object', 'Object', 42); | 48 | 98 | assert.match(String(err), /^TypeError \[ERR_INVALID_ARG_TYPE\]:/); | @@ -66,3 +116,12 @@ assert.strictEqual(cycle(Function), '[Function: Function]'); | 66 | 116 | serializeError(new DynamicError()); | 67 | 117 | assert.strictEqual(called, true); | 68 | 118 | } | | 119 | + | | 120 | + | | 121 | +const data = { | | 122 | + foo: 'bar', | | 123 | + [inspect.custom]() { | | 124 | + return 'barbaz'; | | 125 | + } | | 126 | +}; | | 127 | +assert.strictEqual(inspect(cycle(data)), 'barbaz'); |
|