diff --git a/go.mod b/go.mod index 64c9d1b4..f41ff060 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/cosiner/argv v0.1.0 github.com/creack/pty v1.1.9 github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 - github.com/google/go-dap v0.5.0 + github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987 github.com/hashicorp/golang-lru v0.5.4 github.com/mattn/go-colorable v0.0.9 github.com/mattn/go-isatty v0.0.3 diff --git a/go.sum b/go.sum index 3e6abe01..165d5ab0 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-dap v0.5.0 h1:RMHAVn5xeunBakYk65ggHXttk6qjZVdbmi+xhAoL2wY= -github.com/google/go-dap v0.5.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= +github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987 h1:aghNk+kvabZ5I1OC3cNHWvfZ8svcoDLAGyKYimqyGVk= +github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -185,10 +185,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go index 8378b30b..2d96a643 100644 --- a/service/dap/daptest/client.go +++ b/service/dap/daptest/client.go @@ -184,15 +184,20 @@ func (c *Client) InitializeRequestWithArgs(args dap.InitializeRequestArguments) c.send(request) } +func toRawMessage(in interface{}) json.RawMessage { + out, _ := json.Marshal(in) + return out +} + // LaunchRequest sends a 'launch' request with the specified args. func (c *Client) LaunchRequest(mode, program string, stopOnEntry bool) { request := &dap.LaunchRequest{Request: *c.newRequest("launch")} - request.Arguments = map[string]interface{}{ + request.Arguments = toRawMessage(map[string]interface{}{ "request": "launch", "mode": mode, "program": program, "stopOnEntry": stopOnEntry, - } + }) c.send(request) } @@ -201,7 +206,7 @@ func (c *Client) LaunchRequest(mode, program string, stopOnEntry bool) { // test for values of unexpected types or unspecified values. func (c *Client) LaunchRequestWithArgs(arguments map[string]interface{}) { request := &dap.LaunchRequest{Request: *c.newRequest("launch")} - request.Arguments = arguments + request.Arguments = toRawMessage(arguments) c.send(request) } @@ -209,7 +214,7 @@ func (c *Client) LaunchRequestWithArgs(arguments map[string]interface{}) { // arguments. func (c *Client) AttachRequest(arguments map[string]interface{}) { request := &dap.AttachRequest{Request: *c.newRequest("attach")} - request.Arguments = arguments + request.Arguments = toRawMessage(arguments) c.send(request) } diff --git a/service/dap/server.go b/service/dap/server.go index 6fb982a9..3e90c430 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -150,6 +150,8 @@ type launchAttachArgs struct { } // defaultArgs borrows the defaults for the arguments from the original vscode-go adapter. +// TODO(polinasok): clean up this and its reference (Server.args) +// in favor of default*Config variables defined in types.go. var defaultArgs = launchAttachArgs{ stopOnEntry: false, stackTraceDepth: 50, @@ -218,43 +220,18 @@ func NewServer(config *service.Config) *Server { // If user-specified options are provided via Launch/AttachRequest, // we override the defaults for optional args. -func (s *Server) setLaunchAttachArgs(request dap.LaunchAttachRequest) error { - stop, ok := request.GetArguments()["stopOnEntry"].(bool) - if ok { - s.args.stopOnEntry = stop +func (s *Server) setLaunchAttachArgs(args LaunchAttachCommonConfig) error { + s.args.stopOnEntry = args.StopOnEntry + if depth := args.StackTraceDepth; depth > 0 { + s.args.stackTraceDepth = depth } - depth, ok := request.GetArguments()["stackTraceDepth"].(float64) - if ok && depth > 0 { - s.args.stackTraceDepth = int(depth) - } - globals, ok := request.GetArguments()["showGlobalVariables"].(bool) - if ok { - s.args.showGlobalVariables = globals - } - paths, ok := request.GetArguments()["substitutePath"] - if ok { - typeMismatchError := fmt.Errorf("'substitutePath' attribute '%v' in debug configuration is not a []{'from': string, 'to': string}", paths) - pathsParsed, ok := paths.([]interface{}) - if !ok { - return typeMismatchError - } - clientToServer := make([][2]string, 0, len(pathsParsed)) - serverToClient := make([][2]string, 0, len(pathsParsed)) - for _, arg := range pathsParsed { - pathMapping, ok := arg.(map[string]interface{}) - if !ok { - return typeMismatchError - } - from, ok := pathMapping["from"].(string) - if !ok { - return typeMismatchError - } - to, ok := pathMapping["to"].(string) - if !ok { - return typeMismatchError - } - clientToServer = append(clientToServer, [2]string{from, to}) - serverToClient = append(serverToClient, [2]string{to, from}) + s.args.showGlobalVariables = args.ShowGlobalVariables + if paths := args.SubstitutePath; len(paths) > 0 { + clientToServer := make([][2]string, 0, len(paths)) + serverToClient := make([][2]string, 0, len(paths)) + for _, p := range paths { + clientToServer = append(clientToServer, [2]string{p.From, p.To}) + serverToClient = append(serverToClient, [2]string{p.To, p.From}) } s.args.substitutePathClientToServer = clientToServer s.args.substitutePathServerToClient = serverToClient @@ -766,44 +743,38 @@ func cleanExeName(name string) string { } func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { - // Validate launch request mode - mode, ok := request.Arguments["mode"] - if !ok || mode == "" { - mode = "debug" - } - if !isValidLaunchMode(mode) { + var args = defaultLaunchConfig // narrow copy for initializing non-zero default values + if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil { s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("Unsupported 'mode' value %q in debug configuration.", mode)) + FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - %v", err)) return } + mode := args.Mode + if mode == "" { + mode = "debug" + } + if !isValidLaunchMode(mode) { + s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", mode)) + return + } // TODO(polina): Respond with an error if debug session is in progress? - program, ok := request.Arguments["program"].(string) - if (!ok || program == "") && mode != "replay" { // Only fail on modes requiring a program + program := args.Program + if program == "" && mode != "replay" { // Only fail on modes requiring a program s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", "The program attribute is missing in debug configuration.") return } - backend, ok := request.Arguments["backend"] - if ok { - backendParsed, ok := backend.(string) - if !ok { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("'backend' attribute '%v' in debug configuration is not a string.", backend)) - return - } - s.config.Debugger.Backend = backendParsed + if backend := args.Backend; backend != "" { + s.config.Debugger.Backend = backend } else { s.config.Debugger.Backend = "default" } if mode == "replay" { - traceDirPath, _ := request.Arguments["traceDirPath"].(string) - + traceDirPath := args.TraceDirPath // Validate trace directory if traceDirPath == "" { s.sendErrorResponse(request.Request, @@ -818,17 +789,14 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { } if mode == "core" { - coreFilePath, _ := request.Arguments["coreFilePath"].(string) - + coreFilePath := args.CoreFilePath // Validate core dump path if coreFilePath == "" { - s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", "The 'coreFilePath' attribute is missing in debug configuration.") return } - // Assign the non-empty core file path to debugger configuration. This will // trigger a native core file replay instead of an rr trace replay s.config.Debugger.CoreFile = coreFilePath @@ -839,8 +807,8 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { // Prepare the debug executable filename, build flags and build it if mode == "debug" || mode == "test" { - output, ok := request.Arguments["output"].(string) - if !ok || output == "" { + output := args.Output + if output == "" { output = defaultDebugBinary } output = cleanExeName(output) @@ -850,17 +818,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { return } - buildFlags := "" - buildFlagsArg, ok := request.Arguments["buildFlags"] - if ok { - buildFlags, ok = buildFlagsArg.(string) - if !ok { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("'buildFlags' attribute '%v' in debug configuration is not a string.", buildFlagsArg)) - return - } - } + buildFlags := args.BuildFlags var cmd string var out []byte @@ -890,56 +848,21 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { s.mu.Unlock() } - err := s.setLaunchAttachArgs(request) - if err != nil { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - err.Error()) + if err := s.setLaunchAttachArgs(args.LaunchAttachCommonConfig); err != nil { + s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error()) return } - var targetArgs []string - args, ok := request.Arguments["args"] - if ok { - argsParsed, ok := args.([]interface{}) - if !ok { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("'args' attribute '%v' in debug configuration is not an array.", args)) - return - } - for _, arg := range argsParsed { - argParsed, ok := arg.(string) - if !ok { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("value '%v' in 'args' attribute in debug configuration is not a string.", arg)) - return - } - targetArgs = append(targetArgs, argParsed) - } - } - - s.config.ProcessArgs = append([]string{program}, targetArgs...) + s.config.ProcessArgs = append([]string{program}, args.Args...) s.config.Debugger.WorkingDir = filepath.Dir(program) - - // Set the WorkingDir for this program to the one specified in the request arguments. - wd, ok := request.Arguments["cwd"] - if ok { - wdParsed, ok := wd.(string) - if !ok { - s.sendErrorResponse(request.Request, - FailedToLaunch, "Failed to launch", - fmt.Sprintf("'cwd' attribute '%v' in debug configuration is not a string.", wd)) - return - } - s.config.Debugger.WorkingDir = wdParsed + if args.Cwd != "" { + s.config.Debugger.WorkingDir = args.Cwd } s.log.Debugf("running binary '%s' in '%s'", program, s.config.Debugger.WorkingDir) - if noDebug, ok := request.Arguments["noDebug"].(bool); ok && noDebug { + if args.NoDebug { s.mu.Lock() - cmd, err := s.newNoDebugProcess(program, targetArgs, s.config.Debugger.WorkingDir) + cmd, err := s.newNoDebugProcess(program, args.Args, s.config.Debugger.WorkingDir) s.mu.Unlock() if err != nil { s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error()) @@ -968,6 +891,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { return } + var err error func() { s.mu.Lock() defer s.mu.Unlock() // Make sure to unlock in case of panic that will become internal error @@ -1021,39 +945,6 @@ func (s *Server) stopNoDebugProcess() { s.noDebugProcess = nil } -// Launch debug sessions support the following modes: -// -- [DEFAULT] "debug" - builds and launches debugger for specified program (similar to 'dlv debug') -// Required args: program -// Optional args with default: output, cwd, noDebug -// Optional args: buildFlags, args -// -- "test" - builds and launches debugger for specified test (similar to 'dlv test') -// same args as above -// -- "exec" - launches debugger for precompiled binary (similar to 'dlv exec') -// Required args: program -// Optional args with default: cwd, noDebug -// Optional args: args -// -- replay: skips program build and sets the Debugger.CoreFile property based on the -// Required args: coreFilePath -// Optional args: program, args -// -- core: skips program build and sets the Debugger.CoreFile property based on the -// Required args: program, traceDirPath -// Optional args: args -func isValidLaunchMode(mode interface{}) bool { - switch mode { - case "exec", "debug", "test", "replay", "core": - return true - } - return false -} - -// Attach debug sessions support the following modes: -// -- [DEFAULT] "local" -- attaches debugger to a local running process -// Required args: processId -// TODO(polina): support "remote" mode -func isValidAttachMode(mode interface{}) bool { - return mode == "local" -} - // 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. @@ -1524,46 +1415,40 @@ func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) { // onAttachRequest handles 'attach' request. // This is a mandatory request to support. func (s *Server) onAttachRequest(request *dap.AttachRequest) { - mode, ok := request.Arguments["mode"] - if !ok || mode == "" { - mode = "local" - } - if !isValidAttachMode(mode) { - // TODO(polina): support 'remote' mode that expects a non-nil debugger - s.sendErrorResponse(request.Request, - FailedToAttach, "Failed to attach", - fmt.Sprintf("Unsupported 'mode' value %q in debug configuration", mode)) + var args AttachConfig = defaultAttachConfig // narrow copy for initializing non-zero default values + if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil { + s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", fmt.Sprintf("invalid debug configuration - %v", err)) return } + mode := args.Mode + if mode == "" { + mode = "local" + } + + if !isValidAttachMode(mode) { + s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", + fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode)) + return + } if mode == "local" { - pid, ok := request.Arguments["processId"].(float64) - if !ok || pid == 0 { + if args.ProcessID == 0 { s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", "The 'processId' attribute is missing in debug configuration") return } - s.config.Debugger.AttachPid = int(pid) - err := s.setLaunchAttachArgs(request) - if err != nil { + s.config.Debugger.AttachPid = args.ProcessID + if err := s.setLaunchAttachArgs(args.LaunchAttachCommonConfig); err != nil { s.sendErrorResponse(request.Request, FailedToAttach, "Failed to attach", err.Error()) return } - backend, ok := request.Arguments["backend"] - if ok { - backendParsed, ok := backend.(string) - if !ok { - s.sendErrorResponse(request.Request, - FailedToAttach, "Failed to attach", - fmt.Sprintf("'backend' attribute '%v' in debug configuration is not a string.", backend)) - return - } - s.config.Debugger.Backend = backendParsed + if backend := args.Backend; backend != "" { + s.config.Debugger.Backend = backend } else { s.config.Debugger.Backend = "default" } - + var err error func() { s.mu.Lock() defer s.mu.Unlock() // Make sure to unlock in case of panic that will become internal error diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 6df35c11..080768a9 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -4117,14 +4117,6 @@ func TestLaunchRequestDefaults(t *testing.T) { // writes to default output dir __debug_bin }) }) - - // if noDebug is not a bool, behave as if it is the default value (false). - runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { - runDebugSession(t, client, "launch", func() { - client.LaunchRequestWithArgs(map[string]interface{}{ - "mode": "debug", "program": fixture.Source, "noDebug": "true"}) - }) - }) } // TestLaunchRequestOutputPath verifies that relative output binary path @@ -4428,10 +4420,6 @@ type helperForSetVariable struct { c *daptest.Client } -func (h *helperForSetVariable) expectSetVariableAndStop(ref int, name, value string) { - h.t.Helper() - h.expectSetVariable0(ref, name, value, true) -} func (h *helperForSetVariable) expectSetVariable(ref int, name, value string) { h.t.Helper() h.expectSetVariable0(ref, name, value, false) @@ -4867,7 +4855,7 @@ func TestBadLaunchRequests(t *testing.T) { // Bad "program" client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": 12345}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: The program attribute is missing in debug configuration.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"program\" of type string") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": nil}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), @@ -4880,15 +4868,15 @@ func TestBadLaunchRequests(t *testing.T) { // Bad "mode" client.LaunchRequest("remote", fixture.Path, stopOnEntry) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: Unsupported 'mode' value \"remote\" in debug configuration.") + "Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"remote\"") client.LaunchRequest("notamode", fixture.Path, stopOnEntry) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: Unsupported 'mode' value \"notamode\" in debug configuration.") + "Failed to launch: invalid debug configuration - unsupported 'mode' attribute \"notamode\"") client.LaunchRequestWithArgs(map[string]interface{}{"mode": 12345, "program": fixture.Path}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: Unsupported 'mode' value %!q(float64=12345) in debug configuration.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"mode\" of type string") client.LaunchRequestWithArgs(map[string]interface{}{"mode": ""}) // empty mode defaults to "debug" (not an error) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), @@ -4899,55 +4887,51 @@ func TestBadLaunchRequests(t *testing.T) { "Failed to launch: The program attribute is missing in debug configuration.") // Bad "args" - client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": nil}) + client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": "foobar"}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'args' attribute '' in debug configuration is not an array.") + "Failed to launch: invalid debug configuration - cannot unmarshal string into \"args\" of type []string") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": 12345}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'args' attribute '12345' in debug configuration is not an array.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type []string") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": []int{1, 2}}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: value '1' in 'args' attribute in debug configuration is not a string.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"args\" of type string") // Bad "buildFlags" client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'buildFlags' attribute '123' in debug configuration is not a string.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"buildFlags\" of type string") // Bad "backend" client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": 123}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'backend' attribute '123' in debug configuration is not a string.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"backend\" of type string") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": "foo"}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: could not launch process: unknown backend \"foo\"") - client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": ""}) - checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: could not launch process: unknown backend \"\"") // Bad "substitutePath" client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": 123}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'substitutePath' attribute '123' in debug configuration is not a []{'from': string, 'to': string}") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"substitutePath\" of type {\"from\":string, \"to\":string}") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{123}}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'substitutePath' attribute '[123]' in debug configuration is not a []{'from': string, 'to': string}") + "Failed to launch: invalid debug configuration - cannot use 123 as 'substitutePath' of type {\"from\":string, \"to\":string}") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"to": "path2"}}}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'substitutePath' attribute '[map[to:path2]]' in debug configuration is not a []{'from': string, 'to': string}") + "Failed to launch: invalid debug configuration - 'substitutePath' requires both 'from' and 'to' entries") client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "substitutePath": []interface{}{map[string]interface{}{"from": "path1", "to": 123}}}) checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to launch: 'substitutePath' attribute '[map[from:path1 to:123]]' in debug configuration is not a []{'from': string, 'to': string}") - + "Failed to launch: invalid debug configuration - cannot use {\"from\":\"path1\",\"to\":123} as 'substitutePath' of type {\"from\":string, \"to\":string}") // Bad "cwd" client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "cwd": 123}) checkFailedToLaunchWithMessage(client.ExpectErrorResponse(t), - "Failed to launch: 'cwd' attribute '123' in debug configuration is not a string.") + "Failed to launch: invalid debug configuration - cannot unmarshal number into \"cwd\" of type string") // Skip detailed message checks for potentially different OS-specific errors. client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry) @@ -4960,7 +4944,12 @@ func TestBadLaunchRequests(t *testing.T) { } checkFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) - client.LaunchRequest("" /*debug by default*/, fixture.Path+"_does_not_exist", stopOnEntry) + client.LaunchRequestWithArgs(map[string]interface{}{ + "request": "launch", + /* mode: debug by default*/ + "program": fixture.Path + "_does_not_exist", + "stopOnEntry": stopOnEntry, + }) oe = client.ExpectOutputEvent(t) if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" { t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe) @@ -4989,6 +4978,9 @@ func TestBadLaunchRequests(t *testing.T) { client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"}) checkFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent. + // Bad "noDebug" + client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": "true"}) + checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: invalid debug configuration - cannot unmarshal string into \"noDebug\" of type bool") // Bad "replay" parameters // These errors come from dap layer client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "traceDirPath": ""}) @@ -5057,15 +5049,15 @@ func TestBadAttachRequest(t *testing.T) { // Bad "mode" client.AttachRequest(map[string]interface{}{"mode": "remote"}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: Unsupported 'mode' value \"remote\" in debug configuration") + "Failed to attach: invalid debug configuration - unsupported 'mode' attribute \"remote\"") client.AttachRequest(map[string]interface{}{"mode": "blah blah blah"}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: Unsupported 'mode' value \"blah blah blah\" in debug configuration") + "Failed to attach: invalid debug configuration - unsupported 'mode' attribute \"blah blah blah\"") client.AttachRequest(map[string]interface{}{"mode": 123}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: Unsupported 'mode' value %!q(float64=123) in debug configuration") + "Failed to attach: invalid debug configuration - cannot unmarshal number into \"mode\" of type string") client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), @@ -5090,7 +5082,7 @@ func TestBadAttachRequest(t *testing.T) { client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: The 'processId' attribute is missing in debug configuration") + "Failed to attach: invalid debug configuration - cannot unmarshal string into \"processId\" of type int") client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1}) // The exact message varies on different systems, so skip that check @@ -5116,13 +5108,10 @@ func TestBadAttachRequest(t *testing.T) { // Bad "backend" client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": 123}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: 'backend' attribute '123' in debug configuration is not a string.") + "Failed to attach: invalid debug configuration - cannot unmarshal number into \"backend\" of type string") client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": "foo"}) checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to attach: could not attach to pid 1: unknown backend \"foo\"") - client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "backend": ""}) - checkFailedToAttachWithMessage(client.ExpectInvisibleErrorResponse(t), - "Failed to attach: could not attach to pid 1: unknown backend \"\"") // We failed to attach to the program. Make sure shutdown still works. client.DisconnectRequest() diff --git a/service/dap/types.go b/service/dap/types.go new file mode 100644 index 00000000..5aadf25b --- /dev/null +++ b/service/dap/types.go @@ -0,0 +1,213 @@ +package dap + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Launch debug sessions support the following modes: +// -- [DEFAULT] "debug" - builds and launches debugger for specified program (similar to 'dlv debug') +// Required args: program +// Optional args with default: output, cwd, noDebug +// Optional args: buildFlags, args +// -- "test" - builds and launches debugger for specified test (similar to 'dlv test') +// same args as above +// -- "exec" - launches debugger for precompiled binary (similar to 'dlv exec') +// Required args: program +// Optional args with default: cwd, noDebug +// Optional args: args +// -- "replay" - replays a trace generated by mozilla rr. Mozilla rr must be installed. +// Required args: traceDirPath +// Optional args: args +// -- "core" - examines a core dump (only supports linux and windows core dumps). +// Required args: program, coreFilePath +// Optional args: args +// +// TODO(hyangah): change this to 'validateLaunchMode' that checks +// all the required/optional fields mentioned above. +func isValidLaunchMode(mode string) bool { + switch mode { + case "exec", "debug", "test", "replay", "core": + return true + } + return false +} + +// Attach debug sessions support the following modes: +// -- [DEFAULT] "local" -- attaches debugger to a local running process +// Required args: processID +// TODO(polina): support "remote" mode +func isValidAttachMode(mode string) bool { + return mode == "local" +} + +// Default values for Launch/Attach configs. +// Used to initialize configuration variables before decoding +// arguments in launch/attach requests. +var ( + defaultLaunchAttachCommonConfig = LaunchAttachCommonConfig{ + Backend: "default", + StackTraceDepth: 50, + } + defaultLaunchConfig = LaunchConfig{ + Mode: "debug", + Output: defaultDebugBinary, + LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig, + } + defaultAttachConfig = AttachConfig{ + Mode: "local", + LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig, + } +) + +// LaunchConfig is the collection of launch request attributes recognized by delve DAP implementation. +type LaunchConfig struct { + // Acceptable values are: + // "debug": compiles your program with optimizations disabled, starts and attaches to it. + // "test": compiles your unit test program with optimizations disabled, starts and attaches to it. + // "exec": executes a precompiled binary and begins a debug session. + // "replay": replays an rr trace. + // "core": examines a core dump. + // + // Default is "debug". + Mode string `json:"mode,omitempty"` + + // Required when mode is `debug`, `test`, or `exec`. + // Path to the program folder (or any go file within that folder) + // when in `debug` or `test` mode, and to the pre-built binary file + // to debug in `exec` mode. + // If it is not an absolute path, it will be interpreted as a path + // relative to the working directory of the delve process. + Program string `json:"program,omitempty"` + + // Command line arguments passed to the debugged program. + Args []string `json:"args,omitempty"` + + // Working directory of the program being debugged + // if a non-empty value is specified. If a relative path is provided, + // it will be interpreted as a relative path to the delve's + // working directory. + // + // If not specified or empty, currently the built program's directory will + // be used. + // This is similar to delve's `--wd` flag. + Cwd string `json:"cwd,omitempty"` + + // Build flags, to be passed to the Go compiler. + // It is like delve's `--build-flags`. For example, + // + // "buildFlags": "-tags=integration -mod=vendor -cover -v" + BuildFlags string `json:"buildFlags,omitempty"` + + // Output path for the binary of the debugee. + // Relative path is interpreted as the path relative to + // the delve process's working directory. + // This is deleted after the debug session ends. + // + // FIXIT: the built program's directory is used as the default + // working directory of the debugged program, which means + // the directory of `output` is used as the default working + // directory. This is a bug and needs fix. + Output string `json:"output,omitempty"` + + // NoDebug is used to run the program without debugging. + NoDebug bool `json:"noDebug,omitempty"` + + // TraceDirPath is the trace directory path for replay mode. + // This is required for "replay" mode but unused in other modes. + TraceDirPath string `json:"traceDirPath,omitempty"` + + // CoreFilePath is the core file path for core mode. + // This is required for "core" mode but unused in other modes. + CoreFilePath string `json:"coreFilePath,omitempty"` + + LaunchAttachCommonConfig +} + +// LaunchAttachCommonConfig is the attributes common in both launch/attach requests. +type LaunchAttachCommonConfig struct { + // Automatically stop program after launch or attach. + StopOnEntry bool `json:"stopOnEntry,omitempty"` + + // Backend used by delve. See `dlv backend` for allowed values. + // Default is "default". + Backend string `json:"backend,omitempty"` + + // Maximum depth of stack trace collected from Delve. + // Default is 50. + StackTraceDepth int `json:"stackTraceDepth,omitempty"` + + // Boolean value to indicate whether global package variables + // should be shown in the variables pane or not. + ShowGlobalVariables bool `json:"showGlobalVariables,omitempty"` + + // An array of mappings from a local path (client) to the remote path (debugger). + // This setting is useful when working in a file system with symbolic links, + // running remote debugging, or debugging an executable compiled externally. + // The debug adapter will replace the local path with the remote path in all of the calls. + SubstitutePath []SubstitutePath `json:"substitutePath,omitempty"` +} + +// SubstitutePath defines a mapping from a local path to the remote path. +// Both 'from' and 'to' must be specified and non-empty. +type SubstitutePath struct { + // The local path to be replaced when passing paths to the debugger. + From string `json:"from,omitempty"` + // The remote path to be replaced when passing paths back to the client. + To string `json:"to,omitempty"` +} + +func (m *SubstitutePath) UnmarshalJSON(data []byte) error { + // use custom unmarshal to check if both from/to are set. + type tmpType SubstitutePath + var tmp tmpType + + if err := json.Unmarshal(data, &tmp); err != nil { + if _, ok := err.(*json.UnmarshalTypeError); ok { + return fmt.Errorf(`cannot use %s as 'substitutePath' of type {"from":string, "to":string}`, data) + } + return err + } + if tmp.From == "" || tmp.To == "" { + return errors.New("'substitutePath' requires both 'from' and 'to' entries") + } + *m = SubstitutePath(tmp) + return nil +} + +// AttachConfig is the collection of attach request attributes recognized by delve DAP implementation. +type AttachConfig struct { + // Acceptable values are: + // "local": attaches to the local process with the given ProcessID. + // + // Default is "local". + Mode string `json:"mode"` + + // The numeric ID of the process to be debugged. Required and must not be 0. + ProcessID int `json:"processId,omitempty"` + + LaunchAttachCommonConfig +} + +// unmarshalLaunchAttachArgs wraps unmarshalling of launch/attach request's +// arguments attribute. Upon unmarshal failure, it returns an error massaged +// to be suitable for end-users. +func unmarshalLaunchAttachArgs(input json.RawMessage, config interface{}) error { + if err := json.Unmarshal(input, config); err != nil { + if uerr, ok := err.(*json.UnmarshalTypeError); ok { + // Format json.UnmarshalTypeError error string in our own way. E.g., + // "json: cannot unmarshal number into Go struct field LaunchArgs.substitutePath of type dap.SubstitutePath" + // => "cannot unmarshal number into 'substitutePath' of type {from:string, to:string}" + // "json: cannot unmarshal number into Go struct field LaunchArgs.program of type string" (go1.16) + // => "cannot unmarshal number into 'program' of type string" + typ := uerr.Type.String() + if uerr.Field == "substitutePath" { + typ = `{"from":string, "to":string}` + } + return fmt.Errorf("cannot unmarshal %v into %q of type %v", uerr.Value, uerr.Field, typ) + } + return err + } + return nil +} diff --git a/vendor/github.com/google/go-dap/.travis.yml b/vendor/github.com/google/go-dap/.travis.yml index f793df89..b0212689 100644 --- a/vendor/github.com/google/go-dap/.travis.yml +++ b/vendor/github.com/google/go-dap/.travis.yml @@ -6,6 +6,7 @@ language: go go: - 1.14.x - 1.15.x + - 1.16.x env: global: diff --git a/vendor/github.com/google/go-dap/README.md b/vendor/github.com/google/go-dap/README.md index bfec6168..4e5d11c8 100644 --- a/vendor/github.com/google/go-dap/README.md +++ b/vendor/github.com/google/go-dap/README.md @@ -1,5 +1,6 @@ # go-dap: Go implementation of the Debug Adapter Protocol +[![PkgGoDev](https://pkg.go.dev/badge/github.com/google/go-dap)](https://pkg.go.dev/github.com/google/go-dap) [![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) diff --git a/vendor/github.com/google/go-dap/schematypes.go b/vendor/github.com/google/go-dap/schematypes.go index 219159e9..6397822a 100644 --- a/vendor/github.com/google/go-dap/schematypes.go +++ b/vendor/github.com/google/go-dap/schematypes.go @@ -18,6 +18,8 @@ package dap +import "encoding/json" + // Message is an interface that all DAP message types implement with pointer // receivers. It's not part of the protocol but is used to enforce static // typing in Go code and provide some common accessors. @@ -56,7 +58,7 @@ type EventMessage interface { type LaunchAttachRequest interface { RequestMessage // GetArguments provides access to the Arguments map. - GetArguments() map[string]interface{} + GetArguments() json.RawMessage } // ProtocolMessage: Base class of requests, responses, and events. @@ -492,11 +494,11 @@ func (r *ConfigurationDoneResponse) GetResponse() *Response { return &r.Response type LaunchRequest struct { Request - Arguments map[string]interface{} `json:"arguments"` + Arguments json.RawMessage `json:"arguments"` } -func (r *LaunchRequest) GetRequest() *Request { return &r.Request } -func (r *LaunchRequest) GetArguments() map[string]interface{} { return r.Arguments } +func (r *LaunchRequest) GetRequest() *Request { return &r.Request } +func (r *LaunchRequest) GetArguments() json.RawMessage { return r.Arguments } // LaunchResponse: Response to 'launch' request. This is just an acknowledgement, so no body field is required. type LaunchResponse struct { @@ -510,11 +512,11 @@ func (r *LaunchResponse) GetResponse() *Response { return &r.Response } type AttachRequest struct { Request - Arguments map[string]interface{} `json:"arguments"` + Arguments json.RawMessage `json:"arguments"` } -func (r *AttachRequest) GetRequest() *Request { return &r.Request } -func (r *AttachRequest) GetArguments() map[string]interface{} { return r.Arguments } +func (r *AttachRequest) GetRequest() *Request { return &r.Request } +func (r *AttachRequest) GetArguments() json.RawMessage { return r.Arguments } // AttachResponse: Response to 'attach' request. This is just an acknowledgement, so no body field is required. type AttachResponse struct { diff --git a/vendor/modules.txt b/vendor/modules.txt index 0f5a5e4f..efd02b2f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -13,7 +13,7 @@ github.com/creack/pty # github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 ## explicit github.com/derekparker/trie -# github.com/google/go-dap v0.5.0 +# github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987 ## explicit github.com/google/go-dap # github.com/hashicorp/golang-lru v0.5.4