1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1
2
import { StackFrame } from '@sentry/types' ;
2
3
3
- /**
4
- * This was originally forked from https://github.com/occ/TraceKit, but has since been
5
- * largely modified and is now maintained as part of Sentry JS SDK.
6
- */
7
-
8
- /* eslint-disable @typescript-eslint/no-unsafe-member-access, max-lines */
4
+ // global reference to slice
5
+ const UNKNOWN_FUNCTION = '?' ;
9
6
10
7
/**
11
8
* An object representing a JavaScript stack trace.
@@ -20,27 +17,133 @@ export interface StackTrace {
20
17
stack : StackFrame [ ] ;
21
18
}
22
19
23
- // global reference to slice
24
- const UNKNOWN_FUNCTION = '?' ;
20
+ type StackLineParser = ( line : string ) => StackFrame | undefined ;
25
21
26
22
// Chromium based browsers: Chrome, Brave, new Opera, new Edge
27
- const chrome =
23
+ const chromeRegex =
28
24
/ ^ \s * a t (?: ( .* ?) ? \( (?: a d d r e s s a t ) ? ) ? ( (?: f i l e | h t t p s ? | b l o b | c h r o m e - e x t e n s i o n | a d d r e s s | n a t i v e | e v a l | w e b p a c k | < a n o n y m o u s > | [ - a - z ] + : | .* b u n d l e | \/ ) .* ?) (?: : ( \d + ) ) ? (?: : ( \d + ) ) ? \) ? \s * $ / i;
25
+ const chromeEvalRegex = / \( ( \S * ) (?: : ( \d + ) ) (?: : ( \d + ) ) \) / ;
26
+
27
+ const chrome : StackLineParser = line => {
28
+ const parts = chromeRegex . exec ( line ) ;
29
+
30
+ if ( parts ) {
31
+ const isEval = parts [ 2 ] && parts [ 2 ] . indexOf ( 'eval' ) === 0 ; // start of line
32
+
33
+ if ( isEval ) {
34
+ const subMatch = chromeEvalRegex . exec ( parts [ 2 ] ) ;
35
+
36
+ if ( subMatch ) {
37
+ // throw out eval line/column and use top-most line/column number
38
+ parts [ 2 ] = subMatch [ 1 ] ; // url
39
+ parts [ 3 ] = subMatch [ 2 ] ; // line
40
+ parts [ 4 ] = subMatch [ 3 ] ; // column
41
+ }
42
+ }
43
+
44
+ // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
45
+ // would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable)
46
+ const [ func , filename ] = extractSafariExtensionDetails ( parts [ 1 ] || UNKNOWN_FUNCTION , parts [ 2 ] ) ;
47
+
48
+ return {
49
+ filename,
50
+ function : func ,
51
+ lineno : parts [ 3 ] ? + parts [ 3 ] : undefined ,
52
+ colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
53
+ } ;
54
+ }
55
+
56
+ return ;
57
+ } ;
58
+
29
59
// gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it
30
60
// generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js
31
61
// We need this specific case for now because we want no other regex to match.
32
- const gecko =
62
+ const geckoREgex =
33
63
/ ^ \s * ( .* ?) (?: \( ( .* ?) \) ) ? (?: ^ | @ ) ? ( (?: f i l e | h t t p s ? | b l o b | c h r o m e | w e b p a c k | r e s o u r c e | m o z - e x t e n s i o n | c a p a c i t o r ) .* ?: \/ .* ?| \[ n a t i v e c o d e \] | [ ^ @ ] * (?: b u n d l e | \d + \. j s ) | \/ [ \w \- . / = ] + ) (?: : ( \d + ) ) ? (?: : ( \d + ) ) ? \s * $ / i;
34
- const winjs =
64
+ const geckoEvalRegex = / ( \S + ) l i n e ( \d + ) (?: > e v a l l i n e \d + ) * > e v a l / i;
65
+
66
+ const gecko : StackLineParser = line => {
67
+ const parts = geckoREgex . exec ( line ) ;
68
+
69
+ if ( parts ) {
70
+ const isEval = parts [ 3 ] && parts [ 3 ] . indexOf ( ' > eval' ) > - 1 ;
71
+ if ( isEval ) {
72
+ const subMatch = geckoEvalRegex . exec ( parts [ 3 ] ) ;
73
+
74
+ if ( subMatch ) {
75
+ // throw out eval line/column and use top-most line number
76
+ parts [ 1 ] = parts [ 1 ] || `eval` ;
77
+ parts [ 3 ] = subMatch [ 1 ] ;
78
+ parts [ 4 ] = subMatch [ 2 ] ;
79
+ parts [ 5 ] = '' ; // no column when eval
80
+ }
81
+ }
82
+
83
+ let filename = parts [ 3 ] ;
84
+ let func = parts [ 1 ] || UNKNOWN_FUNCTION ;
85
+ [ func , filename ] = extractSafariExtensionDetails ( func , filename ) ;
86
+
87
+ return {
88
+ filename,
89
+ function : func ,
90
+ lineno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
91
+ colno : parts [ 5 ] ? + parts [ 5 ] : undefined ,
92
+ } ;
93
+ }
94
+
95
+ return ;
96
+ } ;
97
+
98
+ const winjsRegex =
35
99
/ ^ \s * a t (?: ( (?: \[ o b j e c t o b j e c t \] ) ? .+ ) ) ? \( ? ( (?: f i l e | m s - a p p x | h t t p s ? | w e b p a c k | b l o b ) : .* ?) : ( \d + ) (?: : ( \d + ) ) ? \) ? \s * $ / i;
36
- const geckoEval = / ( \S + ) l i n e ( \d + ) (?: > e v a l l i n e \d + ) * > e v a l / i;
37
- const chromeEval = / \( ( \S * ) (?: : ( \d + ) ) (?: : ( \d + ) ) \) / ;
38
- // Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
39
- const reactMinifiedRegexp = / M i n i f i e d R e a c t e r r o r # \d + ; / i;
100
+
101
+ const winjs : StackLineParser = line => {
102
+ const parts = winjsRegex . exec ( line ) ;
103
+
104
+ return parts
105
+ ? {
106
+ filename : parts [ 2 ] ,
107
+ function : parts [ 1 ] || UNKNOWN_FUNCTION ,
108
+ lineno : + parts [ 3 ] ,
109
+ colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
110
+ }
111
+ : undefined ;
112
+ } ;
113
+
40
114
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;
115
+
116
+ const opera10 : StackLineParser = line => {
117
+ const parts = opera10Regex . exec ( line ) ;
118
+
119
+ return parts
120
+ ? {
121
+ filename : parts [ 2 ] ,
122
+ function : parts [ 3 ] || UNKNOWN_FUNCTION ,
123
+ lineno : + parts [ 1 ] ,
124
+ }
125
+ : undefined ;
126
+ } ;
127
+
41
128
const opera11Regex =
42
129
/ 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;
43
130
131
+ const opera11 : StackLineParser = line => {
132
+ const parts = opera11Regex . exec ( line ) ;
133
+
134
+ return parts
135
+ ? {
136
+ filename : parts [ 5 ] ,
137
+ function : parts [ 3 ] || parts [ 4 ] || UNKNOWN_FUNCTION ,
138
+ lineno : + parts [ 1 ] ,
139
+ colno : + parts [ 2 ] ,
140
+ }
141
+ : undefined ;
142
+ } ;
143
+
144
+ // Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
145
+ const reactMinifiedRegexp = / M i n i f i e d R e a c t e r r o r # \d + ; / i;
146
+
44
147
/** JSDoc */
45
148
export function computeStackTrace ( ex : Error & { framesToPop ?: number ; stacktrace ?: string } ) : StackTrace {
46
149
let frames : StackFrame [ ] = [ ] ;
@@ -60,7 +163,16 @@ export function computeStackTrace(ex: Error & { framesToPop?: number; stacktrace
60
163
// reliably in other circumstances.
61
164
const stacktrace = ex . stacktrace || ex . stack || '' ;
62
165
63
- frames = parseFrames ( stacktrace ) ;
166
+ for ( const line of stacktrace . split ( '\n' ) ) {
167
+ for ( const parser of [ opera10 , opera11 , chrome , winjs , gecko ] ) {
168
+ const frame = parser ( line ) ;
169
+
170
+ if ( frame ) {
171
+ frames . push ( frame ) ;
172
+ break ;
173
+ }
174
+ }
175
+ }
64
176
} catch ( e ) {
65
177
// no-empty
66
178
}
@@ -76,86 +188,6 @@ export function computeStackTrace(ex: Error & { framesToPop?: number; stacktrace
76
188
} ;
77
189
}
78
190
79
- /** JSDoc */
80
- // eslint-disable-next-line complexity
81
- function parseFrames ( stackString : string ) : StackFrame [ ] {
82
- const frames : StackFrame [ ] = [ ] ;
83
- const lines = stackString . split ( '\n' ) ;
84
- let isEval ;
85
- let submatch ;
86
- let parts ;
87
- let element : StackFrame | undefined ;
88
-
89
- for ( const line of lines ) {
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 ) ) ) {
104
- isEval = parts [ 2 ] && parts [ 2 ] . indexOf ( 'eval' ) === 0 ; // start of line
105
- if ( isEval && ( submatch = chromeEval . exec ( parts [ 2 ] ) ) ) {
106
- // throw out eval line/column and use top-most line/column number
107
- parts [ 2 ] = submatch [ 1 ] ; // url
108
- parts [ 3 ] = submatch [ 2 ] ; // line
109
- parts [ 4 ] = submatch [ 3 ] ; // column
110
- }
111
-
112
- // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
113
- // would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable)
114
- const [ func , filename ] = extractSafariExtensionDetails ( parts [ 1 ] || UNKNOWN_FUNCTION , parts [ 2 ] ) ;
115
-
116
- element = {
117
- filename,
118
- function : func ,
119
- lineno : parts [ 3 ] ? + parts [ 3 ] : undefined ,
120
- colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
121
- } ;
122
- } else if ( ( parts = winjs . exec ( line ) ) ) {
123
- element = {
124
- filename : parts [ 2 ] ,
125
- function : parts [ 1 ] || UNKNOWN_FUNCTION ,
126
- lineno : + parts [ 3 ] ,
127
- colno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
128
- } ;
129
- } else if ( ( parts = gecko . exec ( line ) ) ) {
130
- isEval = parts [ 3 ] && parts [ 3 ] . indexOf ( ' > eval' ) > - 1 ;
131
- if ( isEval && ( submatch = geckoEval . exec ( parts [ 3 ] ) ) ) {
132
- // throw out eval line/column and use top-most line number
133
- parts [ 1 ] = parts [ 1 ] || `eval` ;
134
- parts [ 3 ] = submatch [ 1 ] ;
135
- parts [ 4 ] = submatch [ 2 ] ;
136
- parts [ 5 ] = '' ; // no column when eval
137
- }
138
-
139
- let filename = parts [ 3 ] ;
140
- let func = parts [ 1 ] || UNKNOWN_FUNCTION ;
141
- [ func , filename ] = extractSafariExtensionDetails ( func , filename ) ;
142
-
143
- element = {
144
- filename,
145
- function : func ,
146
- lineno : parts [ 4 ] ? + parts [ 4 ] : undefined ,
147
- colno : parts [ 5 ] ? + parts [ 5 ] : undefined ,
148
- } ;
149
- } else {
150
- continue ;
151
- }
152
-
153
- frames . push ( element ) ;
154
- }
155
-
156
- return frames ;
157
- }
158
-
159
191
/**
160
192
* Safari web extensions, starting version unknown, can produce "frames-only" stacktraces.
161
193
* What it means, is that instead of format like:
0 commit comments