Skip to content

Commit 5b8c97f

Browse files
committed
events: show throw stack trace for uncaught exception
Show the stack trace for the `eventemitter.emit('error')` call in the case of an uncaught exception. Previously, there would be no clue in Node’s output about where the actual `throw` comes from. PR-URL: #19003 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent db8d197 commit 5b8c97f

10 files changed

+169
-2
lines changed

‎lib/events.js‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,47 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners(){
9494
return$getMaxListeners(this);
9595
};
9696

97+
// Returns the longest sequence of `a` that fully appears in `b`,
98+
// of length at least 3.
99+
// This is a lazy approach but should work well enough, given that stack
100+
// frames are usually unequal or otherwise appear in groups, and that
101+
// we only run this code in case of an unhandled exception.
102+
functionlongestSeqContainedIn(a,b){
103+
for(varlen=a.length;len>=3;--len){
104+
for(vari=0;i<a.length-len;++i){
105+
// Attempt to find a[i:i+len] in b
106+
for(varj=0;j<b.length-len;++j){
107+
letmatches=true;
108+
for(vark=0;k<len;++k){
109+
if(a[i+k]!==b[j+k]){
110+
matches=false;
111+
break;
112+
}
113+
}
114+
if(matches)
115+
return[len,i,j];
116+
}
117+
}
118+
}
119+
120+
return[0,0,0];
121+
}
122+
123+
functionenhanceStackTrace(err,own){
124+
constsep='\nEmitted \'error\' event at:\n';
125+
126+
consterrStack=err.stack.split('\n').slice(1);
127+
constownStack=own.stack.split('\n').slice(1);
128+
129+
const[len,off]=longestSeqContainedIn(ownStack,errStack);
130+
if(len>0){
131+
ownStack.splice(off+1,len-1,
132+
' [... lines matching original stack trace ...]');
133+
}
134+
// Do this last, because it is the only operation with side effects.
135+
err.stack=err.stack+sep+ownStack.join('\n');
136+
}
137+
97138
EventEmitter.prototype.emit=functionemit(type, ...args){
98139
letdoError=(type==='error');
99140

@@ -109,13 +150,25 @@ EventEmitter.prototype.emit = function emit(type, ...args){
109150
if(args.length>0)
110151
er=args[0];
111152
if(erinstanceofError){
153+
try{
154+
const{ kExpandStackSymbol }=require('internal/util');
155+
constcapture={};
156+
Error.captureStackTrace(capture,EventEmitter.prototype.emit);
157+
Object.defineProperty(er,kExpandStackSymbol,{
158+
value: enhanceStackTrace.bind(null,er,capture),
159+
configurable: true
160+
});
161+
}catch(e){}
162+
163+
// Note: The comments on the `throw` lines are intentional, they show
164+
// up in Node's output if this results in an unhandled exception.
112165
thrower;// Unhandled 'error' event
113166
}
114167
// At least give some kind of context to the user
115168
consterrors=lazyErrors();
116169
consterr=newerrors.Error('ERR_UNHANDLED_ERROR',er);
117170
err.context=er;
118-
throwerr;
171+
throwerr;// Unhandled 'error' event
119172
}
120173

121174
consthandler=events[type];

‎lib/internal/bootstrap_node.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,11 @@
438438
}catch(er){
439439
// nothing to be done about it at this point.
440440
}
441+
try{
442+
const{ kExpandStackSymbol }=NativeModule.require('internal/util');
443+
if(typeofer[kExpandStackSymbol]==='function')
444+
er[kExpandStackSymbol]();
445+
}catch(er){}
441446
returnfalse;
442447
}
443448

