Skip to content

Commit 78972d4

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 PR-URL: #47925 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 2b65625 commit 78972d4

2 files changed

Lines changed: 111 additions & 11 deletions

File tree

‎lib/internal/error_serdes.js‎

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,31 @@ const {
1313
ObjectGetOwnPropertyNames,
1414
ObjectGetPrototypeOf,
1515
ObjectKeys,
16+
ObjectPrototypeHasOwnProperty,
1617
ObjectPrototypeToString,
1718
RangeError,
1819
ReferenceError,
1920
SafeSet,
21+
StringFromCharCode,
22+
StringPrototypeSubstring,
2023
SymbolToStringTag,
2124
SyntaxError,
25+
SymbolFor,
2226
TypeError,
27+
TypedArrayPrototypeGetBuffer,
28+
TypedArrayPrototypeGetByteOffset,
29+
TypedArrayPrototypeGetByteLength,
2330
URIError,
2431
}=primordials;
32+
const{inspect: {custom: customInspectSymbol}}=require('util');
2533

2634
constkSerializedError=0;
2735
constkSerializedObject=1;
2836
constkInspectedError=2;
37+
constkInspectedSymbol=3;
38+
constkCustomInspectedObject=4;
39+
40+
constkSymbolStringLength='Symbol('.length;
2941

