Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

service/dap: Add error handlers for unsupported and not-yet-supported requests #1918

Merged
merged 8 commits into from
May 1, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions service/dap/daptest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,91 @@ func (c *Client) ExpectConfigurationDoneResponse(t *testing.T) *dap.Configuratio
return c.expectReadProtocolMessage(t).(*dap.ConfigurationDoneResponse)
}

func (c *Client) ExpectTerminateResponse(t *testing.T) *dap.TerminateResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.TerminateResponse)
}

func (c *Client) ExpectRestartResponse(t *testing.T) *dap.RestartResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.RestartResponse)
}

func (c *Client) ExpectSetFunctionBreakpointsResponse(t *testing.T) *dap.SetFunctionBreakpointsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.SetFunctionBreakpointsResponse)
}

func (c *Client) ExpectStepBackResponse(t *testing.T) *dap.StepBackResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.StepBackResponse)
}

func (c *Client) ExpectRestartFrameResponse(t *testing.T) *dap.RestartFrameResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.RestartFrameResponse)
}

func (c *Client) ExpectSetExpressionResponse(t *testing.T) *dap.SetExpressionResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.SetExpressionResponse)
}

func (c *Client) ExpectTerminateThreadsResponse(t *testing.T) *dap.TerminateThreadsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.TerminateThreadsResponse)
}

func (c *Client) ExpectStepInTargetsResponse(t *testing.T) *dap.StepInTargetsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.StepInTargetsResponse)
}

func (c *Client) ExpectGotoTargetsResponse(t *testing.T) *dap.GotoTargetsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.GotoTargetsResponse)
}

func (c *Client) ExpectCompletionsResponse(t *testing.T) *dap.CompletionsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.CompletionsResponse)
}

func (c *Client) ExpectExceptionInfoResponse(t *testing.T) *dap.ExceptionInfoResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.ExceptionInfoResponse)
}

func (c *Client) ExpectLoadedSourcesResponse(t *testing.T) *dap.LoadedSourcesResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.LoadedSourcesResponse)
}

func (c *Client) ExpectSetDataBreakpointsResponse(t *testing.T) *dap.SetDataBreakpointsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.SetDataBreakpointsResponse)
}

func (c *Client) ExpectReadMemoryResponse(t *testing.T) *dap.ReadMemoryResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.ReadMemoryResponse)
}

func (c *Client) ExpectDisassembleResponse(t *testing.T) *dap.DisassembleResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.DisassembleResponse)
}

func (c *Client) ExpectCancelResponse(t *testing.T) *dap.CancelResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.CancelResponse)
}

func (c *Client) ExpectBreakpointLocationsResponse(t *testing.T) *dap.BreakpointLocationsResponse {
t.Helper()
return c.expectReadProtocolMessage(t).(*dap.BreakpointLocationsResponse)
}

