diff --git a/internal/stack/stacks.go b/internal/stack/stacks.go index 241a9b8..1d31288 100644 --- a/internal/stack/stacks.go +++ b/internal/stack/stacks.go @@ -158,6 +158,13 @@ func (p *stackParser) parseStack(line string) (Stack, error) { // Just skip it. continue } + if strings.HasPrefix(line, "...") && strings.HasSuffix(line, " frames elided...") { + // e.g. ...23 frames elided... + // This indicates frames were elided from the stack trace, + // attempting to parse them via parseFuncName will fail resulting in a panic + // and a relatively useless output. Gracefully handle this. + continue + } funcName, creator, err := parseFuncName(line) if err != nil { diff --git a/internal/stack/stacks_test.go b/internal/stack/stacks_test.go index 156c2aa..b1a21d8 100644 --- a/internal/stack/stacks_test.go +++ b/internal/stack/stacks_test.go @@ -21,6 +21,7 @@ package stack import ( + "bytes" "os" "path/filepath" "runtime" @@ -136,8 +137,8 @@ func TestCurrentCreatedBy(t *testing.T) { func TestAllLargeStack(t *testing.T) { const ( - stackDepth = 100 - numGoroutines = 100 + stackDepth = 101 + numGoroutines = 101 ) var started sync.WaitGroup @@ -163,6 +164,15 @@ func TestAllLargeStack(t *testing.T) { t.Fatalf("Expected larger stack buffer") } + // Also test the stack parser here to ensure it handles elided frames gracefully. + // We want to check this here, so that if the format of the "elided frames" message changes, we catch it. + // At the time of writing this test, with a stack depth of 101, we get 2 elided frames: + // "...2 frames elided...". + assert.Contains(t, string(buf), "frames elided...") + stacks, err := newStackParser(bytes.NewReader(buf)).Parse() + require.NoError(t, err) + assert.Greater(t, len(stacks), numGoroutines, "expect more parsed stacks than goroutines") + // Start enough goroutines so we exceed the default buffer size. close(done) } @@ -263,6 +273,23 @@ func TestParseStack(t *testing.T) { "example.com/foo/bar.baz", }, }, + { + name: "elided frames", + give: joinLines( + "goroutine 1 [running]:", + "example.com/foo/bar.baz()", + " example.com/foo/bar.go:123", + "...3 frames elided...", + "created by example.com/foo/bar.qux", + " example.com/foo/bar.go:456", + ), + id: 1, + state: "running", + firstFunc: "example.com/foo/bar.baz", + funcs: []string{ + "example.com/foo/bar.baz", + }, + }, } for _, tt := range tests {