From aa377789b0cea0c6857c0cbf97f541585a5b1426 Mon Sep 17 00:00:00 2001 From: Suzy Mueller Date: Thu, 10 Jun 2021 13:59:24 -0400 Subject: [PATCH] service/dap: deemphasize internal runtime stack frames (#2522) Apply a presentation hint to the internal runtime stack frames, so that these can be deemphasized in the UI. This should allow users to more easily inspect their own code, and will keep the option to view those frames if they choose to. --- pkg/terminal/command.go | 7 +++++-- service/dap/server.go | 33 ++++++++++++++++++++++++++++++++- service/dap/server_test.go | 15 ++++++++++++++- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index fa0e4f11..8b3d6cda 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -2334,10 +2334,10 @@ func digits(n int) int { const stacktraceTruncatedMessage = "(truncated)" func printStack(t *Term, out io.Writer, stack []api.Stackframe, ind string, offsets bool) { - PrintStack(t.formatPath, out, stack, ind, offsets) + PrintStack(t.formatPath, out, stack, ind, offsets, func(api.Stackframe) bool { return true }) } -func PrintStack(formatPath func(string) string, out io.Writer, stack []api.Stackframe, ind string, offsets bool) { +func PrintStack(formatPath func(string) string, out io.Writer, stack []api.Stackframe, ind string, offsets bool, include func(api.Stackframe) bool) { if len(stack) == 0 { return } @@ -2355,6 +2355,9 @@ func PrintStack(formatPath func(string) string, out io.Writer, stack []api.Stack s := ind + strings.Repeat(" ", d+2+len(ind)) for i := range stack { + if !include(stack[i]) { + continue + } if stack[i].Err != "" { fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err) continue diff --git a/service/dap/server.go b/service/dap/server.go index 5b8cbc71..7da8b033 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -1320,6 +1320,14 @@ func fnName(loc *proc.Location) string { return loc.Fn.Name } +func fnPackageName(loc *proc.Location) string { + if loc.Fn == nil { + // attribute unknown functions to the runtime + return "runtime" + } + return loc.Fn.PackageName() +} + // onThreadsRequest handles 'threads' request. // This is a mandatory request to support. // It is sent in response to configurationDone response and stopped events. @@ -1528,6 +1536,15 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) { return } + // Determine if the goroutine is a system goroutine. + // TODO(suzmue): Use the System() method defined in: https://github.com/go-delve/delve/pull/2504 + g, err := s.debugger.FindGoroutine(goroutineID) + var isSystemGoroutine bool + if err == nil { + userLoc := g.UserCurrent() + isSystemGoroutine = fnPackageName(&userLoc) == "runtime" + } + stackFrames := make([]dap.StackFrame, len(frames)) for i, frame := range frames { loc := &frame.Call @@ -1538,6 +1555,11 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) { stackFrames[i].Source = dap.Source{Name: filepath.Base(clientPath), Path: clientPath} } stackFrames[i].Column = 0 + + packageName := fnPackageName(loc) + if !isSystemGoroutine && packageName == "runtime" { + stackFrames[i].Source.PresentationHint = "deemphasize" + } } // Since the backend doesn't support paging, we load all frames up to // pre-configured depth every time and then slice them here per @@ -2446,7 +2468,16 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) { if err == nil { var buf bytes.Buffer fmt.Fprintln(&buf, "Stack:") - terminal.PrintStack(s.toClientPath, &buf, apiFrames, "\t", false) + userLoc := g.UserCurrent() + userFuncPkg := fnPackageName(&userLoc) + terminal.PrintStack(s.toClientPath, &buf, apiFrames, "\t", false, func(s api.Stackframe) bool { + // Include all stack frames if the stack trace is for a system goroutine, + // otherwise, skip runtime stack frames. + if userFuncPkg == "runtime" { + return true + } + return s.Location.Function != nil && !strings.HasPrefix(s.Location.Function.Name(), "runtime.") + }) body.Details.StackTrace = buf.String() } } diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 6a82d11e..a027ba35 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -516,7 +516,7 @@ func TestPreSetBreakpoint(t *testing.T) { wantMain := dap.Thread{Id: 1, Name: "* [Go 1] main.Increment (Thread ...)"} wantRuntime := dap.Thread{Id: 2, Name: "[Go 2] runtime.gopark"} for _, got := range tResp.Body.Threads { - if got.Id != 1 && !reMain.MatchString(got.Name) && !strings.Contains(got.Name, "runtime") { + if got.Id != 1 && !reMain.MatchString(got.Name) && !strings.Contains(got.Name, "runtime.") { t.Errorf("\ngot %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime) } } @@ -3373,6 +3373,19 @@ func TestPanicBreakpointOnContinue(t *testing.T) { if eInfo.Body.ExceptionId != "panic" || eInfo.Body.Description != "\"BOOM!\"" { t.Errorf("\ngot %#v\nwant ExceptionId=\"panic\" Description=\"\"BOOM!\"\"", eInfo) } + + client.StackTraceRequest(se.Body.ThreadId, 0, 20) + st := client.ExpectStackTraceResponse(t) + for i, frame := range st.Body.StackFrames { + if strings.HasPrefix(frame.Name, "runtime.") { + if frame.Source.PresentationHint != "deemphasize" { + t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"deemphasize\"", i, frame) + } + } else if frame.Source.PresentationHint != "" { + t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"\"", i, frame) + } + + } }, disconnect: true, }})