1
+ import { StackFrame } from '@sentry/types' ;
2
+
1
3
/**
2
4
* This was originally forked from https://github.com/occ/TraceKit, but has since been
3
5
* largely modified and is now maintained as part of Sentry JS SDK.
4
6
*/
5
7
6
8
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-lines */
7
9
8
- /**
9
- * An object representing a single stack frame.
10
- * {Object} StackFrame
11
- * {string} url The JavaScript or HTML file URL.
12
- * {string} func The function name, or empty for anonymous functions (if guessing did not work).
13
- * {string[]?} args The arguments passed to the function, if known.
14
- * {number=} line The line number, if known.
15
- * {number=} column The column number, if known.
16
- * {string[]} context An array of source code lines; the middle element corresponds to the correct line#.
17
- */
18
- export interface StackFrame {
19
- url : string ;
20
- func : string ;
21
- line : number | null ;
22
- column : number | null ;
23
- }
24
-
25
10
/**
26
11
* An object representing a JavaScript stack trace.
27
12
* {Object} StackTrace
@@ -32,9 +17,7 @@ export interface StackFrame {
32
17
export interface StackTrace {
33
18
name : string ;
34
19
message : string ;
35
- mechanism ?: string ;
36
20
stack : StackFrame [ ] ;
37
- failed ?: boolean ;
38
21
}
39
22
40
23
// global reference to slice
@@ -54,11 +37,13 @@ const geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
54
37
const chromeEval = / \( ( \S * ) (?: : ( \d + ) ) (?: : ( \d + ) ) \) / ;
55
38
// Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
56
39
const reactMinifiedRegexp = / M i n i f i e d R e a c t e r r o r # \d + ; / i;
40
+ const opera10Regex = / l i n e ( \d + ) .* s c r i p t (?: i n ) ? ( \S + ) (?: : i n f u n c t i o n ( \S + ) ) ? $ / i;
41
+ const opera11Regex =
42
+ / l i n e ( \d + ) , c o l u m n ( \d + ) \s * (?: i n (?: < a n o n y m o u s f u n c t i o n : ( [ ^ > ] + ) > | ( [ ^ ) ] + ) ) \( .* \) ) ? i n ( .* ) : \s * $ / i;
57
43
58
44
/** JSDoc */
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
60
- export function computeStackTrace ( ex : any ) : StackTrace {
61
- let stack = null ;
45
+ export function computeStackTrace ( ex : Error & { framesToPop ?: number ; stacktrace ?: string } ) : StackTrace {
46
+ let frames : StackFrame [ ] = [ ] ;
62
47
let popSize = 0 ;
63
48
64
49
if ( ex ) {
@@ -70,50 +55,52 @@ export function computeStackTrace(ex: any): StackTrace {
70
55
}
71
56
72
57
try {
73
- // This must be tried first because Opera 10 *destroys*
74
- // its stacktrace property if you try to access the stack
75
- // property first!!
76
- stack = computeStackTraceFromStacktraceProp ( ex ) ;
77
- if ( stack ) {
78
- return popFrames ( stack , popSize ) ;
79
- }
58
+ // Access and store the stacktrace property before doing ANYTHING
59
+ // else to it because Opera is not very good at providing it
60
+ // reliably in other circumstances.
61
+ const stacktrace = ex . stacktrace || ex . stack || '' ;
62
+
63
+ frames = parseFrames ( stacktrace ) ;
80
64
} catch ( e ) {
81
65
// no-empty
82
66
}
83
67
84
- try {
85
- stack = computeStackTraceFromStackProp ( ex ) ;
86
- if ( stack ) {
87
- return popFrames ( stack , popSize ) ;
88
- }
89
- } catch ( e ) {
90
- // no-empty
68
+ if ( frames . length && popSize > 0 ) {
69
+ frames = frames . slice ( popSize ) ;
91
70
}
92
71
93
72
return {
94
73
message : extractMessage ( ex ) ,
95
74
name : ex && ex . name ,
96
- stack : [ ] ,
97
- failed : true ,
75
+ stack : frames ,
98
76
} ;
99
77
}
100
78
101
79
/** JSDoc */
102
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, complexity
103
- function computeStackTraceFromStackProp ( ex : any ) : StackTrace | null {
104
- if ( ! ex || ! ex . stack ) {
105
- return null ;
106
- }
107
-
108
- const stack = [ ] ;
109
- const lines = ex . stack . split ( '\n' ) ;
80
+ // eslint-disable-next-line complexity
81
+ function parseFrames ( stackString : string ) : StackFrame [ ] {
82
+ const frames : StackFrame [ ] = [ ] ;
83
+ const lines = stackString . split ( '\n' ) ;
110
84
let isEval ;
111
85
let submatch ;
112
86
let parts ;
113
- let element ;
87
+ let element : StackFrame | undefined ;
114
88
115
89
for ( const line of lines ) {
116
- if ( ( parts = chrome . exec ( line ) ) ) {
90
+ if ( ( parts = opera10Regex . exec ( line ) ) ) {
91
+ element = {
92
+ filename : parts [ 2 ] ,
93
+ function : parts [ 3 ] || UNKNOWN_FUNCTION ,
94
+ lineno : + parts [ 1 ] ,
95
+ } ;
96
+ } else if ( ( parts = opera11Regex . exec ( line ) ) ) {
97
+ element = {
98
+ filename : parts [ 5 ] ,
99
+ function : parts [ 3 ] || parts [ 4 ] || UNKNOWN_FUNCTION ,
100
+ lineno : + parts [ 1 ] ,
101
+ colno : + parts [ 2 ] ,
102
+ } ;
103
+ } else if ( ( parts = chrome . exec ( line ) ) ) {
117
104
isEval = parts [ 2 ] && parts [ 2 ] . indexOf ( 'eval' ) === 0 ; // start of line
118
105
if ( isEval && ( submatch = chromeEval . exec ( parts [ 2 ] ) ) ) {
119
106
// throw out eval line/column and use top-most line/column number
@@ -124,20 +111,20 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
124
111
125
112
// Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
126
113
// would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable)
127
- const [ func , url ] = extractSafariExtensionDetails ( parts [ 1 ] || UNKNOWN_FUNCTION , parts [ 2 ] ) ;
114
+ const [ func , filename ] = extractSafariExtensionDetails ( parts [ 1 ] || UNKNOWN_FUNCTION , parts [ 2 ] ) ;
128
115
129
116
element = {
130
- url ,
131
- func,
132
- line : parts [ 3 ] ? + parts [ 3 ] : null ,
133
- column : parts [ 4 ] ? + parts [ 4 ] : null ,
117
+ filename ,
118
+ function : func ,
119
+ lineno : parts [ 3 ] ? + parts [ 3 ] : undefined ,
120
+ colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
134
121
} ;
135
122
} else if ( ( parts = winjs . exec ( line ) ) ) {
136
123
element = {
137
- url : parts [ 2 ] ,
138
- func : parts [ 1 ] || UNKNOWN_FUNCTION ,
139
- line : + parts [ 3 ] ,
140
- column : parts [ 4 ] ? + parts [ 4 ] : null ,
124
+ filename : parts [ 2 ] ,
125
+ function : parts [ 1 ] || UNKNOWN_FUNCTION ,
126
+ lineno : + parts [ 3 ] ,
127
+ colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
141
128
} ;
142
129
} else if ( ( parts = gecko . exec ( line ) ) ) {
143
130
isEval = parts [ 3 ] && parts [ 3 ] . indexOf ( ' > eval' ) > - 1 ;
@@ -149,86 +136,24 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
149
136
parts [ 5 ] = '' ; // no column when eval
150
137
}
151
138
152
- let url = parts [ 3 ] ;
139
+ let filename = parts [ 3 ] ;
153
140
let func = parts [ 1 ] || UNKNOWN_FUNCTION ;
154
- [ func , url ] = extractSafariExtensionDetails ( func , url ) ;
141
+ [ func , filename ] = extractSafariExtensionDetails ( func , filename ) ;
155
142
156
143
element = {
157
- url ,
158
- func,
159
- line : parts [ 4 ] ? + parts [ 4 ] : null ,
160
- column : parts [ 5 ] ? + parts [ 5 ] : null ,
144
+ filename ,
145
+ function : func ,
146
+ lineno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
147
+ colno : parts [ 5 ] ? + parts [ 5 ] : undefined ,
161
148
} ;
162
149
} else {
163
150
continue ;
164
151
}
165
152
166
- stack . push ( element ) ;
167
- }
168
-
169
- if ( ! stack . length ) {
170
- return null ;
153
+ frames . push ( element ) ;
171
154
}
172
155
173
- return {
174
- message : extractMessage ( ex ) ,
175
- name : ex . name ,
176
- stack,
177
- } ;
178
- }
179
-
180
- /** JSDoc */
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
- function computeStackTraceFromStacktraceProp ( ex : any ) : StackTrace | null {
183
- if ( ! ex || ! ex . stacktrace ) {
184
- return null ;
185
- }
186
- // Access and store the stacktrace property before doing ANYTHING
187
- // else to it because Opera is not very good at providing it
188
- // reliably in other circumstances.
189
- const stacktrace = ex . stacktrace ;
190
- const opera10Regex = / l i n e ( \d + ) .* s c r i p t (?: i n ) ? ( \S + ) (?: : i n f u n c t i o n ( \S + ) ) ? $ / i;
191
- const opera11Regex =
192
- / l i n e ( \d + ) , c o l u m n ( \d + ) \s * (?: i n (?: < a n o n y m o u s f u n c t i o n : ( [ ^ > ] + ) > | ( [ ^ ) ] + ) ) \( .* \) ) ? i n ( .* ) : \s * $ / i;
193
- const lines = stacktrace . split ( '\n' ) ;
194
- const stack = [ ] ;
195
- let parts ;
196
-
197
- for ( let line = 0 ; line < lines . length ; line += 2 ) {
198
- let element = null ;
199
- if ( ( parts = opera10Regex . exec ( lines [ line ] ) ) ) {
200
- element = {
201
- url : parts [ 2 ] ,
202
- func : parts [ 3 ] ,
203
- line : + parts [ 1 ] ,
204
- column : null ,
205
- } ;
206
- } else if ( ( parts = opera11Regex . exec ( lines [ line ] ) ) ) {
207
- element = {
208
- url : parts [ 5 ] ,
209
- func : parts [ 3 ] || parts [ 4 ] ,
210
- line : + parts [ 1 ] ,
211
- column : + parts [ 2 ] ,
212
- } ;
213
- }
214
-
215
- if ( element ) {
216
- if ( ! element . func && element . line ) {
217
- element . func = UNKNOWN_FUNCTION ;
218
- }
219
- stack . push ( element ) ;
220
- }
221
- }
222
-
223
- if ( ! stack . length ) {
224
- return null ;
225
- }
226
-
227
- return {
228
- message : extractMessage ( ex ) ,
229
- name : ex . name ,
230
- stack,
231
- } ;
156
+ return frames ;
232
157
}
233
158
234
159
/**
@@ -251,30 +176,18 @@ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null {
251
176
* Unfortunatelly "just" changing RegExp is too complicated now and making it pass all tests
252
177
* and fix this case seems like an impossible, or at least way too time-consuming task.
253
178
*/
254
- const extractSafariExtensionDetails = ( func : string , url : string ) : [ string , string ] => {
179
+ const extractSafariExtensionDetails = ( func : string , filename : string ) : [ string , string ] => {
255
180
const isSafariExtension = func . indexOf ( 'safari-extension' ) !== - 1 ;
256
181
const isSafariWebExtension = func . indexOf ( 'safari-web-extension' ) !== - 1 ;
257
182
258
183
return isSafariExtension || isSafariWebExtension
259
184
? [
260
185
func . indexOf ( '@' ) !== - 1 ? func . split ( '@' ) [ 0 ] : UNKNOWN_FUNCTION ,
261
- isSafariExtension ? `safari-extension:${ url } ` : `safari-web-extension:${ url } ` ,
186
+ isSafariExtension ? `safari-extension:${ filename } ` : `safari-web-extension:${ filename } ` ,
262
187
]
263
- : [ func , url ] ;
188
+ : [ func , filename ] ;
264
189
} ;
265
190
266
- /** Remove N number of frames from the stack */
267
- function popFrames ( stacktrace : StackTrace , popSize : number ) : StackTrace {
268
- try {
269
- return {
270
- ...stacktrace ,
271
- stack : stacktrace . stack . slice ( popSize ) ,
272
- } ;
273
- } catch ( e ) {
274
- return stacktrace ;
275
- }
276
- }
277
-
278
191
/**
279
192
* There are cases where stacktrace.message is an Event object
280
193
* https://github.com/getsentry/sentry-javascript/issues/1949
0 commit comments