service/dap: add throw reason to exception info (#2524)
We can get the throw reason by looking at the argument "s" in runtime.throw. This is not currently working in Go 1.16 or Go 1.17 (see golang/go#46425), but does work in Go 1.15 and Go 1.14
This commit is contained in:
parent
7411290468
commit
1ebfc5c37b
6
_fixtures/fatalerror.go
Normal file
6
_fixtures/fatalerror.go
Normal file
@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
var f func()
|
||||
go f()
|
||||
}
|
@ -29,6 +29,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-delve/delve/pkg/gobuild"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/locspec"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
@ -2509,14 +2510,36 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
||||
if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) {
|
||||
switch bpState.Breakpoint.Name {
|
||||
case proc.FatalThrow:
|
||||
// TODO(suzmue): add the fatal throw reason to body.Description.
|
||||
body.ExceptionId = "fatal error"
|
||||
// Attempt to get the value of the throw reason.
|
||||
// This is not currently working for Go 1.16 or 1.17: https://github.com/golang/go/issues/46425.
|
||||
handleError := func(err error) {
|
||||
if err != nil {
|
||||
body.Description = fmt.Sprintf("Error getting throw reason: %s", err.Error())
|
||||
}
|
||||
if goversion.ProducerAfterOrEqual(s.debugger.TargetGoVersion(), 1, 16) {
|
||||
body.Description = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
|
||||
}
|
||||
}
|
||||
|
||||
exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 1, 0, "s", DefaultLoadConfig)
|
||||
if err == nil {
|
||||
if exprVar.Value != nil {
|
||||
body.Description = exprVar.Value.String()
|
||||
} else {
|
||||
handleError(exprVar.Unreadable)
|
||||
}
|
||||
} else {
|
||||
handleError(err)
|
||||
}
|
||||
case proc.UnrecoveredPanic:
|
||||
body.ExceptionId = "panic"
|
||||
// Attempt to get the value of the panic message.
|
||||
exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 0, 0, "(*msgs).arg.(data)", DefaultLoadConfig)
|
||||
if err == nil {
|
||||
body.Description = exprVar.Value.String()
|
||||
} else {
|
||||
body.Description = fmt.Sprintf("Error getting panic message: %s", err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -2545,7 +2568,7 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
||||
}
|
||||
|
||||
frames, err := s.debugger.Stacktrace(goroutineID, s.args.stackTraceDepth, 0)
|
||||
if err == nil && len(frames) > 0 {
|
||||
if err == nil {
|
||||
apiFrames, err := s.debugger.ConvertStacktrace(frames, nil)
|
||||
if err == nil {
|
||||
var buf bytes.Buffer
|
||||
@ -2562,6 +2585,8 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
||||
})
|
||||
body.Details.StackTrace = buf.String()
|
||||
}
|
||||
} else {
|
||||
body.Details.StackTrace = fmt.Sprintf("Error getting stack trace: %s", err.Error())
|
||||
}
|
||||
response := &dap.ExceptionInfoResponse{
|
||||
Response: *newResponse(request.Request),
|
||||
|
@ -3490,7 +3490,6 @@ func TestBadAccess(t *testing.T) {
|
||||
if eInfo.Body.ExceptionId != "runtime error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
|
||||
t.Errorf("\ngot %#v\nwant ExceptionId=\"runtime error\" Text=\"%s\"", eInfo, errorPrefix)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
client.ContinueRequest(1)
|
||||
@ -3602,6 +3601,41 @@ func TestPanicBreakpointOnNext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFatalThrowBreakpoint(t *testing.T) {
|
||||
runTest(t, "fatalerror", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||
},
|
||||
// Set breakpoints
|
||||
fixture.Source, []int{3},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
checkStop(t, client, 1, "main.main", 3)
|
||||
|
||||
client.ContinueRequest(1)
|
||||
client.ExpectContinueResponse(t)
|
||||
|
||||
se := client.ExpectStoppedEvent(t)
|
||||
if se.Body.ThreadId != 1 || se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
|
||||
t.Errorf("\ngot %#v\nwant ThreadId=1 Reason=\"exception\" Description=\"fatal error\"", se)
|
||||
}
|
||||
|
||||
// TODO(suzmue): Enable this test for 1.17 when https://github.com/golang/go/issues/46425 is fixed.
|
||||
errorPrefix := "\"go of nil func value\""
|
||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
|
||||
errorPrefix = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
|
||||
}
|
||||
client.ExceptionInfoRequest(1)
|
||||
eInfo := client.ExpectExceptionInfoResponse(t)
|
||||
if eInfo.Body.ExceptionId != "fatal error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
|
||||
t.Errorf("\ngot %#v\nwant ExceptionId=\"runtime error\" Text=%s", eInfo, errorPrefix)
|
||||
}
|
||||
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
})
|
||||
runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
@ -3621,6 +3655,10 @@ func TestFatalThrowBreakpoint(t *testing.T) {
|
||||
if se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
|
||||
t.Errorf("\ngot %#v\nwant Reason=\"exception\" Description=\"fatal error\"", se)
|
||||
}
|
||||
|
||||
// TODO(suzmue): Get the exception info for the thread and check the description
|
||||
// includes "all goroutines are asleep - deadlock!".
|
||||
// Stopped events with no selected goroutines need to be supported to test deadlock.
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
|
@ -237,6 +237,12 @@ func (d *Debugger) checkGoVersion() error {
|
||||
return goversion.Compatible(producer)
|
||||
}
|
||||
|
||||
func (d *Debugger) TargetGoVersion() string {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
return d.target.BinInfo().Producer()
|
||||
}
|
||||
|
||||
// Launch will start a process with the given args and working directory.
|
||||
func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
|
||||
if err := verifyBinaryFormat(processArgs[0]); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user