@@ -130,33 +130,25 @@ var (
130130 // PC=0 as "no caller available" (see e.g. log/slog.Record.PC), and packages
131131 // using PCs, initialize a uintptr to zero and expect runtime.CallersFrames
132132 // and runtime.FuncForPC to treat it as unknown.
133- knownPositions = map [string ]uintptr {}
133+ knownPositions = map [basicFrame ]uintptr {}
134134 positionCounters = []* Func {nil }
135135)
136136
137- func registerPosition (funcName string , file string , line int , col int ) uintptr {
138- key := file + ":" + itoa (line ) + ":" + itoa (col )
139- if pc , found := knownPositions [key ]; found {
137+ func registerPosition (frame basicFrame ) uintptr {
138+ if pc , found := knownPositions [frame ]; found {
140139 return pc
141140 }
142141 f := & Func {
143- name : funcName ,
144- file : file ,
145- line : line ,
142+ name : frame . FuncName ,
143+ file : frame . File ,
144+ line : frame . Line ,
146145 }
147146 pc := uintptr (len (positionCounters ))
148147 positionCounters = append (positionCounters , f )
149- knownPositions [key ] = pc
148+ knownPositions [frame ] = pc
150149 return pc
151150}
152151
153- // itoa converts an integer to a string.
154- //
155- // Can't use strconv.Itoa() in the `runtime` package due to a cyclic dependency.
156- func itoa (i int ) string {
157- return js .Global .Get ("String" ).New (i ).String ()
158- }
159-
160152// basicFrame contains stack trace information extracted from JS stack trace.
161153type basicFrame struct {
162154 FuncName string
@@ -166,9 +158,13 @@ type basicFrame struct {
166158}
167159
168160func callstack (skip , limit int ) []basicFrame {
169- skip = skip + 1 /*skip error message*/ + 1 /*skip callstack's own frame*/
170- lines := js .Global .Get ("Error" ).New ().Get ("stack" ).Call ("split" , "\n " ).Call ("slice" , skip , skip + limit )
171- return parseCallstack (lines )
161+ if limit <= 0 {
162+ return []basicFrame {}
163+ }
164+
165+ skip = skip + 2 // skip callstack's own frame and $callstack's frame
166+ lines := js .Global .Call ("$callstack" , skip , limit )
167+ return parseCallstack (lines , limit )
172168}
173169
174170var (
@@ -187,80 +183,33 @@ var (
187183 }
188184)
189185
190- func parseCallstack (lines * js.Object ) []basicFrame {
186+ func parseCallstack (lines * js.Object , limit int ) []basicFrame {
187+ // Parse all the frames skipping frames as needed.
191188 frames := []basicFrame {}
192189 l := lines .Length ()
193190 for i := 0 ; i < l ; i ++ {
194- frame := ParseCallFrame (lines .Index (i ))
195- if hiddenFrames [frame .FuncName ] {
191+ frame := js .Global .Call ("$parseCallFrame" , lines .Index (i ))
192+ funcName := frame .Index (0 ).String ()
193+ if hiddenFrames [funcName ] {
196194 continue
197195 }
198- if alias , ok := knownFrames [frame . FuncName ]; ok {
199- frame . FuncName = alias
196+ if alias , ok := knownFrames [funcName ]; ok {
197+ funcName = alias
200198 }
201- frames = append (frames , frame )
202- if frame .FuncName == "runtime.goexit" {
199+ frames = append (frames , basicFrame {
200+ FuncName : funcName ,
201+ File : frame .Index (1 ).String (),
202+ Line : frame .Index (2 ).Int (),
203+ Col : frame .Index (3 ).Int (),
204+ })
205+ if funcName == "runtime.goexit" {
203206 break // We've reached the bottom of the goroutine stack.
204207 }
205- }
206- return frames
207- }
208-
209- // ParseCallFrame is exported for the sake of testing. See this discussion for context https://github.com/gopherjs/gopherjs/pull/1097/files/561e6381406f04ccb8e04ef4effedc5c7887b70f#r776063799
210- //
211- // TLDR; never use this function!
212- func ParseCallFrame (info * js.Object ) basicFrame {
213- // FireFox
214- if info .Call ("indexOf" , "@" ).Int () >= 0 {
215- split := js .Global .Get ("RegExp" ).New ("[@:]" )
216- parts := info .Call ("split" , split )
217- return basicFrame {
218- File : parts .Call ("slice" , 1 , parts .Length ()- 2 ).Call ("join" , ":" ).String (),
219- Line : parts .Index (parts .Length () - 2 ).Int (),
220- Col : parts .Index (parts .Length () - 1 ).Int (),
221- FuncName : parts .Index (0 ).String (),
222- }
223- }
224-
225- // Chrome / Node.js
226- openIdx := info .Call ("lastIndexOf" , "(" ).Int ()
227- if openIdx == - 1 {
228- parts := info .Call ("split" , ":" )
229-
230- return basicFrame {
231- File : parts .Call ("slice" , 0 , parts .Length ()- 2 ).Call ("join" , ":" ).
232- Call ("replace" , js .Global .Get ("RegExp" ).New (`^\s*at ` ), "" ).String (),
233- Line : parts .Index (parts .Length () - 2 ).Int (),
234- Col : parts .Index (parts .Length () - 1 ).Int (),
235- FuncName : "<none>" ,
208+ if len (frames ) >= limit {
209+ break // We've reached the requested limit.
236210 }
237211 }
238-
239- var file , funcName string
240- var line , col int
241-
242- pos := info .Call ("substring" , openIdx + 1 , info .Call ("indexOf" , ")" ).Int ())
243- parts := pos .Call ("split" , ":" )
244-
245- if pos .String () == "<anonymous>" {
246- file = "<anonymous>"
247- } else {
248- file = parts .Call ("slice" , 0 , parts .Length ()- 2 ).Call ("join" , ":" ).String ()
249- line = parts .Index (parts .Length () - 2 ).Int ()
250- col = parts .Index (parts .Length () - 1 ).Int ()
251- }
252- fn := info .Call ("substring" , info .Call ("indexOf" , "at " ).Int ()+ 3 , info .Call ("indexOf" , " (" ).Int ())
253- if idx := fn .Call ("indexOf" , "[as " ).Int (); idx > 0 {
254- fn = fn .Call ("substring" , idx + 4 , fn .Call ("indexOf" , "]" ))
255- }
256- funcName = fn .String ()
257-
258- return basicFrame {
259- File : file ,
260- Line : line ,
261- Col : col ,
262- FuncName : funcName ,
263- }
212+ return frames
264213}
265214
266215func Caller (skip int ) (pc uintptr , file string , line int , ok bool ) {
@@ -269,8 +218,9 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
269218 if len (frames ) != 1 {
270219 return 0 , "" , 0 , false
271220 }
272- pc = registerPosition (frames [0 ].FuncName , frames [0 ].File , frames [0 ].Line , frames [0 ].Col )
273- return pc , frames [0 ].File , frames [0 ].Line , true
221+ frame := frames [0 ]
222+ pc = registerPosition (frame )
223+ return pc , frame .File , frame .Line , true
274224}
275225
276226// Callers fills the slice pc with the return program counters of function
@@ -293,7 +243,7 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
293243func Callers (skip int , pc []uintptr ) int {
294244 frames := callstack (skip , len (pc ))
295245 for i , frame := range frames {
296- pc [i ] = registerPosition (frame . FuncName , frame . File , frame . Line , frame . Col )
246+ pc [i ] = registerPosition (frame )
297247 }
298248 return len (frames )
299249}
@@ -308,7 +258,7 @@ func Callers(skip int, pc []uintptr) int {
308258// For those, FuncForPC returns nil and we emit a Frame with the original PC
309259// and empty symbol fields, like Go will.
310260// - GopherJS's internal frames such as $callDeferred and $goroutine were
311- // already filtered (or aliased) by parseCallstack at capture time, so anything
261+ // already filtered (or aliased) by callstack at capture time, so anything
312262// reaching CallersFrames is either a real Go frame or a PC we can't resolve.
313263func CallersFrames (callers []uintptr ) * Frames {
314264 result := Frames {}
@@ -472,7 +422,7 @@ func FuncForPC(pc uintptr) *Func {
472422 // created without a real caller (record.go's PC field)
473423 // - test/fixedbugs/issue29735.go deliberately walks past the
474424 // end of a function looking for the next. This will cause it to
475- // not secceed at what it is trying to do but will allow the test to pass.
425+ // not succeed at what it is trying to do but will allow the test to pass.
476426 // - test/fixedbugs/issue58300.go and test/fixedbugs/issue58300b.go give
477427 // FuncForPC a function pointer from `reflect.ValueOf(fn).Pointer()`,
478428 // which is not produced by Caller/Callers and so isn't in our table.
0 commit comments