Files
LearnGO/go/pkg/mod/github.com/google/go-dap@v0.12.0/cmd/mockserver/server.go
T
2024-09-19 21:38:24 -04:00

595 lines
21 KiB
Go

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file defines helpers and request handlers for a dummy server
// that accepts DAP requests and responds with dummy or error responses.
// Fake-supported requests:
// - initialize
// - launch
// - setBreakpoints
// - setExceptionBreakpoints
// - configurationDone
// - threads
// - stackTrace
// - scopes
// - variables
// - continue
// - disconnect
// All other requests result in ErrorResponse's.
//
// The server uses the following goroutines:
// - "main" goroutine accepts client connections one by one and
// handles them serially by reading and decoding incoming requests
// and dispatching each one to a new goroutine for further
// processing.
// - per-request goroutines process each request as if
// letting fake debugger take over. They send events and responses
// via the sender goroutine.
// - sender goroutine listens for messages to send and
// writes them to the client connection.
//
package main
import (
"bufio"
"io"
"log"
"net"
"sync"
"time"
"github.com/google/go-dap"
)
// server starts a server that listens on a specified port
// and blocks indefinitely. This server can accept multiple
// client connections at the same time.
func server(port string) error {
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
return err
}
defer listener.Close()
log.Println("Started server at", listener.Addr())
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Connection failed:", err)
continue
}
log.Println("Accepted connection from", conn.RemoteAddr())
// Handle multiple client connections concurrently
go handleConnection(conn)
}
}
// handleConnection handles a connection from a single client.
// It reads and decodes the incoming data and dispatches it
// to per-request processing goroutines. It also launches the
// sender goroutine to send resulting messages over the connection
// back to the client.
func handleConnection(conn net.Conn) {
debugSession := fakeDebugSession{
rw: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
sendQueue: make(chan dap.Message),
stopDebug: make(chan struct{}),
}
go debugSession.sendFromQueue()
for {
err := debugSession.handleRequest()
// TODO(polina): check for connection vs decoding error?
if err != nil {
if err == io.EOF {
log.Println("No more data to read:", err)
break
}
// There maybe more messages to process, but
// we will start with the strict behavior of only accepting
// expected inputs.
log.Fatal("Server error: ", err)
}
}
log.Println("Closing connection from", conn.RemoteAddr())
close(debugSession.stopDebug)
debugSession.sendWg.Wait()
close(debugSession.sendQueue)
conn.Close()
}
func (ds *fakeDebugSession) handleRequest() error {
log.Println("Reading request...")
request, err := dap.ReadProtocolMessage(ds.rw.Reader)
if err != nil {
return err
}
log.Printf("Received request\n\t%#v\n", request)
ds.sendWg.Add(1)
go func() {
ds.dispatchRequest(request)
ds.sendWg.Done()
}()
return nil
}
// dispatchRequest launches a new goroutine to process each request
// and send back events and responses.
func (ds *fakeDebugSession) dispatchRequest(request dap.Message) {
switch request := request.(type) {
case *dap.InitializeRequest:
ds.onInitializeRequest(request)
case *dap.LaunchRequest:
ds.onLaunchRequest(request)
case *dap.AttachRequest:
ds.onAttachRequest(request)
case *dap.DisconnectRequest:
ds.onDisconnectRequest(request)
case *dap.TerminateRequest:
ds.onTerminateRequest(request)
case *dap.RestartRequest:
ds.onRestartRequest(request)
case *dap.SetBreakpointsRequest:
ds.onSetBreakpointsRequest(request)
case *dap.SetFunctionBreakpointsRequest:
ds.onSetFunctionBreakpointsRequest(request)
case *dap.SetExceptionBreakpointsRequest:
ds.onSetExceptionBreakpointsRequest(request)
case *dap.ConfigurationDoneRequest:
ds.onConfigurationDoneRequest(request)
case *dap.ContinueRequest:
ds.onContinueRequest(request)
case *dap.NextRequest:
ds.onNextRequest(request)
case *dap.StepInRequest:
ds.onStepInRequest(request)
case *dap.StepOutRequest:
ds.onStepOutRequest(request)
case *dap.StepBackRequest:
ds.onStepBackRequest(request)
case *dap.ReverseContinueRequest:
ds.onReverseContinueRequest(request)
case *dap.RestartFrameRequest:
ds.onRestartFrameRequest(request)
case *dap.GotoRequest:
ds.onGotoRequest(request)
case *dap.PauseRequest:
ds.onPauseRequest(request)
case *dap.StackTraceRequest:
ds.onStackTraceRequest(request)
case *dap.ScopesRequest:
ds.onScopesRequest(request)
case *dap.VariablesRequest:
ds.onVariablesRequest(request)
case *dap.SetVariableRequest:
ds.onSetVariableRequest(request)
case *dap.SetExpressionRequest:
ds.onSetExpressionRequest(request)
case *dap.SourceRequest:
ds.onSourceRequest(request)
case *dap.ThreadsRequest:
ds.onThreadsRequest(request)
case *dap.TerminateThreadsRequest:
ds.onTerminateThreadsRequest(request)
case *dap.EvaluateRequest:
ds.onEvaluateRequest(request)
case *dap.StepInTargetsRequest:
ds.onStepInTargetsRequest(request)
case *dap.GotoTargetsRequest:
ds.onGotoTargetsRequest(request)
case *dap.CompletionsRequest:
ds.onCompletionsRequest(request)
case *dap.ExceptionInfoRequest:
ds.onExceptionInfoRequest(request)
case *dap.LoadedSourcesRequest:
ds.onLoadedSourcesRequest(request)
case *dap.DataBreakpointInfoRequest:
ds.onDataBreakpointInfoRequest(request)
case *dap.SetDataBreakpointsRequest:
ds.onSetDataBreakpointsRequest(request)
case *dap.ReadMemoryRequest:
ds.onReadMemoryRequest(request)
case *dap.DisassembleRequest:
ds.onDisassembleRequest(request)
case *dap.CancelRequest:
ds.onCancelRequest(request)
case *dap.BreakpointLocationsRequest:
ds.onBreakpointLocationsRequest(request)
default:
log.Fatalf("Unable to process %#v", request)
}
}
// send lets the sender goroutine know via a channel that there is
// a message to be sent to client. This is called by per-request
// goroutines to send events and responses for each request and
// to notify of events triggered by the fake debugger.
func (ds *fakeDebugSession) send(message dap.Message) {
ds.sendQueue <- message
}
// sendFromQueue is to be run in a separate goroutine to listen on a
// channel for messages to send back to the client. It will
// return once the channel is closed.
func (ds *fakeDebugSession) sendFromQueue() {
for message := range ds.sendQueue {
dap.WriteProtocolMessage(ds.rw.Writer, message)
log.Printf("Message sent\n\t%#v\n", message)
ds.rw.Flush()
}
}
// -----------------------------------------------------------------------
// Very Fake Debugger
//
// The debugging session will keep track of how many breakpoints
// have been set. Once start-up is done (i.e. configurationDone
// request is processed), it will "stop" at each breakpoint one by
// one, and once there are no more, it will trigger a terminated event.
type fakeDebugSession struct {
// rw is used to read requests and write events/responses
rw *bufio.ReadWriter
// sendQueue is used to capture messages from multiple request
// processing goroutines while writing them to the client connection
// from a single goroutine via sendFromQueue. We must keep track of
// the multiple channel senders with a wait group to make sure we do
// not close this channel prematurely. Closing this channel will signal
// the sendFromQueue goroutine that it can exit.
sendQueue chan dap.Message
sendWg sync.WaitGroup
// stopDebug is used to notify long-running handlers to stop processing.
stopDebug chan struct{}
// bpSet is a counter of the remaining breakpoints that the debug
// session is yet to stop at before the program terminates.
bpSet int
bpSetMux sync.Mutex
}
// doContinue allows fake program execution to continue when the program
// is started or unpaused. It simulates events from the debug session
// by "stopping" on a breakpoint or terminating if there are no more
// breakpoints. Safe to use concurrently.
func (ds *fakeDebugSession) doContinue() {
var e dap.Message
ds.bpSetMux.Lock()
if ds.bpSet == 0 {
// Pretend that the program is running.
// The delay will allow for all in-flight responses
// to be sent before termination.
time.Sleep(1000 * time.Millisecond)
e = &dap.TerminatedEvent{
Event: *newEvent("terminated"),
}
} else {
e = &dap.StoppedEvent{
Event: *newEvent("stopped"),
Body: dap.StoppedEventBody{Reason: "breakpoint", ThreadId: 1, AllThreadsStopped: true},
}
ds.bpSet--
}
ds.bpSetMux.Unlock()
ds.send(e)
}
// -----------------------------------------------------------------------
// Request Handlers
//
// Below is a dummy implementation of the request handlers.
// They take no action, but just return dummy responses.
// A real debug adaptor would call the debugger methods here
// and use their results to populate each response.
func (ds *fakeDebugSession) onInitializeRequest(request *dap.InitializeRequest) {
response := &dap.InitializeResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body.SupportsConfigurationDoneRequest = true
response.Body.SupportsFunctionBreakpoints = false
response.Body.SupportsConditionalBreakpoints = false
response.Body.SupportsHitConditionalBreakpoints = false
response.Body.SupportsEvaluateForHovers = false
response.Body.ExceptionBreakpointFilters = []dap.ExceptionBreakpointsFilter{}
response.Body.SupportsStepBack = false
response.Body.SupportsSetVariable = false
response.Body.SupportsRestartFrame = false
response.Body.SupportsGotoTargetsRequest = false
response.Body.SupportsStepInTargetsRequest = false
response.Body.SupportsCompletionsRequest = false
response.Body.CompletionTriggerCharacters = []string{}
response.Body.SupportsModulesRequest = false
response.Body.AdditionalModuleColumns = []dap.ColumnDescriptor{}
response.Body.SupportedChecksumAlgorithms = []dap.ChecksumAlgorithm{}
response.Body.SupportsRestartRequest = false
response.Body.SupportsExceptionOptions = false
response.Body.SupportsValueFormattingOptions = false
response.Body.SupportsExceptionInfoRequest = false
response.Body.SupportTerminateDebuggee = false
response.Body.SupportsDelayedStackTraceLoading = false
response.Body.SupportsLoadedSourcesRequest = false
response.Body.SupportsLogPoints = false
response.Body.SupportsTerminateThreadsRequest = false
response.Body.SupportsSetExpression = false
response.Body.SupportsTerminateRequest = false
response.Body.SupportsDataBreakpoints = false
response.Body.SupportsReadMemoryRequest = false
response.Body.SupportsDisassembleRequest = false
response.Body.SupportsCancelRequest = false
response.Body.SupportsBreakpointLocationsRequest = false
// This is a fake set up, so we can start "accepting" configuration
// requests for setting breakpoints, etc from the client at any time.
// Notify the client with an 'initialized' event. The client will end
// the configuration sequence with 'configurationDone' request.
e := &dap.InitializedEvent{Event: *newEvent("initialized")}
ds.send(e)
ds.send(response)
}
func (ds *fakeDebugSession) onLaunchRequest(request *dap.LaunchRequest) {
// This is where a real debug adaptor would check the soundness of the
// arguments (e.g. program from launch.json) and then use them to launch the
// debugger and attach to the program.
response := &dap.LaunchResponse{}
response.Response = *newResponse(request.Seq, request.Command)
ds.send(response)
}
func (ds *fakeDebugSession) onAttachRequest(request *dap.AttachRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "AttachRequest is not yet supported"))
}
func (ds *fakeDebugSession) onDisconnectRequest(request *dap.DisconnectRequest) {
response := &dap.DisconnectResponse{}
response.Response = *newResponse(request.Seq, request.Command)
ds.send(response)
}
func (ds *fakeDebugSession) onTerminateRequest(request *dap.TerminateRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported"))
}
func (ds *fakeDebugSession) onRestartRequest(request *dap.RestartRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "RestartRequest is not yet supported"))
}
func (ds *fakeDebugSession) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
response := &dap.SetBreakpointsResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
for i, b := range request.Arguments.Breakpoints {
response.Body.Breakpoints[i].Line = b.Line
response.Body.Breakpoints[i].Verified = true
ds.bpSetMux.Lock()
ds.bpSet++
ds.bpSetMux.Unlock()
}
ds.send(response)
}
func (ds *fakeDebugSession) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "SetFunctionBreakpointsRequest is not yet supported"))
}
func (ds *fakeDebugSession) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) {
response := &dap.SetExceptionBreakpointsResponse{}
response.Response = *newResponse(request.Seq, request.Command)
ds.send(response)
}
func (ds *fakeDebugSession) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) {
// This would be the place to check if the session was configured to
// stop on entry and if that is the case, to issue a
// stopped-on-breakpoint event. This being a mock implementation,
// we "let" the program continue after sending a successful response.
e := &dap.ThreadEvent{Event: *newEvent("thread"), Body: dap.ThreadEventBody{Reason: "started", ThreadId: 1}}
ds.send(e)
response := &dap.ConfigurationDoneResponse{}
response.Response = *newResponse(request.Seq, request.Command)
ds.send(response)
ds.doContinue()
}
func (ds *fakeDebugSession) onContinueRequest(request *dap.ContinueRequest) {
response := &dap.ContinueResponse{}
response.Response = *newResponse(request.Seq, request.Command)
ds.send(response)
ds.doContinue()
}
func (ds *fakeDebugSession) onNextRequest(request *dap.NextRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "NextRequest is not yet supported"))
}
func (ds *fakeDebugSession) onStepInRequest(request *dap.StepInRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "StepInRequest is not yet supported"))
}
func (ds *fakeDebugSession) onStepOutRequest(request *dap.StepOutRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "StepOutRequest is not yet supported"))
}
func (ds *fakeDebugSession) onStepBackRequest(request *dap.StepBackRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "StepBackRequest is not yet supported"))
}
func (ds *fakeDebugSession) onReverseContinueRequest(request *dap.ReverseContinueRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "ReverseContinueRequest is not yet supported"))
}
func (ds *fakeDebugSession) onRestartFrameRequest(request *dap.RestartFrameRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "RestartFrameRequest is not yet supported"))
}
func (ds *fakeDebugSession) onGotoRequest(request *dap.GotoRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "GotoRequest is not yet supported"))
}
func (ds *fakeDebugSession) onPauseRequest(request *dap.PauseRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "PauseRequest is not yet supported"))
}
func (ds *fakeDebugSession) onStackTraceRequest(request *dap.StackTraceRequest) {
response := &dap.StackTraceResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body = dap.StackTraceResponseBody{
StackFrames: []dap.StackFrame{
{
Id: 1000,
Source: &dap.Source{Name: "hello.go", Path: "/Users/foo/go/src/hello/hello.go", SourceReference: 0},
Line: 5,
Column: 0,
Name: "main.main",
},
},
TotalFrames: 1,
}
ds.send(response)
}
func (ds *fakeDebugSession) onScopesRequest(request *dap.ScopesRequest) {
response := &dap.ScopesResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body = dap.ScopesResponseBody{
Scopes: []dap.Scope{
{Name: "Local", VariablesReference: 1000, Expensive: false},
{Name: "Global", VariablesReference: 1001, Expensive: true},
},
}
ds.send(response)
}
func (ds *fakeDebugSession) onVariablesRequest(request *dap.VariablesRequest) {
select {
case <-ds.stopDebug:
return
// simulate long-running processing to make this handler
// respond to this request after the next request is received
case <-time.After(100 * time.Millisecond):
response := &dap.VariablesResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body = dap.VariablesResponseBody{
Variables: []dap.Variable{{Name: "i", Value: "18434528", EvaluateName: "i", VariablesReference: 0}},
}
ds.send(response)
}
}
func (ds *fakeDebugSession) onSetVariableRequest(request *dap.SetVariableRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "setVariableRequest is not yet supported"))
}
func (ds *fakeDebugSession) onSetExpressionRequest(request *dap.SetExpressionRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "SetExpressionRequest is not yet supported"))
}
func (ds *fakeDebugSession) onSourceRequest(request *dap.SourceRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "SourceRequest is not yet supported"))
}
func (ds *fakeDebugSession) onThreadsRequest(request *dap.ThreadsRequest) {
response := &dap.ThreadsResponse{}
response.Response = *newResponse(request.Seq, request.Command)
response.Body = dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: 1, Name: "main"}}}
ds.send(response)
}
func (ds *fakeDebugSession) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported"))
}
func (ds *fakeDebugSession) onEvaluateRequest(request *dap.EvaluateRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "EvaluateRequest is not yet supported"))
}
func (ds *fakeDebugSession) onStepInTargetsRequest(request *dap.StepInTargetsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "StepInTargetRequest is not yet supported"))
}
func (ds *fakeDebugSession) onGotoTargetsRequest(request *dap.GotoTargetsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "GotoTargetRequest is not yet supported"))
}
func (ds *fakeDebugSession) onCompletionsRequest(request *dap.CompletionsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "CompletionRequest is not yet supported"))
}
func (ds *fakeDebugSession) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "ExceptionRequest is not yet supported"))
}
func (ds *fakeDebugSession) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "LoadedRequest is not yet supported"))
}
func (ds *fakeDebugSession) onDataBreakpointInfoRequest(request *dap.DataBreakpointInfoRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "DataBreakpointInfoRequest is not yet supported"))
}
func (ds *fakeDebugSession) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "SetDataBreakpointsRequest is not yet supported"))
}
func (ds *fakeDebugSession) onReadMemoryRequest(request *dap.ReadMemoryRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "ReadMemoryRequest is not yet supported"))
}
func (ds *fakeDebugSession) onDisassembleRequest(request *dap.DisassembleRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "DisassembleRequest is not yet supported"))
}
func (ds *fakeDebugSession) onCancelRequest(request *dap.CancelRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "CancelRequest is not yet supported"))
}
func (ds *fakeDebugSession) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest) {
ds.send(newErrorResponse(request.Seq, request.Command, "BreakpointLocationsRequest is not yet supported"))
}
func newEvent(event string) *dap.Event {
return &dap.Event{
ProtocolMessage: dap.ProtocolMessage{
Seq: 0,
Type: "event",
},
Event: event,
}
}
func newResponse(requestSeq int, command string) *dap.Response {
return &dap.Response{
ProtocolMessage: dap.ProtocolMessage{
Seq: 0,
Type: "response",
},
Command: command,
RequestSeq: requestSeq,
Success: true,
}
}
func newErrorResponse(requestSeq int, command string, message string) *dap.ErrorResponse {
er := &dap.ErrorResponse{}
er.Response = *newResponse(requestSeq, command)
er.Success = false
er.Message = "unsupported"
er.Body.Error.Format = message
er.Body.Error.Id = 12345
return er
}