service/dap: Initial implementation for 'dlv dap' (#1858)

* Initial implementation for 'dlv dap'

* Fix Travis and AppVeyor failures

* Address review comments

* Address review comments

* Regenrate documentation

* Replace dap server printfs with log.Error

* Update 'dap log'

* Fix typos

* Revert logflags changes that got mixed in by accident
This commit is contained in:
polinasok 2020-02-15 11:52:53 -08:00 committed by GitHub
parent 8fa6d82177
commit fbc4623c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 3194 additions and 28 deletions

@ -38,6 +38,7 @@ Pass flags to the program you are debugging using `--`, for example:
* [dlv attach](dlv_attach.md) - Attach to running process and begin debugging.
* [dlv connect](dlv_connect.md) - Connect to a headless debug server.
* [dlv core](dlv_core.md) - Examine a core dump.
* [dlv dap](dlv_dap.md) - [EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
* [dlv debug](dlv_debug.md) - Compile and begin debugging main package in current directory, or the package specified.
* [dlv exec](dlv_exec.md) - Execute a precompiled binary, and begin a debug session.
* [dlv replay](dlv_replay.md) - Replays a rr trace.

@ -0,0 +1,41 @@
## dlv dap
[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
### Synopsis
[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
The server supports debugging of a precompiled binary akin to 'dlv exec' via a launch request.
It does not yet support support specification of program arguments.
It does not yet support launch requests with 'debug' and 'test' modes that require compilation.
It does not yet support attach requests to debug a running process like with 'dlv attach'.
It does not yet support asynchronous request-response communication.
The server does not accept multiple client connections.
```
dlv dap
```
### Options inherited from parent commands
```
--accept-multiclient Allows a headless server to accept multiple client connections.
--api-version int Selects API version when headless. (default 1)
--backend string Backend selection (see 'dlv help backend'). (default "default")
--build-flags string Build flags, to be passed to the compiler.
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log Enable debugging server logging.
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
--wd string Working directory for running the program. (default ".")
```
### SEE ALSO
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.

@ -17,6 +17,7 @@ names selected from this list:
lldbout Copy output from debugserver/lldb to standard output
debuglineerr Log recoverable errors reading .debug_line
rpc Log all RPC messages
dap Log all DAP messages
fncall Log function call protocol
minidump Log minidump loading
@ -24,8 +25,8 @@ Additionally --log-dest can be used to specify where the logs should be
written.
If the argument is a number it will be interpreted as a file descriptor,
otherwise as a file path.
This option will also redirect the \"API listening\" message in headless
mode.
This option will also redirect the "server listening at" message in headless
and dap modes.

@ -19,6 +19,7 @@ import (
"github.com/go-delve/delve/pkg/version"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/dap"
"github.com/go-delve/delve/service/rpc2"
"github.com/go-delve/delve/service/rpccommon"
"github.com/spf13/cobra"
@ -152,6 +153,22 @@ option to let the process continue or kill it.
}
RootCommand.AddCommand(connectCommand)
// 'dap' subcommand.
dapCommand := &cobra.Command{
Use: "dap",
Short: "[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).",
Long: `[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
The server supports debugging of a precompiled binary akin to 'dlv exec' via a launch request.
It does not yet support support specification of program arguments.
It does not yet support launch requests with 'debug' and 'test' modes that require compilation.
It does not yet support attach requests to debug a running process like with 'dlv attach'.
It does not yet support asynchronous request-response communication.
The server does not accept multiple client connections.`,
Run: dapCmd,
}
RootCommand.AddCommand(dapCommand)
// 'debug' subcommand.
debugCommand := &cobra.Command{
Use: "debug [package]",
@ -318,6 +335,7 @@ names selected from this list:
lldbout Copy output from debugserver/lldb to standard output
debuglineerr Log recoverable errors reading .debug_line
rpc Log all RPC messages
dap Log all DAP messages
fncall Log function call protocol
minidump Log minidump loading
@ -325,8 +343,8 @@ Additionally --log-dest can be used to specify where the logs should be
written.
If the argument is a number it will be interpreted as a file descriptor,
otherwise as a file path.
This option will also redirect the \"API listening\" message in headless
mode.
This option will also redirect the "server listening at" message in headless
and dap modes.
`,
})
@ -344,6 +362,63 @@ func remove(path string) {
}
}
func dapCmd(cmd *cobra.Command, args []string) {
status := func() int {
if err := logflags.Setup(Log, LogOutput, LogDest); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
defer logflags.Close()
if Headless {
fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with dap\n")
}
if AcceptMulti {
fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with dap\n")
}
if InitFile != "" {
fmt.Fprint(os.Stderr, "Warning: init file ignored with dap\n")
}
if ContinueOnStart {
fmt.Fprintf(os.Stderr, "Warning: continue ignored with dap; specify via launch/attach request instead\n")
}
if BuildFlags != "" {
fmt.Fprintf(os.Stderr, "Warning: build flags ignored with dap; specify via launch/attach request instead\n")
}
if WorkingDir != "" {
fmt.Fprintf(os.Stderr, "Warning: working directory ignored with dap; launch requests must specify full program path\n")
}
dlvArgs, targetArgs := splitArgs(cmd, args)
if len(dlvArgs) > 0 {
fmt.Fprintf(os.Stderr, "Warning: debug arguments ignored with dap; specify via launch/attach request instead\n")
}
if len(targetArgs) > 0 {
fmt.Fprintf(os.Stderr, "Warning: program flags ignored with dap; specify via launch/attach request instead\n")
}
listener, err := net.Listen("tcp", Addr)
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
disconnectChan := make(chan struct{})
server := dap.NewServer(&service.Config{
Listener: listener,
Backend: Backend,
Foreground: true, // always headless
DebugInfoDirectories: conf.DebugInfoDirectories,
CheckGoVersion: CheckGoVersion,
DisconnectChan: disconnectChan,
})
defer server.Stop()
server.Run()
waitForDisconnectSignal(disconnectChan)
return 0
}()
os.Exit(status)
}
func debugCmd(cmd *cobra.Command, args []string) {
status := func() int {
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
@ -535,6 +610,37 @@ func connectCmd(cmd *cobra.Command, args []string) {
os.Exit(connect(addr, nil, conf, executingOther))
}
// waitForDisconnectSignal is a blocking function that waits for either
// a SIGINT (Ctrl-C) signal from the OS or for disconnectChan to be closed
// by the server when the client disconnects.
// Note that in headless mode, the debugged process is foregrounded
// (to have control of the tty for debugging interactive programs),
// so SIGINT gets sent to the debuggee and not to delve.
func waitForDisconnectSignal(disconnectChan chan struct{}) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT)
if runtime.GOOS == "windows" {
// On windows Ctrl-C sent to inferior process is delivered
// as SIGINT to delve. Ignore it instead of stopping the server
// in order to be able to debug signal handlers.
go func() {
for {
select {
case <-ch:
}
}
}()
select {
case <-disconnectChan:
}
} else {
select {
case <-ch:
case <-disconnectChan:
}
}
}
func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
if cmd.ArgsLenAtDash() >= 0 {
return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():]
@ -678,28 +784,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
client = rpc2.NewClient(listener.Addr().String())
client.Disconnect(true) // true = continue after disconnect
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT)
if runtime.GOOS == "windows" {
// On windows Ctrl-C sent to inferior process is delivered
// as SIGINT to delve. Ignore it instead of stopping the server
// in order to be able to debug signal handlers.
go func() {
for {
select {
case <-ch:
}
}
}()
select {
case <-disconnectChan:
}
} else {
select {
case <-ch:
case <-disconnectChan:
}
}
waitForDisconnectSignal(disconnectChan)
err = server.Stop()
if err != nil {
fmt.Println(err)

1
go.mod

@ -5,6 +5,7 @@ go 1.11
require (
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5
github.com/cpuguy83/go-md2man v1.0.8 // indirect
github.com/google/go-dap v0.1.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561

2
go.sum

@ -8,6 +8,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-dap v0.1.0 h1:UzVzngq6yBR+bJVnTM8mjcdKrQnYR7m7U7JpNSnEo48=
github.com/google/go-dap v0.1.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=

@ -21,6 +21,7 @@ var gdbWire = false
var lldbServerOutput = false
var debugLineErrors = false
var rpc = false
var dap = false
var fnCall = false
var minidump = false
@ -82,6 +83,16 @@ func RPCLogger() *logrus.Entry {
return makeLogger(rpc, logrus.Fields{"layer": "rpc"})
}
// DAP returns true if dap package should log.
func DAP() bool {
return dap
}
// DAPLogger returns a logger for dap package.
func DAPLogger() *logrus.Entry {
return makeLogger(dap, logrus.Fields{"layer": "dap"})
}
// FnCall returns true if the function call protocol should be logged.
func FnCall() bool {
return fnCall
@ -100,12 +111,21 @@ func MinidumpLogger() *logrus.Entry {
return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"})
}
// WriteDAPListeningMessage writes the "DAP server listening" message in dap mode.
func WriteDAPListeningMessage(addr string) {
writeListeningMessage("DAP", addr)
}
// WriteAPIListeningMessage writes the "API server listening" message in headless mode.
func WriteAPIListeningMessage(addr string) {
writeListeningMessage("API", addr)
}
func writeListeningMessage(server string, addr string) {
if logOut != nil {
fmt.Fprintf(logOut, "API server listening at: %s\n", addr)
fmt.Fprintf(logOut, "%s server listening at: %s\n", server, addr)
} else {
fmt.Printf("API server listening at: %s\n", addr)
fmt.Printf("%s server listening at: %s\n", server, addr)
}
}
@ -151,10 +171,14 @@ func Setup(logFlag bool, logstr string, logDest string) error {
debugLineErrors = true
case "rpc":
rpc = true
case "dap":
dap = true
case "fncall":
fnCall = true
case "minidump":
minidump = true
// If adding another value, do make sure to
// update "Help about logging flags" in commands.go.
}
}
return nil

@ -0,0 +1,189 @@
package daptest
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log"
"net"
"path/filepath"
"github.com/google/go-dap"
)
// Package daptest provides a sample client with utilities
// for DAP mode testing.
// Client is a debugger service client that uses Debug Adaptor Protocol.
// It does not (yet?) implement service.Client interface.
// All client methods are synchronous.
type Client struct {
conn net.Conn
reader *bufio.Reader
// seq is used to track the sequence number of each
// requests that the client sends to the server
seq int
}
// NewClient creates a new Client over a TCP connection.
// Call Close() to close the connection.
func NewClient(addr string) *Client {
fmt.Println("Connecting to server at:", addr)
conn, err := net.Dial("tcp", addr)
if err != nil {
log.Fatal("dialing:", err)
}
c := &Client{conn: conn, reader: bufio.NewReader(conn)}
return c
}
// Close closes the client connection
func (c *Client) Close() {
c.conn.Close()
}
func (c *Client) send(request dap.Message) {
jsonmsg, _ := json.Marshal(request)
fmt.Println("[client -> server]", string(jsonmsg))
dap.WriteProtocolMessage(c.conn, request)
}
// ReadBaseMessage reads and returns a json-encoded DAP message.
func (c *Client) ReadBaseMessage() ([]byte, error) {
message, err := dap.ReadBaseMessage(c.reader)
if err != nil {
fmt.Println("DAP client error:", err)
return nil, err
}
fmt.Println("[client <- server]", string(message))
return message, nil
}
// ReadErrorResponse reads, decodes and validates the result
// to be an error response. Returns the response or an error.
func (c *Client) ReadErrorResponse() (dap.Message, error) {
response, err := dap.ReadProtocolMessage(c.reader)
if err != nil {
return nil, err
}
switch response.(type) {
case *dap.ErrorResponse:
return response, nil
default:
return nil, errors.New(fmt.Sprintf("not an ErrorResponse: %#v", response))
}
}
// InitializeRequest sends an 'initialize' request.
func (c *Client) InitializeRequest() {
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
request.Arguments = dap.InitializeRequestArguments{
AdapterID: "go",
PathFormat: "path",
LinesStartAt1: true,
ColumnsStartAt1: true,
SupportsVariableType: true,
SupportsVariablePaging: true,
SupportsRunInTerminalRequest: true,
Locale: "en-us",
}
c.send(request)
}
// LaunchRequest sends a 'launch' request.
func (c *Client) LaunchRequest(program string, stopOnEntry bool) {
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
request.Arguments = map[string]interface{}{
"request": "launch",
"mode": "exec",
"program": program,
"stopOnEntry": stopOnEntry,
}
c.send(request)
}
// DisconnectRequest sends a 'disconnect' request.
func (c *Client) DisconnectRequest() {
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
c.send(request)
}
// SetBreakpointsRequest sends a 'setBreakpoints' request.
func (c *Client) SetBreakpointsRequest(file string, lines []int) {
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setBreakpoints")}
request.Arguments = dap.SetBreakpointsArguments{
Source: dap.Source{
Name: filepath.Base(file),
Path: file,
},
Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
//sourceModified: false,
}
for i, l := range lines {
request.Arguments.Breakpoints[i].Line = l
}
c.send(request)
}
// SetExceptionBreakpointsRequest sends a 'setExceptionBreakpoints' request.
func (c *Client) SetExceptionBreakpointsRequest() {
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setExceptionBreakpoints")}
c.send(request)
}
// ConfigurationDoneRequest sends a 'configurationDone' request.
func (c *Client) ConfigurationDoneRequest() {
request := &dap.ConfigurationDoneRequest{Request: *c.newRequest("configurationDone")}
c.send(request)
}
// ContinueRequest sends a 'continue' request.
func (c *Client) ContinueRequest(thread int) {
request := &dap.ContinueRequest{Request: *c.newRequest("continue")}
request.Arguments.ThreadId = thread
c.send(request)
}
// UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnkownRequest() {
request := c.newRequest("unknown")
c.send(request)
}
// UnkownProtocolMessage triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnkownProtocolMessage() {
m := &dap.ProtocolMessage{}
m.Seq = -1
m.Type = "unknown"
c.send(m)
}
// UnknownEvent triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnknownEvent() {
event := &dap.Event{}
event.Type = "event"
event.Seq = -1
event.Event = "unknown"
c.send(event)
}
// KnownEvent passes decode checks, but delve has no 'case' to
// handle it. This behaves the same way a new request type
// added to go-dap, but not to delve.
func (c *Client) KnownEvent() {
event := &dap.Event{}
event.Type = "event"
event.Seq = -1
event.Event = "terminated"
c.send(event)
}
func (c *Client) newRequest(command string) *dap.Request {
request := &dap.Request{}
request.Type = "request"
request.Command = command
request.Seq = c.seq
c.seq++
return request
}

15
service/dap/error_ids.go Normal file

@ -0,0 +1,15 @@
package dap
// Unique identifiers for messages returned for errors from requests.
const (
UnsupportedCommand int = 9999
// The values below come from the vscode-go debug adaptor.
// Although the spec says they should be unique, the adaptor
// reuses 3000 for launch, attach and program exit failures.
// TODO(polina): confirm if the extension expects specific ids
// for specific cases, and we must match the existing adaptor
// or if these codes can evolve.
FailedToContinue = 3000
// Add more codes as we support more requests
)

466
service/dap/server.go Normal file

@ -0,0 +1,466 @@
package dap
import (
"bufio"
"encoding/json"
"fmt"
"io"
"net"
"path/filepath"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/debugger"
"github.com/google/go-dap"
"github.com/sirupsen/logrus"
)
// Package dap implements VSCode's Debug Adaptor Protocol (DAP).
// This allows delve to communicate with frontends using DAP
// without a separate adaptor. The frontend will run the debugger
// (which now doubles as an adaptor) in server mode listening on
// a port and communicating over TCP. This is work in progress,
// so for now Delve in dap mode only supports synchronous
// request-response communication, blocking while processing each request.
// For DAP details see https://microsoft.github.io/debug-adapter-protocol.
// Server implements a DAP server that can accept a single client for
// a single debug session. It does not support restarting.
// The server operates via two goroutines:
// (1) Main goroutine where the server is created via NewServer(),
// started via Run() and stopped via Stop().
// (2) Run goroutine started from Run() that accepts a client connection,
// reads, decodes and processes each request, issuing commands to the
// underlying debugger and sending back events and responses.
// TODO(polina): make it asynchronous (i.e. launch goroutine per request)
type Server struct {
// config is all the information necessary to start the debugger and server.
config *service.Config
// listener is used to accept the client connection.
listener net.Listener
// conn is the accepted client connection.
conn net.Conn
// reader is used to read requests from the connection.
reader *bufio.Reader
// debugger is the underlying debugger service.
debugger *debugger.Debugger
// log is used for structured logging.
log *logrus.Entry
// stopOnEntry is set to automatically stop the debugee after start.
stopOnEntry bool
}
// NewServer creates a new DAP Server. It takes an opened Listener
// via config and assumes its ownership. Optionally takes DisconnectChan
// via config, which can be used to detect when the client disconnects
// and the server is ready to be shut down. The caller must call
// Stop() on shutdown.
func NewServer(config *service.Config) *Server {
logger := logflags.DAPLogger()
logflags.WriteDAPListeningMessage(config.Listener.Addr().String())
return &Server{
config: config,
listener: config.Listener,
log: logger,
}
}
// Stop stops the DAP debugger service, closes the listener and
// the client connection. It shuts down the underlying debugger
// and kills the target process if it was launched by it.
func (s *Server) Stop() {
s.listener.Close()
if s.conn != nil {
// Unless Stop() was called after serveDAPCodec()
// returned, this will result in closed connection error
// on next read, breaking out of the read loop and
// allowing the run goroutine to exit.
s.conn.Close()
}
if s.debugger != nil {
kill := s.config.AttachPid == 0
if err := s.debugger.Detach(kill); err != nil {
s.log.Error(err)
}
}
}
// signalDisconnect closes config.DisconnectChan if not nil, which
// signals that the client disconnected or there was a client
// connection failure. Since the server currently services only one
// client, this can be used as a signal to the entire server via
// Stop(). The function safeguards agaist closing the channel more
// than once and can be called multiple times. It is not thread-safe
// and is currently only called from the run goroutine.
// TODO(polina): lock this when we add more goroutines that could call
// this when we support asynchronous request-response communication.
func (s *Server) signalDisconnect() {
// DisconnectChan might be nil at server creation if the
// caller does not want to rely on the disconnect signal.
if s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
// Take advantage of the nil check above to avoid accidentally
// closing the channel twice and causing a panic, when this
// function is called more than once. For example, we could
// have the following sequence of events:
// -- run goroutine: calls onDisconnectRequest()
// -- run goroutine: calls signalDisconnect()
// -- main goroutine: calls Stop()
// -- main goroutine: Stop() closes client connection
// -- run goroutine: serveDAPCodec() gets "closed network connection"
// -- run goroutine: serveDAPCodec() returns
// -- run goroutine: serveDAPCodec calls signalDisconnect()
s.config.DisconnectChan = nil
}
}
// Run launches a new goroutine where it accepts a client connection
// and starts processing requests from it. Use Stop() to close connection.
// The server does not support multiple clients, serially or in parallel.
// The server should be restarted for every new debug session.
// The debugger won't be started until launch/attach request is received.
// TODO(polina): allow new client connections for new debug sessions,
// so the editor needs to launch delve only once?
func (s *Server) Run() {
go func() {
conn, err := s.listener.Accept()
if err != nil {
// This will print if the server is killed with Ctrl+C
// before client connection is accepted.
s.log.Errorf("Error accepting client connection: %s\n", err)
s.signalDisconnect()
return
}
s.conn = conn
s.serveDAPCodec()
}()
}
// serveDAPCodec reads and decodes requests from the client
// until it encounters an error or EOF, when it sends
// the disconnect signal and returns.
func (s *Server) serveDAPCodec() {
defer s.signalDisconnect()
s.reader = bufio.NewReader(s.conn)
for {
request, err := dap.ReadProtocolMessage(s.reader)
// TODO(polina): Differentiate between errors and handle them
// gracefully. For example,
// -- "use of closed network connection" means client connection
// was closed via Stop() in response to a disconnect request.
// -- "Request command 'foo' is not supported" means we
// potentially got some new DAP request that we do not yet have
// decoding support for, so we can respond with an ErrorResponse.
// TODO(polina): to support this add Seq to
// dap.DecodeProtocolMessageFieldError.
if err != nil {
if err != io.EOF {
s.log.Error("DAP error: ", err)
}
return
}
// TODO(polina) Add a panic guard,
// so we do not kill user's process when delve panics.
s.handleRequest(request)
}
}
func (s *Server) handleRequest(request dap.Message) {
jsonmsg, _ := json.Marshal(request)
s.log.Debug("[<- from client]", string(jsonmsg))
switch request := request.(type) {
case *dap.InitializeRequest:
s.onInitializeRequest(request)
case *dap.LaunchRequest:
s.onLaunchRequest(request)
case *dap.AttachRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.DisconnectRequest:
s.onDisconnectRequest(request)
case *dap.TerminateRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.RestartRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetBreakpointsRequest:
s.onSetBreakpointsRequest(request)
case *dap.SetFunctionBreakpointsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetExceptionBreakpointsRequest:
s.onSetExceptionBreakpointsRequest(request)
case *dap.ConfigurationDoneRequest:
s.onConfigurationDoneRequest(request)
case *dap.ContinueRequest:
s.onContinueRequest(request)
case *dap.NextRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepInRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepOutRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepBackRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ReverseContinueRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.RestartFrameRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.GotoRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.PauseRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StackTraceRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ScopesRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.VariablesRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetVariableRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetExpressionRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SourceRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ThreadsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.TerminateThreadsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.EvaluateRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepInTargetsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.GotoTargetsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.CompletionsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ExceptionInfoRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.LoadedSourcesRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.DataBreakpointInfoRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetDataBreakpointsRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.ReadMemoryRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.DisassembleRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.CancelRequest:
s.sendUnsupportedErrorResponse(request.Request)
case *dap.BreakpointLocationsRequest:
s.sendUnsupportedErrorResponse(request.Request)
default:
// This is a DAP message that go-dap has a struct for, so
// decoding succeeded, but this function does not know how
// to handle. We should be sending an ErrorResponse, but
// we cannot get to Seq and other fields from dap.Message.
// TODO(polina): figure out how to handle this better.
// Consider adding GetSeq() method to dap.Message interface.
s.log.Errorf("Unable to process %#v\n", request)
}
}
func (s *Server) send(message dap.Message) {
jsonmsg, _ := json.Marshal(message)
s.log.Debug("[-> to client]", string(jsonmsg))
dap.WriteProtocolMessage(s.conn, message)
}
func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
// TODO(polina): Respond with an error if debug session is in progress?
response := &dap.InitializeResponse{Response: *newResponse(request.Request)}
response.Body.SupportsConfigurationDoneRequest = true
// TODO(polina): support this to match vscode-go functionality
response.Body.SupportsSetVariable = false
s.send(response)
}
func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
// TODO(polina): Respond with an error if debug session is in progress?
program, ok := request.Arguments["program"]
if !ok || program == "" {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch",
"The program attribute is missing in debug configuration.")
return
}
s.config.ProcessArgs = []string{program.(string)}
s.config.WorkingDir = filepath.Dir(program.(string))
// TODO: support program args
stop, ok := request.Arguments["stopOnEntry"]
s.stopOnEntry = (ok && stop == true)
mode, ok := request.Arguments["mode"]
if !ok || mode == "" {
mode = "debug"
}
// TODO(polina): support "debug", "test" and "remote" modes
if mode != "exec" {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch",
fmt.Sprintf("Unsupported 'mode' value '%s' in debug configuration.", mode))
return
}
config := &debugger.Config{
WorkingDir: s.config.WorkingDir,
AttachPid: 0,
CoreFile: "",
Backend: s.config.Backend,
Foreground: s.config.Foreground,
DebugInfoDirectories: s.config.DebugInfoDirectories,
CheckGoVersion: s.config.CheckGoVersion,
}
var err error
if s.debugger, err = debugger.New(config, s.config.ProcessArgs); err != nil {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch", err.Error())
return
}
// Notify the client that the debugger is ready to start accepting
// configuration requests for setting breakpoints, etc. The client
// will end the configuration sequence with 'configurationDone'.
s.send(&dap.InitializedEvent{Event: *newEvent("initialized")})
s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)})
}
// onDisconnectRequest handles the DisconnectRequest. Per the DAP spec,
// it disconnects the debuggee and signals that the debug adaptor
// (in our case this TCP server) can be terminated.
func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)})
if s.debugger != nil {
_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt})
if err != nil {
s.log.Error(err)
}
kill := s.config.AttachPid == 0
err = s.debugger.Detach(kill)
if err != nil {
s.log.Error(err)
}
}
// TODO(polina): make thread-safe when handlers become asynchronous.
s.signalDisconnect()
}
func (s *Server) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) {
if request.Arguments.Source.Path == "" {
s.log.Error("ERROR: Unable to set breakpoint for empty file path")
}
response := &dap.SetBreakpointsResponse{Response: *newResponse(request.Request)}
response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
// Only verified breakpoints will be set and reported back in the
// response. All breakpoints resulting in errors (e.g. duplicates
// or lines that do not have statements) will be skipped.
i := 0
for _, b := range request.Arguments.Breakpoints {
bp, err := s.debugger.CreateBreakpoint(
&api.Breakpoint{File: request.Arguments.Source.Path, Line: b.Line})
if err != nil {
s.log.Error("ERROR:", err)
continue
}
response.Body.Breakpoints[i].Verified = true
response.Body.Breakpoints[i].Line = bp.Line
i++
}
response.Body.Breakpoints = response.Body.Breakpoints[:i]
s.send(response)
}
func (s *Server) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) {
// Unlike what DAP documentation claims, this request is always sent
// even though we specified no filters at initializatin. Handle as no-op.
s.send(&dap.SetExceptionBreakpointsResponse{Response: *newResponse(request.Request)})
}
func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) {
if s.stopOnEntry {
e := &dap.StoppedEvent{
Event: *newEvent("stopped"),
Body: dap.StoppedEventBody{Reason: "breakpoint", ThreadId: 1, AllThreadsStopped: true},
}
s.send(e)
}
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
if !s.stopOnEntry {
s.doContinue()
}
}
func (s *Server) onContinueRequest(request *dap.ContinueRequest) {
s.send(&dap.ContinueResponse{Response: *newResponse(request.Request)})
s.doContinue()
}
func (s *Server) sendErrorResponse(request dap.Request, id int, summary string, details string) {
er := &dap.ErrorResponse{}
er.Type = "response"
er.Command = request.Command
er.RequestSeq = request.Seq
er.Success = false
er.Message = summary
er.Body.Error.Id = id
er.Body.Error.Format = fmt.Sprintf("%s: %s", summary, details)
s.log.Error(er.Body.Error.Format)
s.send(er)
}
func (s *Server) sendUnsupportedErrorResponse(request dap.Request) {
s.sendErrorResponse(request, UnsupportedCommand, "Unsupported command",
fmt.Sprintf("cannot process '%s' request", request.Command))
}
func newResponse(request dap.Request) *dap.Response {
return &dap.Response{
ProtocolMessage: dap.ProtocolMessage{
Seq: 0,
Type: "response",
},
Command: request.Command,
RequestSeq: request.Seq,
Success: true,
}
}
func newEvent(event string) *dap.Event {
return &dap.Event{
ProtocolMessage: dap.ProtocolMessage{
Seq: 0,
Type: "event",
},
Event: event,
}
}
func (s *Server) doContinue() {
if s.debugger == nil {
return
}
state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Continue})
if err != nil {
s.log.Error(err)
switch err.(type) {
case proc.ErrProcessExited:
e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
s.send(e)
default:
}
return
}
if state.Exited {
e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
s.send(e)
} else {
e := &dap.StoppedEvent{Event: *newEvent("stopped")}
// TODO(polina): differentiate between breakpoint and pause on halt.
e.Body.Reason = "breakpoint"
e.Body.AllThreadsStopped = true
e.Body.ThreadId = state.SelectedGoroutine.ID
s.send(e)
}
}

160
service/dap/server_test.go Normal file

@ -0,0 +1,160 @@
package dap
import (
"bytes"
"flag"
"fmt"
"net"
"os"
"testing"
"time"
"github.com/go-delve/delve/pkg/logflags"
protest "github.com/go-delve/delve/pkg/proc/test"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/dap/daptest"
"github.com/google/go-dap"
)
func TestMain(m *testing.M) {
var logOutput string
flag.StringVar(&logOutput, "log-output", "", "configures log output")
flag.Parse()
logflags.Setup(logOutput != "", logOutput, "")
os.Exit(protest.RunTestsWithFixtures(m))
}
func startDAPServer(t *testing.T) (server *Server, addr string) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}
server = NewServer(&service.Config{
Listener: listener,
Backend: "default",
DisconnectChan: nil,
})
server.Run()
// Give server time to start listening for clients
time.Sleep(100 * time.Millisecond)
return server, listener.Addr().String()
}
func expectMessage(t *testing.T, client *daptest.Client, want []byte) {
got, err := client.ReadBaseMessage()
if err != nil {
t.Error(err)
}
if !bytes.Equal(got, want) {
t.Errorf("\ngot %q\nwant %q", got, want)
}
}
// name is for _fixtures/<name>.go
func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
var buildFlags protest.BuildFlags
fixture := protest.BuildFixture(name, buildFlags)
server, addr := startDAPServer(t)
client := daptest.NewClient(addr)
defer client.Close()
defer server.Stop()
test(client, fixture)
}
// TODO(polina): instead of hardcoding message bytes,
// add methods to client to receive, decode and verify responses.
func TestStopOnEntry(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
client.InitializeRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))
client.LaunchRequest(fixture.Path, true /*stopOnEntry*/)
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))
client.SetExceptionBreakpointsRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setExceptionBreakpoints"}`))
client.ConfigurationDoneRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"configurationDone"}`))
client.ContinueRequest(1)
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"continue","body":{}}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))
client.DisconnectRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"disconnect"}`))
})
}
func TestSetBreakpoint(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
client.InitializeRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))
client.LaunchRequest(fixture.Path, false /*stopOnEntry*/)
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))
client.SetBreakpointsRequest(fixture.Source, []int{8, 100})
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"verified":true,"source":{},"line":8}]}}`))
client.SetExceptionBreakpointsRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"setExceptionBreakpoints"}`))
client.ConfigurationDoneRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}`))
client.ContinueRequest(1)
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"continue","body":{}}`))
client.ContinueRequest(1)
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":6,"success":true,"command":"continue","body":{}}`))
client.DisconnectRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":7,"success":true,"command":"disconnect"}`))
})
}
func expectErrorResponse(t *testing.T, client *daptest.Client, requestSeq int, command string, message string, id int) *dap.ErrorResponse {
response, err := client.ReadErrorResponse()
if err != nil {
t.Error(err)
return nil
}
got := response.(*dap.ErrorResponse)
if got.RequestSeq != requestSeq || got.Command != command || got.Message != message || got.Body.Error.Id != id {
want := fmt.Sprintf("{RequestSeq: %d, Command: %q, Message: %q, Id: %d}", requestSeq, command, message, id)
t.Errorf("\ngot %#v\nwant %s", got, want)
return nil
}
return got
}
func TestBadLaunchRequests(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
client.LaunchRequest("", true)
// Test for the DAP-specific detailed error message.
want := "Failed to launch: The program attribute is missing in debug configuration."
if got := expectErrorResponse(t, client, 0, "launch", "Failed to launch", 3000); got != nil && got.Body.Error.Format != want {
t.Errorf("got %q, want %q", got.Body.Error.Format, want)
}
// Skip detailed message checks for potentially different OS-specific errors.
client.LaunchRequest(fixture.Path+"_does_not_exist", false)
expectErrorResponse(t, client, 1, "launch", "Failed to launch", 3000)
client.LaunchRequest(fixture.Source, true) // Not an executable
expectErrorResponse(t, client, 2, "launch", "Failed to launch", 3000)
// We failed to launch the program. Make sure shutdown still works.
client.DisconnectRequest()
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"disconnect"}`))
})
}

12
vendor/github.com/google/go-dap/.travis.yml generated vendored Normal file

@ -0,0 +1,12 @@
language: go
go:
- 1.13.x
env:
global:
- GOPROXY=https://proxy.golang.org
- GO111MODULE=on
script:
- 'internal/test.sh'

202
vendor/github.com/google/go-dap/LICENSE generated vendored Normal file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.

19
vendor/github.com/google/go-dap/README.md generated vendored Normal file

@ -0,0 +1,19 @@
# go-dap: Go implementation of the Debug Adapter Protocol
[![Build Status](https://travis-ci.org/google/go-dap.svg?branch=master)](https://travis-ci.org/google/go-dap)
[![Go Report Card](https://goreportcard.com/badge/github.com/google/go-dap)](https://goreportcard.com/report/github.com/google/go-dap)
For an overview of DAP, see
https://microsoft.github.io/debug-adapter-protocol/overview
## Contributing
We'd love to accept your patches and contributions to this project. See
[docs/contributing](https://github.com/google/go-dap/blob/master/docs/contributing.md)
for more details.
## License
This project is licensed under the Apache License 2.0
This is not an officially supported Google product.

219
vendor/github.com/google/go-dap/codec.go generated vendored Normal file

@ -0,0 +1,219 @@
// Copyright 2019 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 contains utilities for decoding JSON-encoded bytes into DAP message.
package dap
import (
"encoding/json"
"fmt"
)
// DecodeProtocolMessageFieldError describes which JSON attribute
// has an unsupported value that the decoding cannot handle.
type DecodeProtocolMessageFieldError struct {
SubType string
FieldName string
FieldValue string
}
func (e *DecodeProtocolMessageFieldError) Error() string {
return fmt.Sprintf("%s %s '%s' is not supported", e.SubType, e.FieldName, e.FieldValue)
}
// DecodeProtocolMessage parses the JSON-encoded data and returns the result of
// the appropriate type within the ProtocolMessage hierarchy. If message type,
// command, etc cannot be cast, returns DecodeProtocolMessageFieldError.
// See also godoc for json.Unmarshal, which is used for underlying decoding.
func DecodeProtocolMessage(data []byte) (Message, error) {
var protomsg ProtocolMessage
if err := json.Unmarshal(data, &protomsg); err != nil {
return nil, err
}
switch protomsg.Type {
case "request":
return decodeRequest(data)
case "response":
return decodeResponse(data)
case "event":
return decodeEvent(data)
default:
return nil, &DecodeProtocolMessageFieldError{"ProtocolMessage", "type", protomsg.Type}
}
}
type messageCtor func() Message
// decodeRequest determines what request type in the ProtocolMessage hierarchy
// data corresponds to and uses json.Unmarshal to populate the corresponding
// struct to be returned.
func decodeRequest(data []byte) (Message, error) {
var r Request
if err := json.Unmarshal(data, &r); err != nil {
return nil, err
}
if ctor, ok := requestCtor[r.Command]; ok {
requestPtr := ctor()
err := json.Unmarshal(data, requestPtr)
return requestPtr, err
}
return nil, &DecodeProtocolMessageFieldError{"Request", "command", r.Command}
}
// Mapping of request commands and corresponding struct constructors that
// can be passed to json.Unmarshal.
var requestCtor = map[string]messageCtor{
"cancel": func() Message { return &CancelRequest{} },
"runInTerminal": func() Message { return &RunInTerminalRequest{} },
"initialize": func() Message { return &InitializeRequest{} },
"configurationDone": func() Message { return &ConfigurationDoneRequest{} },
"launch": func() Message { return &LaunchRequest{} },
"attach": func() Message { return &AttachRequest{} },
"restart": func() Message { return &RestartRequest{} },
"disconnect": func() Message { return &DisconnectRequest{} },
"terminate": func() Message { return &TerminateRequest{} },
"breakpointLocations": func() Message { return &BreakpointLocationsRequest{} },
"setBreakpoints": func() Message { return &SetBreakpointsRequest{} },
"setFunctionBreakpoints": func() Message { return &SetFunctionBreakpointsRequest{} },
"setExceptionBreakpoints": func() Message { return &SetExceptionBreakpointsRequest{} },
"dataBreakpointInfo": func() Message { return &DataBreakpointInfoRequest{} },
"setDataBreakpoints": func() Message { return &SetDataBreakpointsRequest{} },
"continue": func() Message { return &ContinueRequest{} },
"next": func() Message { return &NextRequest{} },
"stepIn": func() Message { return &StepInRequest{} },
"stepOut": func() Message { return &StepOutRequest{} },
"stepBack": func() Message { return &StepBackRequest{} },
"reverseContinue": func() Message { return &ReverseContinueRequest{} },
"restartFrame": func() Message { return &RestartFrameRequest{} },
"goto": func() Message { return &GotoRequest{} },
"pause": func() Message { return &PauseRequest{} },
"stackTrace": func() Message { return &StackTraceRequest{} },
"scopes": func() Message { return &ScopesRequest{} },
"variables": func() Message { return &VariablesRequest{} },
"setVariable": func() Message { return &SetVariableRequest{} },
"source": func() Message { return &SourceRequest{} },
"threads": func() Message { return &ThreadsRequest{} },
"terminateThreads": func() Message { return &TerminateThreadsRequest{} },
"modules": func() Message { return &ModulesRequest{} },
"loadedSources": func() Message { return &LoadedSourcesRequest{} },
"evaluate": func() Message { return &EvaluateRequest{} },
"setExpression": func() Message { return &SetExpressionRequest{} },
"stepInTargets": func() Message { return &StepInTargetsRequest{} },
"gotoTargets": func() Message { return &GotoTargetsRequest{} },
"completions": func() Message { return &CompletionsRequest{} },
"exceptionInfo": func() Message { return &ExceptionInfoRequest{} },
"readMemory": func() Message { return &ReadMemoryRequest{} },
"disassemble": func() Message { return &DisassembleRequest{} },
}
// decodeResponse determines what response type in the ProtocolMessage hierarchy
// data corresponds to and uses json.Unmarshal to populate the corresponding
// struct to be returned.
func decodeResponse(data []byte) (Message, error) {
var r Response
if err := json.Unmarshal(data, &r); err != nil {
return nil, err
}
if !r.Success {
var er ErrorResponse
err := json.Unmarshal(data, &er)
return &er, err
}
if ctor, ok := responseCtor[r.Command]; ok {
responsePtr := ctor()
err := json.Unmarshal(data, responsePtr)
return responsePtr, err
}
return nil, &DecodeProtocolMessageFieldError{"Response", "command", r.Command}
}
// Mapping of response commands and corresponding struct constructors that
// can be passed to json.Unmarshal.
var responseCtor = map[string]messageCtor{
"cancel": func() Message { return &CancelResponse{} },
"runInTerminal": func() Message { return &RunInTerminalResponse{} },
"initialize": func() Message { return &InitializeResponse{} },
"configurationDone": func() Message { return &ConfigurationDoneResponse{} },
"launch": func() Message { return &LaunchResponse{} },
"attach": func() Message { return &AttachResponse{} },
"restart": func() Message { return &RestartResponse{} },
"disconnect": func() Message { return &DisconnectResponse{} },
"terminate": func() Message { return &TerminateResponse{} },
"breakpointLocations": func() Message { return &BreakpointLocationsResponse{} },
"setBreakpoints": func() Message { return &SetBreakpointsResponse{} },
"setFunctionBreakpoints": func() Message { return &SetFunctionBreakpointsResponse{} },
"setExceptionBreakpoints": func() Message { return &SetExceptionBreakpointsResponse{} },
"dataBreakpointInfo": func() Message { return &DataBreakpointInfoResponse{} },
"setDataBreakpoints": func() Message { return &SetDataBreakpointsResponse{} },
"continue": func() Message { return &ContinueResponse{} },
"next": func() Message { return &NextResponse{} },
"stepIn": func() Message { return &StepInResponse{} },
"stepOut": func() Message { return &StepOutResponse{} },
"stepBack": func() Message { return &StepBackResponse{} },
"reverseContinue": func() Message { return &ReverseContinueResponse{} },
"restartFrame": func() Message { return &RestartFrameResponse{} },
"goto": func() Message { return &GotoResponse{} },
"pause": func() Message { return &PauseResponse{} },
"stackTrace": func() Message { return &StackTraceResponse{} },
"scopes": func() Message { return &ScopesResponse{} },
"variables": func() Message { return &VariablesResponse{} },
"setVariable": func() Message { return &SetVariableResponse{} },
"source": func() Message { return &SourceResponse{} },
"threads": func() Message { return &ThreadsResponse{} },
"terminateThreads": func() Message { return &TerminateThreadsResponse{} },
"modules": func() Message { return &ModulesResponse{} },
"loadedSources": func() Message { return &LoadedSourcesResponse{} },
"evaluate": func() Message { return &EvaluateResponse{} },
"setExpression": func() Message { return &SetExpressionResponse{} },
"stepInTargets": func() Message { return &StepInTargetsResponse{} },
"gotoTargets": func() Message { return &GotoTargetsResponse{} },
"completions": func() Message { return &CompletionsResponse{} },
"exceptionInfo": func() Message { return &ExceptionInfoResponse{} },
"readMemory": func() Message { return &ReadMemoryResponse{} },
"disassemble": func() Message { return &DisassembleResponse{} },
}
// decodeEvent determines what event type in the ProtocolMessage hierarchy
// data corresponds to and uses json.Unmarshal to populate the corresponding
// struct to be returned.
func decodeEvent(data []byte) (Message, error) {
var e Event
if err := json.Unmarshal(data, &e); err != nil {
return nil, err
}
if ctor, ok := eventCtor[e.Event]; ok {
eventPtr := ctor()
err := json.Unmarshal(data, eventPtr)
return eventPtr, err
}
return nil, &DecodeProtocolMessageFieldError{"Event", "event", e.Event}
}
// Mapping of event ids and corresponding struct constructors that
// can be passed to json.Unmarshal.
var eventCtor = map[string]messageCtor{
"initialized": func() Message { return &InitializedEvent{} },
"stopped": func() Message { return &StoppedEvent{} },
"continued": func() Message { return &ContinuedEvent{} },
"exited": func() Message { return &ExitedEvent{} },
"terminated": func() Message { return &TerminatedEvent{} },
"thread": func() Message { return &ThreadEvent{} },
"output": func() Message { return &OutputEvent{} },
"breakpoint": func() Message { return &BreakpointEvent{} },
"module": func() Message { return &ModuleEvent{} },
"loadedSource": func() Message { return &LoadedSourceEvent{} },
"process": func() Message { return &ProcessEvent{} },
"capabilities": func() Message { return &CapabilitiesEvent{} },
}

3
vendor/github.com/google/go-dap/go.mod generated vendored Normal file

@ -0,0 +1,3 @@
module github.com/google/go-dap
go 1.13

137
vendor/github.com/google/go-dap/io.go generated vendored Normal file

@ -0,0 +1,137 @@
// Copyright 2019 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 contains utilities for DAP Base protocol I/O.
// For additional information, see "Base protocol" section in
// https://microsoft.github.io/debug-adapter-protocol/overview.
package dap
import (
"bufio"
"encoding/json"
"fmt"
"io"
"regexp"
"strconv"
"strings"
)
// BaseProtocolError represents base protocol error, which occurs when the raw
// message does not conform to the header+content format of the base protocol.
type BaseProtocolError struct {
Err string
}
func (bpe *BaseProtocolError) Error() string { return bpe.Err }
var (
// ErrHeaderDelimiterNotCrLfCrLf is returned when only partial header
// delimiter \r\n\r\n is encountered.
ErrHeaderDelimiterNotCrLfCrLf = &BaseProtocolError{fmt.Sprintf("header delimiter is not %q", crLfcrLf)}
// ErrHeaderNotContentLength is returned when the parsed header is
// not of valid Content-Length format.
ErrHeaderNotContentLength = &BaseProtocolError{fmt.Sprintf("header format is not %q", contentLengthHeaderRegex)}
// ErrHeaderContentTooLong is returned when the content length specified in
// the header is above contentMaxLength.
ErrHeaderContentTooLong = &BaseProtocolError{fmt.Sprintf("content length over %v bytes", contentMaxLength)}
)
const (
crLfcrLf = "\r\n\r\n"
contentLengthHeaderFmt = "Content-Length: %d\r\n\r\n"
contentMaxLength = 4 * 1024 * 1024
)
var (
contentLengthHeaderRegex = regexp.MustCompile("^Content-Length: ([0-9]+)$")
)
// WriteBaseMessage formats content with Content-Length header and delimiters
// as per the base protocol and writes the resulting message to w.
func WriteBaseMessage(w io.Writer, content []byte) error {
header := fmt.Sprintf(contentLengthHeaderFmt, len(content))
if _, err := w.Write([]byte(header)); err != nil {
return err
}
_, err := w.Write(content)
return err
}
// ReadBaseMessage reads one message from r consisting of a Content-Length
// header and a content part. It parses the header to determine the size of
// the content part and extracts and returns the actual content of the message.
// Returns nil bytes on error, which can be one of the standard IO errors or
// a BaseProtocolError defined in this package.
func ReadBaseMessage(r *bufio.Reader) ([]byte, error) {
contentLength, err := readContentLengthHeader(r)
if err != nil {
return nil, err
}
if contentLength > contentMaxLength {
return nil, ErrHeaderContentTooLong
}
content := make([]byte, contentLength)
if _, err = io.ReadFull(r, content); err != nil {
return nil, err
}
return content, nil
}
// readContentLengthHeader looks for the only header field that is supported
// and required:
// Content-Length: [0-9]+\r\n\r\n
// Extracts and returns the content length.
func readContentLengthHeader(r *bufio.Reader) (contentLength int, err error) {
// Look for <some header>\r\n\r\n
headerWithCr, err := r.ReadString('\r')
if err != nil {
return 0, err
}
nextThree := make([]byte, 3)
if _, err = io.ReadFull(r, nextThree); err != nil {
return 0, err
}
if string(nextThree) != "\n\r\n" {
return 0, ErrHeaderDelimiterNotCrLfCrLf
}
// If header is in the right format, get the length
header := strings.TrimSuffix(headerWithCr, "\r")
headerAndLength := contentLengthHeaderRegex.FindStringSubmatch(header)
if len(headerAndLength) < 2 {
return 0, ErrHeaderNotContentLength
}
return strconv.Atoi(headerAndLength[1])
}
// WriteProtocolMessage encodes message and writes it to w.
func WriteProtocolMessage(w io.Writer, message Message) error {
b, err := json.Marshal(message)
if err != nil {
return err
}
return WriteBaseMessage(w, b)
}
// ReadProtocolMessage reads a message from r, decodes and returns it.
func ReadProtocolMessage(r *bufio.Reader) (Message, error) {
content, err := ReadBaseMessage(r)
if err != nil {
return nil, err
}
return DecodeProtocolMessage(content)
}

1589
vendor/github.com/google/go-dap/schematypes.go generated vendored Normal file

File diff suppressed because it is too large Load Diff