service/dap: add "panic" and "fatal error" as stopped reasons (#2186)
* service/dap: add "panic" and "fatal error" as stopped reasons The unrecovered panic and fatal throw breakpoints are not set by the user. We now check for these special breakpoints and send appropriate stopped reasons to the client. * Add getter for StopReason * Set threadID and stop reason correctly If there is no selected goroutine, no goroutine ID should be set in the stopped event. The stopped reason can be better determined using the process StopReason. * Update panic breakpoint on next test to work with Go 1.13 runtime When running panic.go with Go1.13, the next line that is stepped to after panic('boom') is the defer function in the runtime package. The unrecovered panic breakpoint is not hit until after several steps. The test now steps until the breakpoint is hit, or the program terminates without hitting the unrecovered panic breakpoint, in which case it fails. * Skip breakpoint on next test in < Go 1.14
This commit is contained in:
parent
80d0c8e717
commit
5632cf92be
@ -1195,13 +1195,24 @@ func (s *Server) doCommand(command string) {
|
||||
stopped.Body.AllThreadsStopped = true
|
||||
|
||||
if err == nil {
|
||||
stopped.Body.ThreadId = state.SelectedGoroutine.ID
|
||||
switch command {
|
||||
case api.Next, api.Step, api.StepOut:
|
||||
if state.SelectedGoroutine != nil {
|
||||
stopped.Body.ThreadId = state.SelectedGoroutine.ID
|
||||
}
|
||||
|
||||
switch s.debugger.StopReason() {
|
||||
case proc.StopNextFinished:
|
||||
stopped.Body.Reason = "step"
|
||||
default:
|
||||
stopped.Body.Reason = "breakpoint"
|
||||
}
|
||||
if state.CurrentThread.Breakpoint != nil {
|
||||
switch state.CurrentThread.Breakpoint.Name {
|
||||
case proc.FatalThrow:
|
||||
stopped.Body.Reason = "fatal error"
|
||||
case proc.UnrecoveredPanic:
|
||||
stopped.Body.Reason = "panic"
|
||||
}
|
||||
}
|
||||
s.send(stopped)
|
||||
} else {
|
||||
s.log.Error("runtime error: ", err)
|
||||
|
@ -1327,6 +1327,91 @@ func TestBadAccess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPanicBreakpointOnContinue(t *testing.T) {
|
||||
runTest(t, "panic", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client,
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||
},
|
||||
// Set breakpoints
|
||||
fixture.Source, []int{5},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
handleStop(t, client, 1, 5)
|
||||
|
||||
client.ContinueRequest(1)
|
||||
client.ExpectContinueResponse(t)
|
||||
|
||||
se := client.ExpectStoppedEvent(t)
|
||||
if se.Body.ThreadId != 1 || se.Body.Reason != "panic" {
|
||||
t.Errorf("\ngot %#v\nwant ThreadId=1 Reason=\"panic\"", se)
|
||||
}
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPanicBreakpointOnNext(t *testing.T) {
|
||||
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
|
||||
// In Go 1.13, 'next' will step into the defer in the runtime
|
||||
// main function, instead of the next line in the main program.
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
runTest(t, "panic", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client,
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||
},
|
||||
// Set breakpoints
|
||||
fixture.Source, []int{5},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
handleStop(t, client, 1, 5)
|
||||
|
||||
client.NextRequest(1)
|
||||
client.ExpectNextResponse(t)
|
||||
|
||||
se := client.ExpectStoppedEvent(t)
|
||||
|
||||
if se.Body.ThreadId != 1 || se.Body.Reason != "panic" {
|
||||
t.Errorf("\ngot %#v\nexpected ThreadId=1 Reason=\"panic\"", se)
|
||||
}
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
func TestFatalThrowBreakpoint(t *testing.T) {
|
||||
runTest(t, "testdeadlock", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client,
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||
},
|
||||
// Set breakpoints
|
||||
fixture.Source, []int{3},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
handleStop(t, client, 1, 3)
|
||||
|
||||
client.ContinueRequest(1)
|
||||
client.ExpectContinueResponse(t)
|
||||
|
||||
se := client.ExpectStoppedEvent(t)
|
||||
if se.Body.Reason != "fatal error" {
|
||||
t.Errorf("\ngot %#v\nwant Reason=\"fatal error\"", se)
|
||||
}
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
// handleStop covers the standard sequence of reqeusts issued by
|
||||
// a client at a breakpoint or another non-terminal stop event.
|
||||
// The details have been tested by other tests,
|
||||
|
@ -1602,6 +1602,15 @@ func (d *Debugger) StopRecording() error {
|
||||
return d.stopRecording()
|
||||
}
|
||||
|
||||
// StopReason returns the reason the reason why the target process is stopped.
|
||||
// A process could be stopped for multiple simultaneous reasons, in which
|
||||
// case only one will be reported.
|
||||
func (d *Debugger) StopReason() proc.StopReason {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
return d.target.StopReason
|
||||
}
|
||||
|
||||
// LockTarget acquires the target mutex.
|
||||
func (d *Debugger) LockTarget() {
|
||||
d.targetMutex.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user