3042
consterrors={
3143
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError,
@@ -42,19 +54,24 @@ function TryGetAllProperties(object, target = object) {
4254
ArrayPrototypeForEach(keys,(key)=>{
4355
letdescriptor;
4456
try{
57+
// TODO: create a null-prototype descriptor with needed properties only
4558
descriptor=ObjectGetOwnPropertyDescriptor(object,key);
4659
}catch{return;}
4760
constgetter=descriptor.get;
4861
if(getter&&key!=='__proto__'){
4962
try{
5063
descriptor.value=FunctionPrototypeCall(getter,target);
64+
deletedescriptor.get;
65+
deletedescriptor.set;
5166
}catch{
5267
// Continue regardless of error.
5368
}
5469
}
55-
if('value'indescriptor&&typeofdescriptor.value!=='function'){
56-
deletedescriptor.get;
57-
deletedescriptor.set;
70+
if(key==='cause'){
71+
descriptor.value=serializeError(descriptor.value);
72+
all[key]=descriptor;
73+
}elseif('value'indescriptor&&
74+
typeofdescriptor.value!=='function'&&typeofdescriptor.value!=='symbol'){
5875
all[key]=descriptor;
5976
}
6077
});
@@ -95,6 +112,9 @@ function inspect(...args) {
95112
letserialize;
96113
functionserializeError(error){
97114
if(!serialize)serialize=require('v8').serialize;
115+
if(typeoferror==='symbol'){
116+
returnBuffer.from(StringFromCharCode(kInspectedSymbol)+inspect(error),'utf8');
117+
}
98118
try{
99119
if(typeoferror==='object'&&
100120
ObjectPrototypeToString(error)==='[object Error]'){
@@ -113,14 +133,27 @@ function serializeError(error) {
113133
}catch{
114134
// Continue regardless of error.
115135
}
136+
try{
137+
if(error!=null&&
138+
ObjectPrototypeHasOwnProperty(error,customInspectSymbol)){
139+
returnBuffer.from(StringFromCharCode(kCustomInspectedObject)+inspect(error),'utf8');
140+
}
141+
}catch{
142+
// Continue regardless of error.
143+
}
116144
try{
117145
constserialized=serialize(error);
118146
returnBuffer.concat([Buffer.from([kSerializedObject]),serialized]);
119147
}catch{
120148
// Continue regardless of error.
121149
}
122-
returnBuffer.concat([Buffer.from([kInspectedError]),
123-
Buffer.from(inspect(error),'utf8')]);
150+
returnBuffer.from(StringFromCharCode(kInspectedError)+inspect(error),'utf8');
151+
}
152+
153+
functionfromBuffer(error){
154+
returnBuffer.from(TypedArrayPrototypeGetBuffer(error),
155+
TypedArrayPrototypeGetByteOffset(error)+1,
156+
TypedArrayPrototypeGetByteLength(error)-1);
124157
}
125158

126159
letdeserialize;
@@ -132,19 +165,27 @@ function deserializeError(error) {
132165
constctor=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'inproperties&&'value'inproperties.cause){
172+
properties.cause.value=deserializeError(properties.cause.value);
173+
}
138174
returnObjectCreate(ctor.prototype,properties);
139175
}
140176
casekSerializedObject:
141177
returndeserialize(error.subarray(1));
142-
casekInspectedError:{
143-
constbuf=Buffer.from(error.buffer,
144-
error.byteOffset+1,
145-
error.byteLength-1);
146-
returnbuf.toString('utf8');
178+
casekInspectedError:
179+
returnfromBuffer(error).toString('utf8');
180+
casekInspectedSymbol: {
181+
constbuf=fromBuffer(error);
182+
returnSymbolFor(StringPrototypeSubstring(buf.toString('utf8'),kSymbolStringLength,buf.length-1));
147183
}
184+
casekCustomInspectedObject:
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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'use strict';
33
require('../common');
44
constassert=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
leterr=newError('foo');
2024
for(leti=0;i<10;i++){
@@ -43,6 +47,52 @@ 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+
classErrorWithCauseextendsError{
51+
getcause(){
52+
returnnewError('err');
53+
}
54+
}
55+
classErrorWithThowingCauseextendsError{
56+
getcause(){
57+
thrownewError('err');
58+
}
59+
}
60+
classErrorWithCyclicCauseextendsError{
61+
getcause(){
62+
returnnewErrorWithCyclicCause();
63+
}
64+
}
65+
consterrorWithCause=Object
66+
.defineProperty(newError('Error with cause'),'cause',{get(){return{foo: 'bar'};}});
67+
consterrorWithThrowingCause=Object
68+
.defineProperty(newError('Error with cause'),'cause',{get(){thrownewError('err');}});
69+
consterrorWithCyclicCause=Object
70+
.defineProperty(newError('Error with cause'),'cause',{get(){returnerrorWithCyclicCause;}});
71+
72+
assert.strictEqual(cycle(newError('Error with cause',{cause: 0})).cause,0);
73+
assert.strictEqual(cycle(newError('Error with cause',{cause: -1})).cause,-1);
74+
assert.strictEqual(cycle(newError('Error with cause',{cause: 1.4})).cause,1.4);
75+
assert.strictEqual(cycle(newError('Error with cause',{cause: null})).cause,null);
76+
assert.strictEqual(cycle(newError('Error with cause',{cause: undefined})).cause,undefined);
77+
assert.strictEqual(Object.hasOwn(cycle(newError('Error with cause',{cause: undefined})),'cause'),true);
78+
assert.strictEqual(cycle(newError('Error with cause',{cause: 'foo'})).cause,'foo');
79+
assert.deepStrictEqual(cycle(newError('Error with cause',{cause: newError('err')})).cause,newError('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(newErrorWithCause('Error with cause')).cause,newError('err'));
84+
assert.strictEqual(cycle(newErrorWithThowingCause('Error with cause')).cause,undefined);
85+
assert.strictEqual(Object.hasOwn(cycle(newErrorWithThowingCause('Error with cause')),'cause'),false);
86+
// When the cause is cyclic, it is serialized until Maxiumum call stack size is reached
87+
letdepth=0;
88+
lete=cycle(newErrorWithCyclicCause('Error with cause'));
89+
while(e.cause){
90+
e=e.cause;
91+
depth++;
92+
}
93+
assert(depth>1);
94+
95+
4696
{
4797
consterr=newERR_INVALID_ARG_TYPE('object','Object',42);
4898
assert.match(String(err),/^TypeError\[ERR_INVALID_ARG_TYPE\]:/);
@@ -66,3 +116,12 @@ assert.strictEqual(cycle(Function), '[Function: Function]');
66116
serializeError(newDynamicError());
67117
assert.strictEqual(called,true);
68118
}
119+
120+
121+
constdata={
122+
foo: 'bar',
123+
[inspect.custom](){
124+
return'barbaz';
125+
}
126+
};
127+
assert.strictEqual(inspect(cycle(data)),'barbaz');

0 commit comments

Comments
 (0)