@@ -10,7 +10,6 @@ package stacktrace
1010
1111import (
1212 "errors"
13- "regexp"
1413 "runtime"
1514 "slices"
1615 "strconv"
@@ -179,28 +178,70 @@ func (q *queue[T]) Remove() T {
179178 return item
180179}
181180
182- var symbolRegex = regexp .MustCompile (`^(([^(]+/)?([^(/.]+)?)(\.\(([^/)]+)\))?\.([^/()]+)$` )
183-
184- // parseSymbol parses a symbol name into its package, receiver and function
185- // ex: github.com/DataDog/dd-trace-go/v2/internal/stacktrace.(*Event).NewException
186- // -> package: github.com/DataDog/dd-trace-go/v2/internal/stacktrace
187- // -> receiver: *Event
188- // -> function: NewException
181+ // parseSymbol parses a symbol name into its package, receiver and function using
182+ // zero-allocation string operations. This is a hot path called once per stack frame.
183+ //
184+ // Handles various Go symbol formats:
185+ // - Simple function: pkg.Function
186+ // - Method with receiver: pkg.(*Type).Method or pkg.(Type).Method
187+ // - Lambda/closure: pkg.Function.func1 or pkg.(*Type).Method.func1
188+ // - Generics: pkg.(*Type[...]).Method or pkg.Function[...]
189+ //
190+ // Examples:
191+ // github.com/DataDog/dd-trace-go/v2/internal/stacktrace.(*Event).NewException
192+ // -> package: github.com/DataDog/dd-trace-go/v2/internal/stacktrace
193+ // -> receiver: *Event
194+ // -> function: NewException
195+ // github.com/DataDog/dd-trace-go/v2/internal/stacktrace.TestFunc.func1
196+ // -> package: github.com/DataDog/dd-trace-go/v2/internal/stacktrace
197+ // -> receiver: ""
198+ // -> function: TestFunc.func1
189199func parseSymbol (name string ) symbol {
190- matches := symbolRegex .FindStringSubmatch (name )
191- if len (matches ) != 7 {
192- log .Error ("Failed to parse symbol for stacktrace: %s" , name )
193- return symbol {
194- Package : "" ,
195- Receiver : "" ,
196- Function : "" ,
200+ // Check for receiver first: pkg.(*Type) or pkg.(Type)
201+ // Look for ".(" which marks the start of a receiver
202+ if idx := strings .Index (name , ".(" ); idx != - 1 {
203+ // Find the closing paren of the receiver
204+ receiverEnd := strings .IndexByte (name [idx + 2 :], ')' )
205+ if receiverEnd != - 1 {
206+ pkg := name [:idx ]
207+ receiver := name [idx + 2 : idx + 2 + receiverEnd ]
208+ // Everything after ")." is the function (which may contain dots for lambdas)
209+ fn := name [idx + 2 + receiverEnd + 2 :] // +2 for ")."
210+ return symbol {
211+ Package : pkg ,
212+ Receiver : receiver ,
213+ Function : fn ,
214+ }
197215 }
198216 }
199217
218+ // No receiver case: need to find where package ends and function begins
219+ // Package path ends at the last '/' followed by a segment before first '.'
220+ // Examples:
221+ // "pkg.Function" -> pkg: "pkg", fn: "Function"
222+ // "pkg.Function.func1" -> pkg: "pkg", fn: "Function.func1"
223+ // "github.com/org/pkg.Function" -> pkg: "github.com/org/pkg", fn: "Function"
224+
225+ // Find the last slash to identify where the package name starts
226+ lastSlash := strings .LastIndexByte (name , '/' )
227+
228+ // Find the first dot after the last slash (or from the beginning if no slash)
229+ searchStart := 0
230+ if lastSlash != - 1 {
231+ searchStart = lastSlash + 1
232+ }
233+
234+ firstDotAfterSlash := strings .IndexByte (name [searchStart :], '.' )
235+ if firstDotAfterSlash == - 1 {
236+ // No dots after last slash, the whole thing is the function name
237+ return symbol {Function : name }
238+ }
239+
240+ // Package ends at this dot, function starts after it
241+ pkgEnd := searchStart + firstDotAfterSlash
200242 return symbol {
201- Package : matches [1 ],
202- Receiver : matches [5 ],
203- Function : matches [6 ],
243+ Package : name [:pkgEnd ],
244+ Function : name [pkgEnd + 1 :], // Everything after the dot, including nested dots for lambdas
204245 }
205246}
206247
0 commit comments