// InitializeRequest sends an 'initialize' request.
func (c *Client) InitializeRequest() {
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
Expand Down Expand Up @@ -199,6 +284,91 @@ func (c *Client) ContinueRequest(thread int) {
c.send(request)
}

// TeriminateRequest sends a 'terminate' request.
func (c *Client) TerminateRequest() {
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
}

// RestartRequest sends a 'restart' request.
func (c *Client) RestartRequest() {
c.send(&dap.RestartRequest{Request: *c.newRequest("restart")})
}

// SetFunctionBreakpointsRequest sends a 'setFunctionBreakpoints' request.
func (c *Client) SetFunctionBreakpointsRequest() {
c.send(&dap.SetFunctionBreakpointsRequest{Request: *c.newRequest("setFunctionBreakpoints")})
}

// StepBackRequest sends a 'stepBack' request.
func (c *Client) StepBackRequest() {
c.send(&dap.StepBackRequest{Request: *c.newRequest("stepBack")})
}

// RestartFrameRequest sends a 'restartFrame' request.
func (c *Client) RestartFrameRequest() {
c.send(&dap.RestartFrameRequest{Request: *c.newRequest("restartFrame")})
}

// SetExpressionRequest sends a 'setExpression' request.
func (c *Client) SetExpressionRequest() {
c.send(&dap.SetExpressionRequest{Request: *c.newRequest("setExpression")})
}

// TerminateThreadsRequest sends a 'terminateThreads' request.
func (c *Client) TerminateThreadsRequest() {
c.send(&dap.TerminateThreadsRequest{Request: *c.newRequest("terminateThreads")})
}

// StepInTargetsRequest sends a 'stepInTargets' request.
func (c *Client) StepInTargetsRequest() {
c.send(&dap.StepInTargetsRequest{Request: *c.newRequest("stepInTargets")})
}

// GotoTargetsRequest sends a 'gotoTargets' request.
func (c *Client) GotoTargetsRequest() {
c.send(&dap.GotoTargetsRequest{Request: *c.newRequest("gotoTargets")})
}

// CompletionsRequest sends a 'completions' request.
func (c *Client) CompletionsRequest() {
c.send(&dap.CompletionsRequest{Request: *c.newRequest("completions")})
}

// ExceptionInfoRequest sends a 'exceptionInfo' request.
func (c *Client) ExceptionInfoRequest() {
c.send(&dap.ExceptionInfoRequest{Request: *c.newRequest("exceptionInfo")})
}

// LoadedSourcesRequest sends a 'loadedSources' request.
func (c *Client) LoadedSourcesRequest() {
c.send(&dap.LoadedSourcesRequest{Request: *c.newRequest("loadedSources")})
}

// SetDataBreakpointsRequest sends a 'setDataBreakpoints' request.
func (c *Client) SetDataBreakpointsRequest() {
c.send(&dap.SetDataBreakpointsRequest{Request: *c.newRequest("setDataBreakpoints")})
}

// ReadMemoryRequest sends a 'readMemory' request.
func (c *Client) ReadMemoryRequest() {
c.send(&dap.ReadMemoryRequest{Request: *c.newRequest("readMemory")})
}

// DisassembleRequest sends a 'disassemble' request.
func (c *Client) DisassembleRequest() {
c.send(&dap.DisassembleRequest{Request: *c.newRequest("disassemble")})
}

// CancelRequest sends a 'cancel' request.
func (c *Client) CancelRequest() {
c.send(&dap.CancelRequest{Request: *c.newRequest("cancel")})
}

// BreakpointLocationsRequest sends a 'breakpointLocations' request.
func (c *Client) BreakpointLocationsRequest() {
c.send(&dap.BreakpointLocationsRequest{Request: *c.newRequest("breakpointLocations")})
}

// UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnknownRequest() {
request := c.newRequest("unknown")
Expand Down
160 changes: 143 additions & 17 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,13 @@ func (s *Server) handleRequest(request dap.Message) {
case *dap.DisconnectRequest:
s.onDisconnectRequest(request)
case *dap.TerminateRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onTerminateRequest(request)
case *dap.RestartRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onRestartRequest(request)
case *dap.SetBreakpointsRequest:
s.onSetBreakpointsRequest(request)
case *dap.SetFunctionBreakpointsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onSetFunctionBreakpointsRequest(request)
case *dap.SetExceptionBreakpointsRequest:
s.onSetExceptionBreakpointsRequest(request)
case *dap.ConfigurationDoneRequest:
Expand All @@ -220,11 +220,11 @@ func (s *Server) handleRequest(request dap.Message) {
case *dap.StepOutRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepBackRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onStepBackRequest(request)
case *dap.ReverseContinueRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.RestartFrameRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onRestartFrameRequest(request)
case *dap.GotoRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.PauseRequest:
Expand All @@ -238,37 +238,37 @@ func (s *Server) handleRequest(request dap.Message) {
case *dap.SetVariableRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetExpressionRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onSetExpressionRequest(request)
case *dap.SourceRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ThreadsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.TerminateThreadsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onTerminateThreadsRequest(request)
case *dap.EvaluateRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepInTargetsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onStepInTargetsRequest(request)
case *dap.GotoTargetsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onGotoTargetsRequest(request)
case *dap.CompletionsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onCompletionsRequest(request)
case *dap.ExceptionInfoRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onExceptionInfoRequest(request)
case *dap.LoadedSourcesRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onLoadedSourcesRequest(request)
case *dap.DataBreakpointInfoRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetDataBreakpointsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onSetDataBreakpointsRequest(request)
case *dap.ReadMemoryRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onReadMemoryRequest(request)
case *dap.DisassembleRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onDisassembleRequest(request)
case *dap.CancelRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onCancelRequest(request)
case *dap.BreakpointLocationsRequest:
s.sendUnsupportedErrorResponse(request.Request)
s.onBreakpointLocationsRequest(request)
default:
// This is a DAP message that go-dap has a struct for, so
// decoding succeeded, but this function does not know how
Expand Down Expand Up @@ -451,6 +451,132 @@ func (s *Server) onContinueRequest(request *dap.ContinueRequest) {
s.doContinue()
}

//
// The rest of the handlers below are no-ops because the adaptor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the UX implications of this. Presumably controls to send this requests still exist in VSCode's UI, if the adapter does a no-op then the user can select them and receive no feedback? I presume an error response is percolated to the user, is it not? How is no feedback better than seeing an error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR only focuses on the requests that have corresponding capabilities flags set or unset/absent at the initialization (see 'supportsFoo' references in the comments for each handler).

From the spec, "the protocol is still at its first version because it was an explicit design goal to support new feature in a completely backward compatible way. Making this possible without version numbers requires that every new feature gets a corresponding flag that lets a development tool know whether a debug adapter supports the feature or not. The absence of the flag always means that the feature is not supported."

I think the idea is that based on these the editor will know when to disable the UI, avoid issuing unsupported requests, etc. That said I did find one example where this was not quite working as intended, but the vscode side was very responsive in fixing this when I flagged it (see microsoft/vscode#90001). But I figured it would be best to just maintain feature parity with the existing implementation and just do what they are doing - make these no-ops. For now, I left out the couple of requests that are also no-ops in vscode-go, but do not have corresponding capability flags to let the tool know explicitly not to rely on them. See also my questions at microsoft/debug-adapter-protocol#99

Also, when I asked my vscode contacts how to decide when a no-op vs an error response is more appropriate, they said "if it is an error that the user should see, then return an ErrorResponse, if it is not an error that the user should see, then don't return an ErrorResponse", which matches your assumption.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird that we care whether or not they return an error if they are disabled by a feature flag, but alright. I think however we should at least put a TODO comment on the ones we want to implement in the future, as opposed to ones that just don't make sense for us (like RestartFrame)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is a bit odd. I am not completely happy with this change myself. I can take the handlers out and trust the development tools not to issue these requests and if they do, just fallback on the default unsupported errors that I had before, maybe differentiating between truly unsupported and not yet supported ones.

Which ones do you think deserve a TODO for future implementation? Here is the full list.

TerminateRequest
RestartRequest <== ?
SetFunctionBreakpointsRequest
StepBackRequest
RestartFrameRequest
SetExpressionRequest <==?
TerminateThreadsRequest
StepInTargetsRequest
GotoTargetsRequest
CompletionsRequest
ExceptionInfoRequest
LoadedSourcesRequest
SetDataBreakpointsRequest
ReadMemoryRequest
DisassembleRequest
CancelRequest
BreakpointLocationsRequest
ModulesRequest

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on a quick look at the spec I think these can be implemented:

TerminateRequest
RestartRequest
SetFunctionBreakpointsRequest
StepBackRequest
SetExpressionRequest
LoadedSourcesRequest
ReadMemoryRequest
DisassembleRequest

and these can not:

RestartFrameRequest
TerminateThreadsRequest
StepInTargetsRequest
GotoTargetsRequest
CompletionsRequest
SetDataBreakpointsRequest
BreakpointLocationsRequest

I'm unsure about:

ExceptionInfoRequest
CancelRequest
ModulesRequest

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am updating the PR based on this discussion. Please stay tuned. In the meantime, you listed StepBack as a request to support. It comes in a pair with ReverseContinue. I see "rev" and "rewind", so this one can also be supported, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds like it could be implemented as well.

// does not support these messages. We choose no-op over an error response
// because that is the default behavior in vscode-debugadapter-node
// that many other adaptors, including vscode-go, inherit from.
//

// onTerminateRequest is no-op because this adaptor does not support
// the 'terminate' request (supportsTerminateRequest is false in
// 'initialize' response).
func (s *Server) onTerminateRequest(request *dap.TerminateRequest) {
s.send(&dap.TerminateResponse{Response: *newResponse(request.Request)})
}

// onRestartRequest is no-op because this adaptor does not support
// the 'restart' request (supportsRestartRequest is false in
// 'initialize' response).
func (s *Server) onRestartRequest(request *dap.RestartRequest) {
s.send(&dap.RestartResponse{Response: *newResponse(request.Request)})
}

// onSetFunctionBreakpointsRequest is no-op because this adaptor does not support
// the 'setFunctionBreakpoints' request (supportsSetFunctionBreakpoints is false in
// 'initialize' response).
func (s *Server) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) {
s.send(&dap.SetFunctionBreakpointsResponse{Response: *newResponse(request.Request)})
}

// onStepBackRequest is no-op because this adaptor does not support
// the 'stepBack' request (supportsStepBack is false in
// 'initialize' response).
func (s *Server) onStepBackRequest(request *dap.StepBackRequest) {
s.send(&dap.StepBackResponse{Response: *newResponse(request.Request)})
}

// onRestartFrameRequest is no-op because this adaptor does not support
// the 'restartFrame' request (supportsRestartFrame is false in
// 'initialize' response).
func (s *Server) onRestartFrameRequest(request *dap.RestartFrameRequest) {
s.send(&dap.RestartFrameResponse{Response: *newResponse(request.Request)})
}

// onSetExpression is no-op because this adaptor does not support
// the 'setExpression' request (supportsSetExpression is false in
// 'initialize' response).
func (s *Server) onSetExpressionRequest(request *dap.SetExpressionRequest) {
s.send(&dap.SetExpressionResponse{Response: *newResponse(request.Request)})
}

// onTerminateThreadsRequest is no-op because this adaptor does not support
// the 'terminateThreads' request (supportsTerminateThreadsRequest is false in
// 'initialize' response).
func (s *Server) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest) {
s.send(&dap.TerminateThreadsResponse{Response: *newResponse(request.Request)})
}

// onStepInTargetsRequest is no-op because this adaptor does not support
// the 'stepInTargets' request (supportsStepInTargetsRequest is false in
// 'initialize' response).
func (s *Server) onStepInTargetsRequest(request *dap.StepInTargetsRequest) {
s.send(&dap.StepInTargetsResponse{Response: *newResponse(request.Request)})
}

// onGotoTargetsRequest is no-op because this adaptor does not support
// the 'gotoTargets' request (supportsGotoTargetsRequest is false in
// 'initialize' response).
func (s *Server) onGotoTargetsRequest(request *dap.GotoTargetsRequest) {
s.send(&dap.GotoTargetsResponse{Response: *newResponse(request.Request)})
}

// onCompletionsRequest is no-op because this adaptor does not support
// the 'completions' request (supportsCompletionsRequest is false in
// 'initialize' response).
func (s *Server) onCompletionsRequest(request *dap.CompletionsRequest) {
s.send(&dap.CompletionsResponse{Response: *newResponse(request.Request)})
}

// onExceptionInfoRequest is no-op because this adaptor does not support
// the 'exceptionInfo' request (supportsExceptionInfoRequest is false in
// 'initialize' response).
func (s *Server) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
s.send(&dap.ExceptionInfoResponse{Response: *newResponse(request.Request)})
}

// onLoadedSourcesRequest is no-op because this adaptor does not support
// the 'loadedSources' request (supportsLoadedSourcesRequest is false in
// 'initialize' response).
func (s *Server) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) {
s.send(&dap.LoadedSourcesResponse{Response: *newResponse(request.Request)})
}

// onSetDataBreakpointsRequest is no-op because this adaptor does not support
// the 'setDataBreakpoints' request (supportsDataBreakpoints is false in
// 'initialize' response).
func (s *Server) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest) {
s.send(&dap.SetDataBreakpointsResponse{Response: *newResponse(request.Request)})
}

