@@ -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
} ;
@@ -671,13 +697,19 @@ exports.install = function(options) {
671
697
options . emptyCacheBetweenOperations : false ;
672
698
}
673
699
700
+
674
701
// Install the error reformatter
675
- if ( ! sharedData . errorFormatterInstalled ) {
676
- sharedData . errorFormatterInstalled = true ;
677
- Error . prepareStackTrace = prepareStackTrace ;
702
+ if ( ! sharedData . errorPrepareStackTraceHook ) {
703
+ const originalValue = Error . prepareStackTrace ;
704
+ sharedData . errorPrepareStackTraceHook = {
705
+ enabled : true ,
706
+ originalValue,
707
+ installedValue : undefined
708
+ } ;
709
+ Error . prepareStackTrace = sharedData . errorPrepareStackTraceHook . installedValue = createPrepareStackTrace ( sharedData . errorPrepareStackTraceHook ) ;
678
710
}
679
711
680
- if ( ! sharedData . uncaughtShimInstalled ) {
712
+ if ( ! sharedData . processEmitHook ) {
681
713
var installHandler = 'handleUncaughtExceptions' in options ?
682
714
options . handleUncaughtExceptions : true ;
683
715
@@ -700,12 +732,35 @@ exports.install = function(options) {
700
732
// generated JavaScript code will be shown above the stack trace instead of
701
733
// the original source code.
702
734
if ( installHandler && hasGlobalProcessEventEmitter ( ) ) {
703
- sharedData . uncaughtShimInstalled = true ;
704
735
shimEmitUncaughtException ( ) ;
705
736
}
706
737
}
707
738
} ;
708
739
740
+ exports . uninstall = function ( ) {
741
+ if ( sharedData . processEmitHook ) {
742
+ // Disable behavior
743
+ sharedData . processEmitHook . enabled = false ;
744
+ // If possible, remove our hook function. May not be possible if subsequent third-party hooks have wrapped around us.
745
+ if ( process . emit === sharedData . processEmitHook . installedValue ) {
746
+ process . emit = sharedData . processEmitHook . originalValue ;
747
+ }
748
+ sharedData . processEmitHook = undefined ;
749
+ }
750
+ if ( sharedData . errorPrepareStackTraceHook ) {
751
+ // Disable behavior
752
+ sharedData . errorPrepareStackTraceHook . enabled = false ;
753
+ // If possible or necessary, remove our hook function.
754
+ // In vanilla environments, prepareStackTrace is `undefined`.
755
+ // We cannot delegate to `undefined` the way we can to a function w/`.apply()`; our only option is to remove the function.
756
+ // If we are the *first* hook installed, and another was installed on top of us, we have no choice but to remove both.
757
+ if ( Error . prepareStackTrace === sharedData . errorPrepareStackTraceHook . installedValue || typeof sharedData . errorPrepareStackTraceHook . originalValue !== 'function' ) {
758
+ Error . prepareStackTrace = sharedData . errorPrepareStackTraceHook . originalValue ;
759
+ }
760
+ sharedData . errorPrepareStackTraceHook = undefined ;
761
+ }
762
+ }
763
+
709
764
exports . resetRetrieveHandlers = function ( ) {
710
765
sharedData . retrieveFileHandlers . length = 0 ;
711
766
sharedData . retrieveMapHandlers . length = 0 ;
0 commit comments