Skip to content

Commit 3d9f2c0

Browse files
fix: prevent invalid error stack traces (#617)
1 parent 14d4294 commit 3d9f2c0

File tree

4 files changed

+35
-12
lines changed

4 files changed

+35
-12
lines changed

src/features/jserrors/constants.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
import { FEATURE_NAMES } from '../../loaders/features/features'
22

33
export const FEATURE_NAME = FEATURE_NAMES.jserrors
4-
export const NR_ERR_PROP = 'nr@seenError'

src/features/jserrors/instrument/index.js

+34-9
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export class Instrument extends InstrumentBase {
4747

4848
globalScope.addEventListener('error', (errorEvent) => {
4949
if (!this.abortHandler) return
50+
51+
/**
52+
* If the spa feature is loaded, errors may already have been captured in the `fn-err` listener above.
53+
* This ensures those errors are not captured twice.
54+
*/
5055
if (this.#seenErrors.has(errorEvent.error)) {
5156
this.#seenErrors.delete(errorEvent.error)
5257
return
@@ -66,16 +71,31 @@ export class Instrument extends InstrumentBase {
6671
this.abortHandler = undefined // weakly allow this abort op to run only once
6772
}
6873

74+
/**
75+
* Any value can be used with the `throw` keyword. This function ensures that the value is
76+
* either a proper Error instance or attempts to convert it to an UncaughtError instance.
77+
* @param {any} error The value thrown
78+
* @returns {Error|UncaughtError} The converted error instance
79+
*/
6980
#castError (error) {
7081
if (error instanceof Error) {
7182
return error
7283
}
7384

74-
if (typeof error.message !== 'undefined') {
75-
return new UncaughtError(error.message, error.filename, error.lineno, error.colno)
85+
/**
86+
* The thrown value may contain a message property. If it does, try to treat the thrown
87+
* value as an Error-like object.
88+
*/
89+
if (typeof error?.message !== 'undefined') {
90+
return new UncaughtError(
91+
error.message,
92+
error.filename || error.sourceURL,
93+
error.lineno || error.line,
94+
error.colno || error.col
95+
)
7696
}
7797

78-
return new UncaughtError(error)
98+
return new UncaughtError(typeof error === 'string' ? error : stringify(error))
7999
}
80100

81101
/**
@@ -85,6 +105,7 @@ export class Instrument extends InstrumentBase {
85105
*/
86106
#castPromiseRejectionEvent (promiseRejectionEvent) {
87107
let prefix = 'Unhandled Promise Rejection: '
108+
88109
if (promiseRejectionEvent?.reason instanceof Error) {
89110
try {
90111
promiseRejectionEvent.reason.message = prefix + promiseRejectionEvent.reason.message
@@ -93,12 +114,12 @@ export class Instrument extends InstrumentBase {
93114
return promiseRejectionEvent.reason
94115
}
95116
}
96-
if (typeof promiseRejectionEvent.reason === 'undefined') return new Error(prefix)
97-
try {
98-
return new Error(prefix + stringify(promiseRejectionEvent.reason))
99-
} catch (e) {
100-
return new Error(promiseRejectionEvent.reason)
101-
}
117+
118+
if (typeof promiseRejectionEvent.reason === 'undefined') return new UncaughtError(prefix)
119+
120+
const error = this.#castError(promiseRejectionEvent.reason)
121+
error.message = prefix + error.message
122+
return error
102123
}
103124

104125
/**
@@ -111,6 +132,10 @@ export class Instrument extends InstrumentBase {
111132
return errorEvent.error
112133
}
113134

135+
/**
136+
* Older browsers do not contain the `error` property on the ErrorEvent instance.
137+
* https://caniuse.com/mdn-api_errorevent_error
138+
*/
114139
return new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno)
115140
}
116141
}

tests/functional/err/unhandled-rejection-readable.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ testDriver.test('unhandledPromiseRejections are caught and are readable', suppor
2828
assertErrorAttributes(t, request.query)
2929
const actualErrors = getErrorsFromResponse(request, browser)
3030
const expectedErrorMessages = [
31-
{ message: 'Unhandled Promise Rejection: "Test"', tested: false, meta: 'string' },
31+
{ message: 'Unhandled Promise Rejection: Test', tested: false, meta: 'string' },
3232
{ message: 'Unhandled Promise Rejection: 1', tested: false, meta: 'number' },
3333
{ message: 'Unhandled Promise Rejection: {"a":1,"b":{"a":1}}', tested: false, meta: 'nested obj' },
3434
{ message: 'Unhandled Promise Rejection: [1,2,3]', tested: false, meta: 'array' },

tests/functional/spa/errors.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ testDriver.test('string error in custom tracer', function (t, browser, router) {
179179
var nodeId = interactionTree.children[0].nodeId
180180

181181
var error = errors[0]
182-
console.log(JSON.stringify(error))
183182
t.equal(error.params.message, 'some error')
184183
t.equal(error.params.browserInteractionId, interactionId, 'should have the correct interaction id')
185184
t.equal(error.params.parentNodeId, nodeId, 'has the correct node id')

0 commit comments

Comments
 (0)