@@ -10,7 +10,6 @@ package stacktrace
1010
1111import (
1212 "errors"
13- "regexp"
1413 "runtime"
1514 "slices"
1615 "strconv"
@@ -179,28 +178,71 @@ 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+ //
192+ // github.com/DataDog/dd-trace-go/v2/internal/stacktrace.(*Event).NewException
193+ // -> package: github.com/DataDog/dd-trace-go/v2/internal/stacktrace
194+ // -> receiver: *Event
195+ // -> function: NewException
196+ // github.com/DataDog/dd-trace-go/v2/internal/stacktrace.TestFunc.func1
197+ // -> package: github.com/DataDog/dd-trace-go/v2/internal/stacktrace
198+ // -> receiver: ""
199+ // -> function: TestFunc.func1
189200func 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 : "" ,
201+ // Check for receiver first: pkg.(*Type) or pkg.(Type)
202+ // Look for ".(" which marks the start of a receiver
203+ if idx := strings .Index (name , ".(" ); idx != - 1 {
204+ // Find the closing paren of the receiver
205+ receiverEnd := strings .IndexByte (name [idx + 2 :], ')' )
206+ if receiverEnd != - 1 {
207+ pkg := name [:idx ]
208+ receiver := name [idx + 2 : idx + 2 + receiverEnd ]
209+ // Everything after ")." is the function (which may contain dots for lambdas)
210+ fn := name [idx + 2 + receiverEnd + 2 :] // +2 for ")."
211+ return symbol {
212+ Package : pkg ,
213+ Receiver : receiver ,
214+ Function : fn ,
215+ }
197216 }
198217 }
199218
219+ // No receiver case: need to find where package ends and function begins
220+ // Package path ends at the last '/' followed by a segment before first '.'
221+ // Examples:
222+ // "pkg.Function" -> pkg: "pkg", fn: "Function"
223+ // "pkg.Function.func1" -> pkg: "pkg", fn: "Function.func1"
224+ // "github.com/org/pkg.Function" -> pkg: "github.com/org/pkg", fn: "Function"
225+
226+ // Find the last slash to identify where the package name starts
227+ lastSlash := strings .LastIndexByte (name , '/' )
228+
229+ // Find the first dot after the last slash (or from the beginning if no slash)
230+ searchStart := 0
231+ if lastSlash != - 1 {
232+ searchStart = lastSlash + 1
233+ }
234+
235+ firstDotAfterSlash := strings .IndexByte (name [searchStart :], '.' )
236+ if firstDotAfterSlash == - 1 {
237+ // No dots after last slash, the whole thing is the function name
238+ return symbol {Function : name }
239+ }
240+
241+ // Package ends at this dot, function starts after it
242+ pkgEnd := searchStart + firstDotAfterSlash
200243 return symbol {
201- Package : matches [1 ],
202- Receiver : matches [5 ],
203- Function : matches [6 ],
244+ Package : name [:pkgEnd ],
245+ Function : name [pkgEnd + 1 :], // Everything after the dot, including nested dots for lambdas
204246 }
205247}
206248
0 commit comments