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"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/gobuild"
|
"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/locspec"
|
||||||
"github.com/go-delve/delve/pkg/logflags"
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
"github.com/go-delve/delve/pkg/proc"
|
"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) {
|
if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) {
|
||||||
switch bpState.Breakpoint.Name {
|
switch bpState.Breakpoint.Name {
|
||||||
case proc.FatalThrow:
|
case proc.FatalThrow:
|
||||||
// TODO(suzmue): add the fatal throw reason to body.Description.
|
|
||||||
body.ExceptionId = "fatal error"
|
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:
|
case proc.UnrecoveredPanic:
|
||||||
body.ExceptionId = "panic"
|
body.ExceptionId = "panic"
|
||||||
// Attempt to get the value of the panic message.
|
// Attempt to get the value of the panic message.
|
||||||
exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 0, 0, "(*msgs).arg.(data)", DefaultLoadConfig)
|
exprVar, err := s.debugger.EvalVariableInScope(goroutineID, 0, 0, "(*msgs).arg.(data)", DefaultLoadConfig)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
body.Description = exprVar.Value.String()
|
body.Description = exprVar.Value.String()
|
||||||
|
} else {
|
||||||
|
body.Description = fmt.Sprintf("Error getting panic message: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2545,7 +2568,7 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frames, err := s.debugger.Stacktrace(goroutineID, s.args.stackTraceDepth, 0)
|
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)
|
apiFrames, err := s.debugger.ConvertStacktrace(frames, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@ -2562,6 +2585,8 @@ func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
|||||||
})
|
})
|
||||||
body.Details.StackTrace = buf.String()
|
body.Details.StackTrace = buf.String()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
body.Details.StackTrace = fmt.Sprintf("Error getting stack trace: %s", err.Error())
|
||||||
}
|
}
|
||||||
response := &dap.ExceptionInfoResponse{
|
response := &dap.ExceptionInfoResponse{
|
||||||
Response: *newResponse(request.Request),
|
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) {
|
if eInfo.Body.ExceptionId != "runtime error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
|
||||||
t.Errorf("\ngot %#v\nwant ExceptionId=\"runtime error\" Text=\"%s\"", eInfo, errorPrefix)
|
t.Errorf("\ngot %#v\nwant ExceptionId=\"runtime error\" Text=\"%s\"", eInfo, errorPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.ContinueRequest(1)
|
client.ContinueRequest(1)
|
||||||
@ -3602,6 +3601,41 @@ func TestPanicBreakpointOnNext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFatalThrowBreakpoint(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) {
|
runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
|
||||||
runDebugSessionWithBPs(t, client, "launch",
|
runDebugSessionWithBPs(t, client, "launch",
|
||||||
// Launch
|
// Launch
|
||||||
@ -3621,6 +3655,10 @@ func TestFatalThrowBreakpoint(t *testing.T) {
|
|||||||
if se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
|
if se.Body.Reason != "exception" || se.Body.Description != "fatal error" {
|
||||||
t.Errorf("\ngot %#v\nwant Reason=\"exception\" Description=\"fatal error\"", se)
|
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,
|
disconnect: true,
|
||||||
}})
|
}})
|
||||||
|
@ -237,6 +237,12 @@ func (d *Debugger) checkGoVersion() error {
|
|||||||
return goversion.Compatible(producer)
|
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.
|
// Launch will start a process with the given args and working directory.
|
||||||
func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
|
func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
|
||||||
if err := verifyBinaryFormat(processArgs[0]); err != nil {
|
if err := verifyBinaryFormat(processArgs[0]); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user