‎lib/internal/util.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,5 +320,6 @@ module.exports ={
320320

321321
// Used by the buffer module to capture an internal reference to the
322322
// default isEncoding implementation, just in case userland overrides it.
323-
kIsEncodingSymbol: Symbol('node.isEncoding')
323+
kIsEncodingSymbol: Symbol('kIsEncodingSymbol'),
324+
kExpandStackSymbol: Symbol('kExpandStackSymbol')
324325
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
require('../common');
3+
constEventEmitter=require('events');
4+
5+
functionfoo(){
6+
functionbar(){
7+
returnnewError('foo:bar');
8+
}
9+
10+
returnbar();
11+
}
12+
13+
constee=newEventEmitter();
14+
consterr=foo();
15+
16+
functionquux(){
17+
ee.emit('error',err);
18+
}
19+
20+
quux();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
events.js:*
2+
throw er; // Unhandled 'error' event
3+
^
4+
5+
Error: foo:bar
6+
at bar (*events_unhandled_error_common_trace.js:*:*)
7+
at foo (*events_unhandled_error_common_trace.js:*:*)
8+
at Object.<anonymous> (*events_unhandled_error_common_trace.js:*:*)
9+
at Module._compile (module.js:*:*)
10+
at Object.Module._extensions..js (module.js:*:*)
11+
at Module.load (module.js:*:*)
12+
at tryModuleLoad (module.js:*:*)
13+
at Function.Module._load (module.js:*:*)
14+
at Function.Module.runMain (module.js:*:*)
15+
at startup (bootstrap_node.js:*:*)
16+
Emitted 'error' event at:
17+
at quux (*events_unhandled_error_common_trace.js:*:*)
18+
at Object.<anonymous> (*events_unhandled_error_common_trace.js:*:*)
19+
at Module._compile (module.js:*:*)
20+
[... lines matching original stack trace ...]
21+
at startup (bootstrap_node.js:*:*)
22+
at bootstrap_node.js:*:*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
require('../common');
3+
constEventEmitter=require('events');
4+
conster=newError();
5+
process.nextTick(()=>{
6+
newEventEmitter().emit('error',er);
7+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
events.js:*
2+
throw er; // Unhandled 'error' event
3+
^
4+
5+
Error
6+
at Object.<anonymous> (*events_unhandled_error_nexttick.js:*:*)
7+
at Module._compile (module.js:*:*)
8+
at Object.Module._extensions..js (module.js:*:*)
9+
at Module.load (module.js:*:*)
10+
at tryModuleLoad (module.js:*:*)
11+
at Function.Module._load (module.js:*:*)
12+
at Function.Module.runMain (module.js:*:*)
13+
at startup (bootstrap_node.js:*:*)
14+
at bootstrap_node.js:*:*
15+
Emitted 'error' event at:
16+
at process.nextTick (*events_unhandled_error_nexttick.js:*:*)
17+
at process._tickCallback (internal/process/next_tick.js:*:*)
18+
at Function.Module.runMain (module.js:*:*)
19+
at startup (bootstrap_node.js:*:*)
20+
at bootstrap_node.js:*:*
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use strict';
2+
require('../common');
3+
constEventEmitter=require('events');
4+
newEventEmitter().emit('error',newError());
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
events.js:*
2+
throw er; // Unhandled 'error' event
3+
^
4+
5+
Error
6+
at Object.<anonymous> (*events_unhandled_error_sameline.js:*:*)
7+
at Module._compile (module.js:*:*)
8+
at Object.Module._extensions..js (module.js:*:*)
9+
at Module.load (module.js:*:*)
10+
at tryModuleLoad (module.js:*:*)
11+
at Function.Module._load (module.js:*:*)
12+
at Function.Module.runMain (module.js:*:*)
13+
at startup (bootstrap_node.js:*:*)
14+
at bootstrap_node.js:*:*
15+
Emitted 'error' event at:
16+
at Object.<anonymous> (*events_unhandled_error_sameline.js:*:*)
17+
at Module._compile (module.js:*:*)
18+
[... lines matching original stack trace ...]
19+
at bootstrap_node.js:*:*
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constassert=require('assert');
4+
constEventEmitter=require('events');
5+
6+
// Tests that the error stack where the exception was thrown is *not* appended.
7+
8+
process.on('uncaughtException',common.mustCall((err)=>{
9+
constlines=err.stack.split('\n');
10+
assert.strictEqual(lines[0],'Error');
11+
lines.slice(1).forEach((line)=>{
12+
assert(/^at/.test(line),`${line} has an unexpected format`);
13+
});
14+
}));
15+
16+
newEventEmitter().emit('error',newError());

0 commit comments

Comments
(0)