Skip to content

Commit 724afbb

Browse files
committed
dap: fix skipped breakpoint when the breakpoint and the entrypoint were the same
We erroneously skipped a breakpoint when that breakpoint was the same as the entrypoint and we did not use stop on entry. This is because we only started evaluating breakpoints after the first step on the entrypoint instead of at the entrypoint. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
1 parent c4b5d67 commit 724afbb

2 files changed

Lines changed: 88 additions & 20 deletions

File tree

dap/thread.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,20 @@ func (t *thread) Evaluate(ctx Context, c gateway.Client, headRef gateway.Referen
7171
}
7272
defer t.reset()
7373

74+
var next *step
7475
action := stepContinue
7576
if cfg.StopOnEntry {
76-
action = stepNext
77+
// If we are stopping on entry, automatically advance to the
78+
// entrypoint.
79+
action, next = stepNext, t.entrypoint
7780
}
7881

7982
var (
8083
k string
8184
refs map[string]gateway.Reference
82-
next = t.entrypoint
8385
err error
8486
)
85-
for next != nil {
87+
for {
8688
event := t.needsDebug(next, action, err)
8789
if event.Reason != "" {
8890
select {
@@ -98,7 +100,9 @@ func (t *thread) Evaluate(ctx Context, c gateway.Client, headRef gateway.Referen
98100
}
99101

100102
t.setBreakpoints(ctx)
101-
k, next, refs, err = t.seekNext(ctx, next, action)
103+
if k, next, refs, err = t.seekNext(ctx, next, action); next == nil {
104+
break
105+
}
102106
}
103107
return nil
104108
}
@@ -509,19 +513,24 @@ func (t *thread) setBreakpoints(ctx Context) {
509513
}
510514

511515
func (t *thread) seekNext(ctx Context, from *step, action stepType) (string, *step, map[string]gateway.Reference, error) {
512-
// If we're at the end, return no digest to signal that
513-
// we should conclude debugging.
514-
var target *step
516+
// Determine how we are going to limit the scan for the next step.
517+
var limit func(s *step) *step
515518
switch action {
516519
case stepNext:
517-
target = t.continueDigest(from, from.next)
520+
limit = func(s *step) *step {
521+
return s.next
522+
}
518523
case stepIn:
519-
target = from.in
524+
limit = func(s *step) *step {
525+
return s.in
526+
}
520527
case stepOut:
521-
target = t.continueDigest(from, from.out)
522-
case stepContinue:
523-
target = t.continueDigest(from, nil)
528+
limit = func(s *step) *step {
529+
return s.out
530+
}
524531
}
532+
533+
target := t.continueDigest(from, limit)
525534
return t.seek(ctx, target)
526535
}
527536

@@ -547,8 +556,10 @@ func (t *thread) seek(ctx Context, target *step) (k string, result *step, mounts
547556
return k, result, refs, nil
548557
}
549558

550-
func (t *thread) continueDigest(from, until *step) *step {
551-
if len(t.bps) == 0 && until == nil {
559+
func (t *thread) continueDigest(from *step, limit func(*step) *step) *step {
560+
// First chance to exit early. If there's no function for limiting
561+
// the until step and no breakpoints then just go directly to the end step.
562+
if len(t.bps) == 0 && limit == nil {
552563
return nil
553564
}
554565

@@ -561,6 +572,27 @@ func (t *thread) continueDigest(from, until *step) *step {
561572
return ok
562573
}
563574

575+
// Special case. When we aren't coming from any step we consider
576+
// whether the entrypoint itself is a breakpoint. If it is, we stop
577+
// there. Otherwise, we treat the entrypoint as the from location.
578+
if from == nil {
579+
if isBreakpoint(t.entrypoint.dgst) {
580+
return t.entrypoint
581+
}
582+
from = t.entrypoint
583+
}
584+
585+
var until *step
586+
if limit != nil {
587+
until = limit(from)
588+
}
589+
590+
// Second chance to exit early. If we've fully resolved from and the
591+
// limit function doesn't return an end step, just go directly to the end.
592+
if len(t.bps) == 0 && until == nil {
593+
return nil
594+
}
595+
564596
next := func(s *step) *step {
565597
cur := s.in
566598
for cur != nil && cur != until {

tests/dap_build.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ var dapBuildTests = []func(t *testing.T, sb integration.Sandbox){
7878
testDapBuild,
7979
testDapBuildStopOnEntry,
8080
testDapBuildSetBreakpoints,
81+
testDapBuildEntryBreakpoint,
8182
testDapBuildVerifiedBreakpoints,
8283
testDapBuildLoadedSource,
8384
testDapBuildStepIn,
@@ -121,17 +122,19 @@ func testDapBuildStopOnEntry(t *testing.T, sb integration.Sandbox) {
121122
})
122123

123124
stopped := waitForInterrupt[*dap.StoppedEvent](t, interruptCh)
125+
require.Equal(t, "step", stopped.Body.Reason)
126+
124127
threads := doThreads(t, client)
125128
require.ElementsMatch(t, []int{stopped.Body.ThreadId}, threads)
126129

127-
stackTraceResp := <-daptest.DoRequest[*dap.StackTraceResponse](t, client, &dap.StackTraceRequest{
128-
Request: dap.Request{Command: "stackTrace"},
129-
Arguments: dap.StackTraceArguments{
130-
ThreadId: stopped.Body.ThreadId,
130+
stackFrames := doStackTrace(t, client, stopped.Body.ThreadId)
131+
assertStackTrace(t, stackFrames, []stackFrameMatcher{
132+
{
133+
SourceName: "Dockerfile",
134+
Line: 7,
135+
Name: `^\[stage-1 .*\] COPY`,
131136
},
132137
})
133-
require.True(t, stackTraceResp.Success)
134-
require.Len(t, stackTraceResp.Body.StackFrames, 1)
135138

136139
var exitErr *exec.ExitError
137140
require.ErrorAs(t, done(true), &exitErr)
@@ -202,6 +205,39 @@ func testDapBuildSetBreakpoints(t *testing.T, sb integration.Sandbox) {
202205
require.NoError(t, done(false))
203206
}
204207

208+
// testDapBuildEntryBreakpoint checks that the entrypoint is a valid breakpoint.
209+
func testDapBuildEntryBreakpoint(t *testing.T, sb integration.Sandbox) {
210+
dir := createTestProject(t)
211+
client, done, err := dapBuildCmd(t, sb, withArgs(dir))
212+
require.NoError(t, err)
213+
214+
interruptCh := pollInterruptEvents(client)
215+
doLaunch(t, client, commands.LaunchConfig{
216+
Dockerfile: path.Join(dir, "Dockerfile"),
217+
ContextPath: dir,
218+
},
219+
dap.SourceBreakpoint{Line: 7},
220+
)
221+
222+
stopped := waitForInterrupt[*dap.StoppedEvent](t, interruptCh)
223+
require.Equal(t, "breakpoint", stopped.Body.Reason)
224+
225+
threads := doThreads(t, client)
226+
require.ElementsMatch(t, []int{stopped.Body.ThreadId}, threads)
227+
228+
stackFrames := doStackTrace(t, client, stopped.Body.ThreadId)
229+
assertStackTrace(t, stackFrames, []stackFrameMatcher{
230+
{
231+
SourceName: "Dockerfile",
232+
Line: 7,
233+
Name: `^\[stage-1 .*\] COPY`,
234+
},
235+
})
236+
237+
var exitErr *exec.ExitError
238+
require.ErrorAs(t, done(true), &exitErr)
239+
}
240+
205241
func testDapBuildVerifiedBreakpoints(t *testing.T, sb integration.Sandbox) {
206242
dir := createTestProject(t)
207243
client, done, err := dapBuildCmd(t, sb, withArgs(dir))

0 commit comments

Comments
 (0)