Skip to content

Commit e5aff80

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 75c53e3 commit e5aff80

File tree

2 files changed

+78
-14
lines changed

2 files changed

+78
-14
lines changed

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
}
@@ -487,19 +491,24 @@ func (t *thread) setBreakpoints(ctx Context) {
487491
}
488492

489493
func (t *thread) seekNext(ctx Context, from *step, action stepType) (string, *step, map[string]gateway.Reference, error) {
490-
// If we're at the end, return no digest to signal that
491-
// we should conclude debugging.
492-
var target *step
494+
// Determine how we are going to limit the scan for the next step.
495+
var limit func(s *step) *step
493496
switch action {
494497
case stepNext:
495-
target = t.continueDigest(from, from.next)
498+
limit = func(s *step) *step {
499+
return s.next
500+
}
496501
case stepIn:
497-
target = from.in
502+
limit = func(s *step) *step {
503+
return s.in
504+
}
498505
case stepOut:
499-
target = t.continueDigest(from, from.out)
500-
case stepContinue:
501-
target = t.continueDigest(from, nil)
506+
limit = func(s *step) *step {
507+
return s.out
508+
}
502509
}
510+
511+
target := t.continueDigest(from, limit)
503512
return t.seek(ctx, target)
504513
}
505514

@@ -525,8 +534,10 @@ func (t *thread) seek(ctx Context, target *step) (k string, result *step, mounts
525534
return k, result, refs, nil
526535
}
527536

528-
func (t *thread) continueDigest(from, until *step) *step {
529-
if len(t.bps) == 0 && until == nil {
537+
func (t *thread) continueDigest(from *step, limit func(*step) *step) *step {
538+
// First chance to exit early. If there's no function for limiting
539+
// the until step and no breakpoints then just go directly to the end step.
540+
if len(t.bps) == 0 && limit == nil {
530541
return nil
531542
}
532543

@@ -539,6 +550,27 @@ func (t *thread) continueDigest(from, until *step) *step {
539550
return ok
540551
}
541552

553+
// Special case. When we aren't coming from any step we consider
554+
// whether the entrypoint itself is a breakpoint. If it is, we stop
555+
// there. Otherwise, we treat the entrypoint as the from location.
556+
if from == nil {
557+
if isBreakpoint(t.entrypoint.dgst) {
558+
return t.entrypoint
559+
}
560+
from = t.entrypoint
561+
}
562+
563+
var until *step
564+
if limit != nil {
565+
until = limit(from)
566+
}
567+
568+
// Second chance to exit early. If we've fully resolved from and the
569+
// limit function doesn't return an end step, just go directly to the end.
570+
if len(t.bps) == 0 && until == nil {
571+
return nil
572+
}
573+
542574
next := func(s *step) *step {
543575
cur := s.in
544576
for cur != nil && cur != until {

tests/dap_build.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ var dapBuildTests = []func(t *testing.T, sb integration.Sandbox){
7777
testDapBuild,
7878
testDapBuildStopOnEntry,
7979
testDapBuildSetBreakpoints,
80+
testDapBuildEntryBreakpoint,
8081
testDapBuildVerifiedBreakpoints,
8182
testDapBuildStepIn,
8283
testDapBuildStepNext,
@@ -200,6 +201,37 @@ func testDapBuildSetBreakpoints(t *testing.T, sb integration.Sandbox) {
200201
require.NoError(t, done(false))
201202
}
202203

204+
// testDapBuildEntryBreakpoint checks that the entrypoint is a valid breakpoint.
205+
func testDapBuildEntryBreakpoint(t *testing.T, sb integration.Sandbox) {
206+
dir := createTestProject(t)
207+
client, done, err := dapBuildCmd(t, sb, withArgs(dir))
208+
require.NoError(t, err)
209+
210+
interruptCh := pollInterruptEvents(client)
211+
doLaunch(t, client, commands.LaunchConfig{
212+
Dockerfile: path.Join(dir, "Dockerfile"),
213+
ContextPath: dir,
214+
},
215+
dap.SourceBreakpoint{Line: 7},
216+
)
217+
218+
stopped := waitForInterrupt[*dap.StoppedEvent](t, interruptCh)
219+
threads := doThreads(t, client)
220+
require.ElementsMatch(t, []int{stopped.Body.ThreadId}, threads)
221+
222+
stackTraceResp := <-daptest.DoRequest[*dap.StackTraceResponse](t, client, &dap.StackTraceRequest{
223+
Request: dap.Request{Command: "stackTrace"},
224+
Arguments: dap.StackTraceArguments{
225+
ThreadId: stopped.Body.ThreadId,
226+
},
227+
})
228+
require.True(t, stackTraceResp.Success)
229+
require.Len(t, stackTraceResp.Body.StackFrames, 1)
230+
231+
var exitErr *exec.ExitError
232+
require.ErrorAs(t, done(true), &exitErr)
233+
}
234+
203235
func testDapBuildVerifiedBreakpoints(t *testing.T, sb integration.Sandbox) {
204236
dir := createTestProject(t)
205237
client, done, err := dapBuildCmd(t, sb, withArgs(dir))

0 commit comments

Comments
 (0)