@@ -23,6 +23,15 @@ function dynamicRequire(mod, request) {
23
23
return mod . require ( request ) ;
24
24
}
25
25
26
+ /**
27
+ * @typedef {{
28
+ * enabled: boolean;
29
+ * originalValue: any;
30
+ * installedValue: any;
31
+ * }} HookState
32
+ * Used for installing and uninstalling hooks
33
+ */
34
+
26
35
// Increment this if the format of sharedData changes in a breaking way.
27
36
var sharedDataVersion = 1 ;
28
37
@@ -63,8 +72,11 @@ function initializeSharedData(defaults) {
63
72
var sharedData = initializeSharedData ( {
64
73
65
74
// Only install once if called multiple times
66
- errorFormatterInstalled : false ,
67
- uncaughtShimInstalled : false ,
75
+ // Remember how the environment looked before installation so we can restore if able
76
+ /** @type {HookState } */
77
+ errorPrepareStackTraceHook : undefined ,
78
+ /** @type {HookState } */
79
+ processEmitHook : undefined ,
68
80
69
81
// If true, the caches are reset before a stack trace formatting operation
70
82
emptyCacheBetweenOperations : false ,
@@ -483,38 +495,45 @@ try {
483
495
484
496
const ErrorPrototypeToString = ( err ) => Error . prototype . toString . call ( err ) ;
485
497
486
- // This function is part of the V8 stack trace API, for more info see:
487
- // https://v8.dev/docs/stack-trace-api
488
- function prepareStackTrace ( error , stack ) {
489
- if ( sharedData . emptyCacheBetweenOperations ) {
490
- sharedData . fileContentsCache = { } ;
491
- sharedData . sourceMapCache = { } ;
492
- }
493
-
494
- // node gives its own errors special treatment. Mimic that behavior
495
- // https://github.com/nodejs/node/blob/3cbaabc4622df1b4009b9d026a1a970bdbae6e89/lib/internal/errors.js#L118-L128
496
- // https://github.com/nodejs/node/pull/39182
497
- var errorString ;
498
- if ( kIsNodeError ) {
499
- if ( kIsNodeError in error ) {
500
- errorString = `${ error . name } [${ error . code } ]: ${ error . message } ` ;
498
+ /** @param {HookState } hookState */
499
+ function createPrepareStackTrace ( hookState ) {
500
+ return prepareStackTrace ;
501
+
502
+ // This function is part of the V8 stack trace API, for more info see:
503
+ // https://v8.dev/docs/stack-trace-api
504
+ function prepareStackTrace ( error , stack ) {
505
+ if ( ! hookState . enabled ) return hookState . originalValue . apply ( this , arguments ) ;
506
+
507
+ if ( sharedData . emptyCacheBetweenOperations ) {
508
+ sharedData . fileContentsCache = { } ;
509
+ sharedData . sourceMapCache = { } ;
510
+ }
511
+
512
+ // node gives its own errors special treatment. Mimic that behavior
513
+ // https://github.com/nodejs/node/blob/3cbaabc4622df1b4009b9d026a1a970bdbae6e89/lib/internal/errors.js#L118-L128
514
+ // https://github.com/nodejs/node/pull/39182
515
+ var errorString ;
516
+ if ( kIsNodeError ) {
517
+ if ( kIsNodeError in error ) {
518
+ errorString = `${ error . name } [${ error . code } ]: ${ error . message } ` ;
519
+ } else {
520
+ errorString = ErrorPrototypeToString ( error ) ;
521
+ }
501
522
} else {
502
- errorString = ErrorPrototypeToString ( error ) ;
523
+ var name = error . name || 'Error' ;
524
+ var message = error . message || '' ;
525
+ errorString = name + ": " + message ;
503
526
}
504
- } else {
505
- var name = error . name || 'Error' ;
506
- var message = error . message || '' ;
507
- errorString = name + ": " + message ;
508
- }
509
527
510
- var state = { nextPosition : null , curPosition : null } ;
511
- var processedStack = [ ] ;
512
- for ( var i = stack . length - 1 ; i >= 0 ; i -- ) {
513
- processedStack . push ( '\n at ' + wrapCallSite ( stack [ i ] , state ) ) ;
514
- state . nextPosition = state . curPosition ;
528
+ var state = { nextPosition : null , curPosition : null } ;
529
+ var processedStack = [ ] ;
530
+ for ( var i = stack . length - 1 ; i >= 0 ; i -- ) {
531
+ processedStack . push ( '\n at ' + wrapCallSite ( stack [ i ] , state ) ) ;
532
+ state . nextPosition = state . curPosition ;
533
+ }
534
+ state . curPosition = state . nextPosition = null ;
535
+ return errorString + processedStack . reverse ( ) . join ( '' ) ;
515
536
}
516
- state . curPosition = state . nextPosition = null ;
517
- return errorString + processedStack . reverse ( ) . join ( '' ) ;
518
537
}
519
538
520
539
// Generate position and snippet of original source with pointer
@@ -571,19 +590,26 @@ function printFatalErrorUponExit (error) {
571
590
}
572
591
573
592
function shimEmitUncaughtException ( ) {
574
- var origEmit = process . emit ;
593
+ const originalValue = process . emit ;
594
+ var hook = sharedData . processEmitHook = {
595
+ enabled : true ,
596
+ originalValue,
597
+ installedValue : undefined
598
+ } ;
575
599
var isTerminatingDueToFatalException = false ;
576
600
var fatalException ;
577
601
578
- process . emit = function ( type ) {
579
- const hadListeners = origEmit . apply ( this , arguments ) ;
580
- if ( type === 'uncaughtException' && ! hadListeners ) {
581
- isTerminatingDueToFatalException = true ;
582
- fatalException = arguments [ 1 ] ;
583
- process . exit ( 1 ) ;
584
- }
585
- if ( type === 'exit' && isTerminatingDueToFatalException ) {
586
- printFatalErrorUponExit ( fatalException ) ;
602
+ process . emit = sharedData . processEmitHook . installedValue = function ( type ) {
603
+ const hadListeners = originalValue . apply ( this , arguments ) ;
604
+ if ( hook . enabled ) {
605
+ if ( type === 'uncaughtException' && ! hadListeners ) {
606
+ isTerminatingDueToFatalException = true ;
607
+ fatalException = arguments [ 1 ] ;
608
+ process . exit ( 1 ) ;
609
+ }
610
+ if ( type === 'exit' && isTerminatingDueToFatalException ) {
611
+ printFatalErrorUponExit ( fatalException ) ;
612
+ }
587
613
}
588
614
return hadListeners ;
589
615
} ;
@@ -650,13 +676,19 @@ exports.install = function(options) {
650
676
options . emptyCacheBetweenOperations : false ;
651
677
}
652
678
679
+
653
680
// Install the error reformatter
654
- if ( ! sharedData . errorFormatterInstalled ) {
655
- sharedData . errorFormatterInstalled = true ;
656
- Error . prepareStackTrace = prepareStackTrace ;
681
+ if ( ! sharedData . errorPrepareStackTraceHook ) {
682
+ const originalValue = Error . prepareStackTrace ;
683
+ sharedData . errorPrepareStackTraceHook = {
684
+ enabled : true ,
685
+ originalValue,
686
+ installedValue : undefined
687
+ } ;
688
+ Error . prepareStackTrace = sharedData . errorPrepareStackTraceHook . installedValue = createPrepareStackTrace ( sharedData . errorPrepareStackTraceHook ) ;
657
689
}
658
690
659
- if ( ! sharedData . uncaughtShimInstalled ) {
691
+ if ( ! sharedData . processEmitHook ) {
660
692
var installHandler = 'handleUncaughtExceptions' in options ?
661
693
options . handleUncaughtExceptions : true ;
662
694
@@ -679,12 +711,35 @@ exports.install = function(options) {
679
711
// generated JavaScript code will be shown above the stack trace instead of
680
712
// the original source code.
681
713
if ( installHandler && hasGlobalProcessEventEmitter ( ) ) {
682
- sharedData . uncaughtShimInstalled = true ;
683
714
shimEmitUncaughtException ( ) ;
684
715
}
685
716
}
686
717
} ;
687
718
719
+ exports . uninstall = function ( ) {
720
+ if ( sharedData . processEmitHook ) {
721
+ // Disable behavior
722
+ sharedData . processEmitHook . enabled = false ;
723
+ // If possible, remove our hook function. May not be possible if subsequent third-party hooks have wrapped around us.
724
+ if ( process . emit === sharedData . processEmitHook . installedValue ) {
725
+ process . emit = sharedData . processEmitHook . originalValue ;
726
+ }
727
+ sharedData . processEmitHook = undefined ;
728
+ }
729
+ if ( sharedData . errorPrepareStackTraceHook ) {
730
+ // Disable behavior
731
+ sharedData . errorPrepareStackTraceHook . enabled = false ;
732
+ // If possible or necessary, remove our hook function.
733
+ // In vanilla environments, prepareStackTrace is `undefined`.
734
+ // We cannot delegate to `undefined` the way we can to a function w/`.apply()`; our only option is to remove the function.
735
+ // If we are the *first* hook installed, and another was installed on top of us, we have no choice but to remove both.
736
+ if ( Error . prepareStackTrace === sharedData . errorPrepareStackTraceHook . installedValue || typeof sharedData . errorPrepareStackTraceHook . originalValue !== 'function' ) {
737
+ Error . prepareStackTrace = sharedData . errorPrepareStackTraceHook . originalValue ;
738
+ }
739
+ sharedData . errorPrepareStackTraceHook = undefined ;
740
+ }
741
+ }
742
+
688
743
exports . resetRetrieveHandlers = function ( ) {
689
744
sharedData . retrieveFileHandlers . length = 0 ;
690
745
sharedData . retrieveMapHandlers . length = 0 ;
0 commit comments