@@ -24,8 +24,18 @@ function dynamicRequire(mod, request) {
24
24
}
25
25
26
26
// Only install once if called multiple times
27
- var errorFormatterInstalled = false ;
28
- var uncaughtShimInstalled = false ;
27
+ // Remember how the environment looked before installation so we can restore if able
28
+ /** @type {HookState } */
29
+ var errorPrepareStackTraceHook ;
30
+ /** @type {HookState } */
31
+ var processEmitHook ;
32
+ /**
33
+ * @typedef {{
34
+ * enabled: boolean;
35
+ * originalValue: any;
36
+ * installedValue: any;
37
+ * }} HookState
38
+ */
29
39
30
40
// If true, the caches are reset before a stack trace formatting operation
31
41
var emptyCacheBetweenOperations = false ;
@@ -431,38 +441,45 @@ try {
431
441
432
442
const ErrorPrototypeToString = ( err ) => Error . prototype . toString . call ( err ) ;
433
443
434
- // This function is part of the V8 stack trace API, for more info see:
435
- // https://v8.dev/docs/stack-trace-api
436
- function prepareStackTrace ( error , stack ) {
437
- if ( emptyCacheBetweenOperations ) {
438
- fileContentsCache = { } ;
439
- sourceMapCache = { } ;
440
- }
441
-
442
- // node gives its own errors special treatment. Mimic that behavior
443
- // https://github.com/nodejs/node/blob/3cbaabc4622df1b4009b9d026a1a970bdbae6e89/lib/internal/errors.js#L118-L128
444
- // https://github.com/nodejs/node/pull/39182
445
- var errorString ;
446
- if ( kIsNodeError ) {
447
- if ( kIsNodeError in error ) {
448
- errorString = `${ error . name } [${ error . code } ]: ${ error . message } ` ;
444
+ /** @param {HookState } hookState */
445
+ function createPrepareStackTrace ( hookState ) {
446
+ return prepareStackTrace ;
447
+
448
+ // This function is part of the V8 stack trace API, for more info see:
449
+ // https://v8.dev/docs/stack-trace-api
450
+ function prepareStackTrace ( error , stack ) {
451
+ if ( ! hookState . enabled ) return hookState . originalValue . apply ( this , arguments ) ;
452
+
453
+ if ( emptyCacheBetweenOperations ) {
454
+ fileContentsCache = { } ;
455
+ sourceMapCache = { } ;
456
+ }
457
+
458
+ // node gives its own errors special treatment. Mimic that behavior
459
+ // https://github.com/nodejs/node/blob/3cbaabc4622df1b4009b9d026a1a970bdbae6e89/lib/internal/errors.js#L118-L128
460
+ // https://github.com/nodejs/node/pull/39182
461
+ var errorString ;
462
+ if ( kIsNodeError ) {
463
+ if ( kIsNodeError in error ) {
464
+ errorString = `${ error . name } [${ error . code } ]: ${ error . message } ` ;
465
+ } else {
466
+ errorString = ErrorPrototypeToString ( error ) ;
467
+ }
449
468
} else {
450
- errorString = ErrorPrototypeToString ( error ) ;
469
+ var name = error . name || 'Error' ;
470
+ var message = error . message || '' ;
471
+ errorString = name + ": " + message ;
451
472
}
452
- } else {
453
- var name = error . name || 'Error' ;
454
- var message = error . message || '' ;
455
- errorString = name + ": " + message ;
456
- }
457
473
458
- var state = { nextPosition : null , curPosition : null } ;
459
- var processedStack = [ ] ;
460
- for ( var i = stack . length - 1 ; i >= 0 ; i -- ) {
461
- processedStack . push ( '\n at ' + wrapCallSite ( stack [ i ] , state ) ) ;
462
- state . nextPosition = state . curPosition ;
474
+ var state = { nextPosition : null , curPosition : null } ;
475
+ var processedStack = [ ] ;
476
+ for ( var i = stack . length - 1 ; i >= 0 ; i -- ) {
477
+ processedStack . push ( '\n at ' + wrapCallSite ( stack [ i ] , state ) ) ;
478
+ state . nextPosition = state . curPosition ;
479
+ }
480
+ state . curPosition = state . nextPosition = null ;
481
+ return errorString + processedStack . reverse ( ) . join ( '' ) ;
463
482
}
464
- state . curPosition = state . nextPosition = null ;
465
- return errorString + processedStack . reverse ( ) . join ( '' ) ;
466
483
}
467
484
468
485
// Generate position and snippet of original source with pointer
@@ -519,19 +536,26 @@ function printFatalErrorUponExit (error) {
519
536
}
520
537
521
538
function shimEmitUncaughtException ( ) {
522
- var origEmit = process . emit ;
539
+ const originalValue = process . emit ;
540
+ var hook = processEmitHook = {
541
+ enabled : true ,
542
+ originalValue,
543
+ installedValue : undefined
544
+ } ;
523
545
var isTerminatingDueToFatalException = false ;
524
546
var fatalException ;
525
547
526
- process . emit = function ( type ) {
527
- const hadListeners = origEmit . apply ( this , arguments ) ;
528
- if ( type === 'uncaughtException' && ! hadListeners ) {
529
- isTerminatingDueToFatalException = true ;
530
- fatalException = arguments [ 1 ] ;
531
- process . exit ( 1 ) ;
532
- }
533
- if ( type === 'exit' && isTerminatingDueToFatalException ) {
534
- printFatalErrorUponExit ( fatalException ) ;
548
+ process . emit = processEmitHook . installedValue = function ( type ) {
549
+ const hadListeners = originalValue . apply ( this , arguments ) ;
550
+ if ( hook . enabled ) {
551
+ if ( type === 'uncaughtException' && ! hadListeners ) {
552
+ isTerminatingDueToFatalException = true ;
553
+ fatalException = arguments [ 1 ] ;
554
+ process . exit ( 1 ) ;
555
+ }
556
+ if ( type === 'exit' && isTerminatingDueToFatalException ) {
557
+ printFatalErrorUponExit ( fatalException ) ;
558
+ }
535
559
}
536
560
return hadListeners ;
537
561
} ;
@@ -598,13 +622,19 @@ exports.install = function(options) {
598
622
options . emptyCacheBetweenOperations : false ;
599
623
}
600
624
625
+
601
626
// Install the error reformatter
602
- if ( ! errorFormatterInstalled ) {
603
- errorFormatterInstalled = true ;
604
- Error . prepareStackTrace = prepareStackTrace ;
627
+ if ( ! errorPrepareStackTraceHook ) {
628
+ const originalValue = Error . prepareStackTrace ;
629
+ errorPrepareStackTraceHook = {
630
+ enabled : true ,
631
+ originalValue,
632
+ installedValue : undefined
633
+ } ;
634
+ Error . prepareStackTrace = errorPrepareStackTraceHook . installedValue = createPrepareStackTrace ( errorPrepareStackTraceHook ) ;
605
635
}
606
636
607
- if ( ! uncaughtShimInstalled ) {
637
+ if ( ! processEmitHook ) {
608
638
var installHandler = 'handleUncaughtExceptions' in options ?
609
639
options . handleUncaughtExceptions : true ;
610
640
@@ -627,12 +657,35 @@ exports.install = function(options) {
627
657
// generated JavaScript code will be shown above the stack trace instead of
628
658
// the original source code.
629
659
if ( installHandler && hasGlobalProcessEventEmitter ( ) ) {
630
- uncaughtShimInstalled = true ;
631
660
shimEmitUncaughtException ( ) ;
632
661
}
633
662
}
634
663
} ;
635
664
665
+ exports . uninstall = function ( ) {
666
+ if ( processEmitHook ) {
667
+ // Disable behavior
668
+ processEmitHook . enabled = false ;
669
+ // If possible, remove our hook function. May not be possible if subsequent third-party hooks have wrapped around us.
670
+ if ( process . emit === processEmitHook . installedValue ) {
671
+ process . emit = processEmitHook . originalValue ;
672
+ }
673
+ processEmitHook = undefined ;
674
+ }
675
+ if ( errorPrepareStackTraceHook ) {
676
+ // Disable behavior
677
+ errorPrepareStackTraceHook . enabled = false ;
678
+ // If possible or necessary, remove our hook function.
679
+ // In vanilla environments, prepareStackTrace is `undefined`.
680
+ // We cannot delegate to `undefined` the way we can to a function w/`.apply()`; our only option is to remove the function.
681
+ // If we are the *first* hook installed, and another was installed on top of us, we have no choice but to remove both.
682
+ if ( Error . prepareStackTrace === errorPrepareStackTraceHook . installedValue || typeof errorPrepareStackTraceHook . originalValue !== 'function' ) {
683
+ Error . prepareStackTrace = errorPrepareStackTraceHook . originalValue ;
684
+ }
685
+ errorPrepareStackTraceHook = undefined ;
686
+ }
687
+ }
688
+
636
689
exports . resetRetrieveHandlers = function ( ) {
637
690
retrieveFileHandlers . length = 0 ;
638
691
retrieveMapHandlers . length = 0 ;
0 commit comments