1+ import { StackFrame } from '@sentry/types' ;
2+
13/**
24 * This was originally forked from https://github.com/occ/TraceKit, but has since been
35 * largely modified and is now maintained as part of Sentry JS SDK.
46 */
57
68/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-lines */
79
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-
2510/**
2611 * An object representing a JavaScript stack trace.
2712 * {Object} StackTrace
@@ -32,9 +17,7 @@ export interface StackFrame {
3217export interface StackTrace {
3318 name : string ;
3419 message : string ;
35- mechanism ?: string ;
3620 stack : StackFrame [ ] ;
37- failed ?: boolean ;
3821}
3922
4023// global reference to slice
@@ -54,11 +37,13 @@ const geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
5437const chromeEval = / \( ( \S * ) (?: : ( \d + ) ) (?: : ( \d + ) ) \) / ;
5538// Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
5639const 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;
5743
5844/** 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 [ ] = [ ] ;
6247 let popSize = 0 ;
6348
6449 if ( ex ) {
@@ -70,50 +55,52 @@ export function computeStackTrace(ex: any): StackTrace {
7055 }
7156
7257 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 ) ;
8064 } catch ( e ) {
8165 // no-empty
8266 }
8367
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 ) ;
9170 }
9271
9372 return {
9473 message : extractMessage ( ex ) ,
9574 name : ex && ex . name ,
96- stack : [ ] ,
97- failed : true ,
75+ stack : frames ,
9876 } ;
9977}
10078
10179/** 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' ) ;
11084 let isEval ;
11185 let submatch ;
11286 let parts ;
113- let element ;
87+ let element : StackFrame | undefined ;
11488
11589 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 ) ) ) {
117104 isEval = parts [ 2 ] && parts [ 2 ] . indexOf ( 'eval' ) === 0 ; // start of line
118105 if ( isEval && ( submatch = chromeEval . exec ( parts [ 2 ] ) ) ) {
119106 // throw out eval line/column and use top-most line/column number
@@ -124,20 +111,20 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
124111
125112 // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
126113 // 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 ] ) ;
128115
129116 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 ,
134121 } ;
135122 } else if ( ( parts = winjs . exec ( line ) ) ) {
136123 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 ,
141128 } ;
142129 } else if ( ( parts = gecko . exec ( line ) ) ) {
143130 isEval = parts [ 3 ] && parts [ 3 ] . indexOf ( ' > eval' ) > - 1 ;
@@ -149,86 +136,24 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
149136 parts [ 5 ] = '' ; // no column when eval
150137 }
151138
152- let url = parts [ 3 ] ;
139+ let filename = parts [ 3 ] ;
153140 let func = parts [ 1 ] || UNKNOWN_FUNCTION ;
154- [ func , url ] = extractSafariExtensionDetails ( func , url ) ;
141+ [ func , filename ] = extractSafariExtensionDetails ( func , filename ) ;
155142
156143 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 ,
161148 } ;
162149 } else {
163150 continue ;
164151 }
165152
166- stack . push ( element ) ;
167- }
168-
169- if ( ! stack . length ) {
170- return null ;
153+ frames . push ( element ) ;
171154 }
172155
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 ;
232157}
233158
234159/**
@@ -251,30 +176,18 @@ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null {
251176 * Unfortunatelly "just" changing RegExp is too complicated now and making it pass all tests
252177 * and fix this case seems like an impossible, or at least way too time-consuming task.
253178 */
254- const extractSafariExtensionDetails = ( func : string , url : string ) : [ string , string ] => {
179+ const extractSafariExtensionDetails = ( func : string , filename : string ) : [ string , string ] => {
255180 const isSafariExtension = func . indexOf ( 'safari-extension' ) !== - 1 ;
256181 const isSafariWebExtension = func . indexOf ( 'safari-web-extension' ) !== - 1 ;
257182
258183 return isSafariExtension || isSafariWebExtension
259184 ? [
260185 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 } ` ,
262187 ]
263- : [ func , url ] ;
188+ : [ func , filename ] ;
264189} ;
265190
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-
278191/**
279192 * There are cases where stacktrace.message is an Event object
280193 * https://github.com/getsentry/sentry-javascript/issues/1949
0 commit comments