From 7c4dd41cb04f83e0a969b9b3e68e97fd2bb150fa Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Thu, 23 Oct 2025 05:31:37 -0400 Subject: [PATCH 1/2] dap: stop sending null to conform to the specification The setBreakpoints response body is defined to be an array of breakpoints but the debug adapter incorrectly serialized an empty array as null in the JSON message. Explicitly initializing the array will force the JSON serialization process to send an empty array back instead of null. Signed-off-by: Remy Suen --- dap/adapter.go | 5 ++++ dap/adapter_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/dap/adapter.go b/dap/adapter.go index ade3139cc..68106654b 100644 --- a/dap/adapter.go +++ b/dap/adapter.go @@ -590,6 +590,11 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi breakpoints = append(breakpoints, bp) } b.byPath[fname] = breakpoints + if breakpoints == nil { + // explicitly initialize breakpoints so that + // we do not send a null back in the JSON if there are no breakpoints + breakpoints = []dap.Breakpoint{} + } return breakpoints } diff --git a/dap/adapter_test.go b/dap/adapter_test.go index 1017849c1..1e990ada9 100644 --- a/dap/adapter_test.go +++ b/dap/adapter_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "path/filepath" "testing" "time" @@ -69,6 +70,70 @@ func TestLaunch(t *testing.T) { eg.Wait() } +// TestSetBreakpoints will test sending a setBreakpoints request with no breakpoints. +// The response should be an empty array instead of null in the JSON. +func TestSetBreakpoints(t *testing.T) { + adapter, conn, client := NewTestAdapter[common.Config](t) + + ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, context.DeadlineExceeded) + defer cancel() + + eg, _ := errgroup.WithContext(ctx) + eg.Go(func() error { + _, err := adapter.Start(ctx, conn) + assert.NoError(t, err) + return nil + }) + + var ( + initialized = make(chan struct{}) + setBreakpoints <-chan *dap.SetBreakpointsResponse + ) + + client.RegisterEvent("initialized", func(em dap.EventMessage) { + setBreakpoints = DoRequest[*dap.SetBreakpointsResponse](t, client, &dap.SetBreakpointsRequest{ + Request: dap.Request{Command: "setBreakpoints"}, + Arguments: dap.SetBreakpointsArguments{ + Source: dap.Source{Name: "Dockerfile", Path: filepath.Join(t.TempDir(), "Dockerfile")}, + Breakpoints: []dap.SourceBreakpoint{}, + }, + }) + close(initialized) + }) + + eg.Go(func() error { + initializeResp := <-DoRequest[*dap.InitializeResponse](t, client, &dap.InitializeRequest{ + Request: dap.Request{Command: "initialize"}, + }) + assert.True(t, initializeResp.Success) + assert.True(t, initializeResp.Body.SupportsConfigurationDoneRequest) + + launchResp := <-DoRequest[*dap.LaunchResponse](t, client, &dap.LaunchRequest{ + Request: dap.Request{Command: "launch"}, + }) + assert.True(t, launchResp.Success) + + // We should have received the initialized event. + select { + case <-initialized: + default: + assert.Fail(t, "did not receive initialized event") + } + + select { + case setBreakpointsResp := <-setBreakpoints: + assert.True(t, setBreakpointsResp.Success) + assert.Len(t, setBreakpointsResp.Body.Breakpoints, 0) + assert.NotNil(t, setBreakpointsResp.Body.Breakpoints, "breakpoints should be an empty array instead of null in the JSON") + case <-time.After(10 * time.Second): + assert.Fail(t, "did not receive setBreakpoints response") + } + return nil + }) + + eg.Wait() +} + func NewTestAdapter[C LaunchConfig](t *testing.T) (*Adapter[C], Conn, *Client) { t.Helper() From 2af3005f52edc62dd3b2e7632a6e5373ac932ef7 Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Thu, 23 Oct 2025 10:44:05 -0400 Subject: [PATCH 2/2] Initialize the breakpoints map in the beginning Signed-off-by: Remy Suen --- dap/adapter.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dap/adapter.go b/dap/adapter.go index 68106654b..12e8bfae6 100644 --- a/dap/adapter.go +++ b/dap/adapter.go @@ -568,6 +568,9 @@ func newBreakpointMap() *breakpointMap { func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoints []dap.Breakpoint) { b.mu.Lock() defer b.mu.Unlock() + // explicitly initialize breakpoints so that + // we do not send a null back in the JSON if there are no breakpoints + breakpoints = []dap.Breakpoint{} prev := b.byPath[fname] for _, sbp := range sbps { @@ -590,11 +593,6 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi breakpoints = append(breakpoints, bp) } b.byPath[fname] = breakpoints - if breakpoints == nil { - // explicitly initialize breakpoints so that - // we do not send a null back in the JSON if there are no breakpoints - breakpoints = []dap.Breakpoint{} - } return breakpoints }