// onReadMemoryRequest is no-op because this adaptor does not support
// the 'readMemory' request (supportsReadMemoryRequest is false in
// 'initialize' response).
func (s *Server) onReadMemoryRequest(request *dap.ReadMemoryRequest) {
s.send(&dap.ReadMemoryResponse{Response: *newResponse(request.Request)})
}

// onDisassembleRequest is no-op because this adaptor does not support
// the 'disassemble' request (supportsDisassembleRequest is false in
// 'initialize' response).
func (s *Server) onDisassembleRequest(request *dap.DisassembleRequest) {
s.send(&dap.DisassembleResponse{Response: *newResponse(request.Request)})
}

// onCancelRequest is no-op because this adaptor does not support
// the 'cancel' request (supportsCancelRequest is false in
// 'initialize' response).
func (s *Server) onCancelRequest(request *dap.CancelRequest) {
s.send(&dap.CancelResponse{Response: *newResponse(request.Request)})
}

// onBreakpointLocationsRequest is no-op because this adaptor does not support
// the 'breakpointLocations' request (supportsBreakpointLocationsRequest is false in
// 'initialize' response).
func (s *Server) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest) {
s.send(&dap.BreakpointLocationsResponse{Response: *newResponse(request.Request)})
}

func (s *Server) sendErrorResponse(request dap.Request, id int, summary string, details string) {
er := &dap.ErrorResponse{}
er.Type = "response"
Expand Down
Loading