Skip to content

Commit c1366e9

Browse files
authored
proc: fix bug with range-over-func stepping (#3778)
Set a breakpoint on the return address of the current function, if it's a range-over-func body, and clear the stepping breakpoints for the current function (except the entry one) when its hit. Without this what can happen is the following: 1. the range-over-func body finishes and returns to the iterator 2. the iterator calls back into the range-over-func body 3. a stepping breakpoint that's inside the prologue gets hit Updates #3733
1 parent 3ae2262 commit c1366e9

File tree

5 files changed

+149
-70
lines changed

5 files changed

+149
-70
lines changed

pkg/proc/breakpoints.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ const (
144144
// goroutine.
145145
StepIntoNewProcBreakpoint
146146

147-
steppingMask = NextBreakpoint | NextDeferBreakpoint | StepBreakpoint | StepIntoNewProcBreakpoint
147+
// NextInactivatedBreakpoint a NextBreakpoint that has been inactivated, see rangeFrameInactivateNextBreakpoints
148+
NextInactivatedBreakpoint
149+
150+
steppingMask = NextBreakpoint | NextDeferBreakpoint | StepBreakpoint | StepIntoNewProcBreakpoint | NextInactivatedBreakpoint
148151
)
149152

150153
// WatchType is the watchpoint type
@@ -221,6 +224,8 @@ func (bp *Breakpoint) VerboseDescr() []string {
221224
r = append(r, "PluginOpenBreakpoint")
222225
case StepIntoNewProcBreakpoint:
223226
r = append(r, "StepIntoNewProcBreakpoint")
227+
case NextInactivatedBreakpoint:
228+
r = append(r, "NextInactivatedBreakpoint")
224229
default:
225230
r = append(r, fmt.Sprintf("Unknown %d", breaklet.Kind))
226231
}
@@ -318,6 +323,9 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
318323
case StackResizeBreakpoint, PluginOpenBreakpoint, StepIntoNewProcBreakpoint:
319324
// no further checks
320325

326+
case NextInactivatedBreakpoint:
327+
active = false
328+
321329
default:
322330
bpstate.CondError = fmt.Errorf("internal error unknown breakpoint kind %v", breaklet.Kind)
323331
}
@@ -834,6 +842,29 @@ func (t *Target) ClearSteppingBreakpoints() error {
834842
return nil
835843
}
836844

845+
func (t *Target) clearInactivatedSteppingBreakpoint() error {
846+
threads := t.ThreadList()
847+
for _, bp := range t.Breakpoints().M {
848+
for i := range bp.Breaklets {
849+
if bp.Breaklets[i].Kind == NextInactivatedBreakpoint {
850+
bp.Breaklets[i] = nil
851+
}
852+
}
853+
cleared, err := t.finishClearBreakpoint(bp)
854+
if err != nil {
855+
return err
856+
}
857+
if cleared {
858+
for _, thread := range threads {
859+
if thread.Breakpoint().Breakpoint == bp {
860+
thread.Breakpoint().Clear()
861+
}
862+
}
863+
}
864+
}
865+
return nil
866+
}
867+
837868
// finishClearBreakpoint clears nil breaklets from the breaklet list of bp
838869
// and if it is empty erases the breakpoint.
839870
// Returns true if the breakpoint was deleted

pkg/proc/eval.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,8 @@ func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variabl
340340
}
341341
}
342342
for i, scope2 := range scope.enclosingRangeScopes {
343-
if i == len(scope.enclosingRangeScopes)-1 {
344-
// Last one is the caller frame, we shouldn't check it
345-
break
346-
}
347343
if scope2 == nil {
348-
scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[i:]...)
344+
scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[2*i:]...)
349345
scope.enclosingRangeScopes[i] = scope2
350346
}
351347
vars, err := scope2.simpleLocals(flags, wantedName)
@@ -381,8 +377,8 @@ func (scope *EvalScope) setupRangeFrames() error {
381377
if err != nil {
382378
return err
383379
}
384-
scope.rangeFrames = scope.rangeFrames[1:]
385-
scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames))
380+
scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame
381+
scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)/2)
386382
return nil
387383
}
388384

pkg/proc/proc_test.go

Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6299,25 +6299,6 @@ func TestRangeOverFuncNext(t *testing.T) {
62996299
return seqTest{contNext, n}
63006300
}
63016301

6302-
nx2 := func(t *testing.T, n int) seqTest {
6303-
return seqTest{contNothing, func(grp *proc.TargetGroup, p *proc.Target) {
6304-
_, ln1 := currentLineNumber(p, t)
6305-
assertNoError(grp.Next(), t, "Next() returned an error")
6306-
f, ln2 := currentLineNumber(p, t)
6307-
if ln2 == n {
6308-
return
6309-
}
6310-
if ln2 != ln1 {
6311-
t.Fatalf("Program did not continue to correct next location (expected %d or %d) was %s:%d", ln1, n, f, ln2)
6312-
}
6313-
assertNoError(grp.Next(), t, "Next() returned an error")
6314-
f, ln2 = currentLineNumber(p, t)
6315-
if ln2 != n {
6316-
t.Fatalf("Program did not continue to correct next location (expected %d) was %s:%d", n, f, ln2)
6317-
}
6318-
}}
6319-
}
6320-
63216302
assertLocals := func(t *testing.T, varnames ...string) seqTest {
63226303
return seqTest{
63236304
contNothing,
@@ -6659,20 +6640,20 @@ func TestRangeOverFuncNext(t *testing.T) {
66596640
nx(118), // if z == 4
66606641
nx(121),
66616642

6662-
nx(116), // for _, z := range (z == 2)
6663-
nx2(t, 117), // result = append(result, z)
6664-
nx(118), // if z == 4
6643+
nx(116), // for _, z := range (z == 2)
6644+
nx(117), // result = append(result, z)
6645+
nx(118), // if z == 4
66656646
nx(121),
66666647

6667-
nx(116), // for _, z := range (z == 3)
6668-
nx2(t, 117), // result = append(result, z)
6669-
nx(118), // if z == 4
6648+
nx(116), // for _, z := range (z == 3)
6649+
nx(117), // result = append(result, z)
6650+
nx(118), // if z == 4
66706651
nx(121),
66716652

6672-
nx(116), // for _, z := range (z == 4)
6673-
nx2(t, 117), // result = append(result, z)
6674-
nx(118), // if z == 4
6675-
nx(119), // break
6653+
nx(116), // for _, z := range (z == 4)
6654+
nx(117), // result = append(result, z)
6655+
nx(118), // if z == 4
6656+
nx(119), // break
66766657

66776658
nx(112), // defer func()
66786659
nx(113), // r := recover()
@@ -6773,14 +6754,14 @@ func TestRangeOverFuncNext(t *testing.T) {
67736754
nx(203), // result = append(result, y)
67746755
nx(204),
67756756

6776-
nx(199), // for _, y := range (y == 2)
6777-
nx2(t, 200), // if y == 3
6778-
nx(203), // result = append(result, y)
6757+
nx(199), // for _, y := range (y == 2)
6758+
nx(200), // if y == 3
6759+
nx(203), // result = append(result, y)
67796760
nx(204),
67806761

6781-
nx(199), // for _, y := range (y == 3)
6782-
nx2(t, 200), // if y == 3
6783-
nx(201), // goto A
6762+
nx(199), // for _, y := range (y == 3)
6763+
nx(200), // if y == 3
6764+
nx(201), // goto A
67846765
nx(204),
67856766
nx(206), // result = append(result, x)
67866767
nx(207),
@@ -6809,14 +6790,14 @@ func TestRangeOverFuncNext(t *testing.T) {
68096790
nx(222), // result = append(result, y)
68106791
nx(223),
68116792

6812-
nx(218), // for _, y := range (y == 2)
6813-
nx2(t, 219), // if y == 3
6814-
nx(222), // result = append(result, y)
6793+
nx(218), // for _, y := range (y == 2)
6794+
nx(219), // if y == 3
6795+
nx(222), // result = append(result, y)
68156796
nx(223),
68166797

6817-
nx(218), // for _, y := range (y == 3)
6818-
nx2(t, 219), // if y == 3
6819-
nx(220), // goto B
6798+
nx(218), // for _, y := range (y == 3)
6799+
nx(219), // if y == 3
6800+
nx(220), // goto B
68206801
nx(223),
68216802
nx(225),
68226803
nx(227), // result = append(result, 999)

pkg/proc/stack.go

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ func (g *G) readDefers(frames []Stackframe) {
690690
frames[i].TopmostDefer = curdefer.topdefer()
691691
}
692692

693-
if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) {
693+
if frames[i].SystemStack || frames[i].Inlined || curdefer.SP >= uint64(frames[i].Regs.CFA) {
694694
// frames[i].Regs.CFA is the value that SP had before the function of
695695
// frames[i] was called.
696696
// This means that when curdefer.SP == frames[i].Regs.CFA then curdefer
@@ -900,8 +900,8 @@ func ruleString(rule *frame.DWRule, regnumToString func(uint64) string) string {
900900

901901
// rangeFuncStackTrace, if the topmost frame of the stack is a the body of a
902902
// range-over-func statement, returns a slice containing the stack of range
903-
// bodies on the stack, the frame of the function containing them and
904-
// finally the function that called it.
903+
// bodies on the stack, interleaved with their return frames, the frame of
904+
// the function containing them and finally the function that called it.
905905
//
906906
// For example, given:
907907
//
@@ -916,9 +916,11 @@ func ruleString(rule *frame.DWRule, regnumToString func(uint64) string) string {
916916
// It will return the following frames:
917917
//
918918
// 0. f-range2()
919-
// 1. f-range1()
920-
// 2. f()
921-
// 3. function that called f()
919+
// 1. function that called f-range2
920+
// 2. f-range1()
921+
// 3. function that called f-range1
922+
// 4. f()
923+
// 5. function that called f()
922924
//
923925
// If the topmost frame of the stack is *not* the body closure of a
924926
// range-over-func statement then nothing is returned.
@@ -931,7 +933,17 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
931933
return nil, err
932934
}
933935
frames := []Stackframe{}
934-
stage := 0
936+
937+
const (
938+
startStage = iota
939+
normalStage
940+
lastFrameStage
941+
doneStage
942+
)
943+
944+
stage := startStage
945+
addRetFrame := false
946+
935947
var rangeParent *Function
936948
nonMonotonicSP := false
937949
var closurePtr int64
@@ -941,6 +953,7 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
941953
if fr.closurePtr != 0 {
942954
closurePtr = fr.closurePtr
943955
}
956+
addRetFrame = true
944957
}
945958

946959
closurePtrOk := func(fr *Stackframe) bool {
@@ -976,33 +989,40 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
976989
}
977990
}
978991

992+
if addRetFrame {
993+
addRetFrame = false
994+
frames = append(frames, fr)
995+
}
996+
979997
switch stage {
980-
case 0:
998+
case startStage:
981999
appendFrame(fr)
9821000
rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent
983-
stage++
1001+
stage = normalStage
9841002
if rangeParent == nil || closurePtr == 0 {
9851003
frames = nil
986-
stage = 3
1004+
addRetFrame = false
1005+
stage = doneStage
9871006
return false
9881007
}
989-
case 1:
1008+
case normalStage:
9901009
if fr.Call.Fn.offset == rangeParent.offset && closurePtrOk(&fr) {
991-
appendFrame(fr)
992-
stage++
1010+
frames = append(frames, fr)
1011+
stage = lastFrameStage
9931012
} else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent && closurePtrOk(&fr) {
9941013
appendFrame(fr)
9951014
if closurePtr == 0 {
9961015
frames = nil
997-
stage = 3
1016+
addRetFrame = false
1017+
stage = doneStage
9981018
return false
9991019
}
10001020
}
1001-
case 2:
1021+
case lastFrameStage:
10021022
frames = append(frames, fr)
1003-
stage++
1023+
stage = doneStage
10041024
return false
1005-
case 3:
1025+
case doneStage:
10061026
return false
10071027
}
10081028
return true
@@ -1013,9 +1033,12 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
10131033
if nonMonotonicSP {
10141034
return nil, errors.New("corrupted stack (SP not monotonically decreasing)")
10151035
}
1016-
if stage != 3 {
1036+
if stage != doneStage {
10171037
return nil, errors.New("could not find range-over-func closure parent on the stack")
10181038
}
1039+
if len(frames)%2 != 0 {
1040+
return nil, errors.New("incomplete range-over-func stacktrace")
1041+
}
10191042
g.readDefers(frames)
10201043
return frames, nil
10211044
}

pkg/proc/target_exec.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ func (grp *TargetGroup) Continue() error {
119119
}
120120
delete(it.Breakpoints().Logical, watchpoint.LogicalID())
121121
}
122+
// Clear inactivated breakpoints
123+
err := it.clearInactivatedSteppingBreakpoint()
124+
if err != nil {
125+
logflags.DebuggerLogger().Errorf("could not clear inactivated stepping breakpoints: %v", err)
126+
}
122127
}
123128

124129
if contOnceErr != nil {
@@ -847,8 +852,10 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
847852

848853
// Set step-out breakpoints for range-over-func body closures
849854
if !stepInto && selg != nil && topframe.Current.Fn.extra(bi).rangeParent != nil && len(rangeFrames) > 0 {
850-
for _, fr := range rangeFrames[:len(rangeFrames)-1] {
851-
retframecond := astutil.And(sameGCond, frameoffCondition(&fr))
855+
// Set step-out breakpoint for every range-over-func body currently on the stack so that we stop on them.
856+
for i := 2; i < len(rangeFrames); i += 2 {
857+
fr := &rangeFrames[i]
858+
retframecond := astutil.And(sameGCond, frameoffCondition(fr))
852859
if !fr.hasInlines {
853860
dbp.SetBreakpoint(0, fr.Current.PC, NextBreakpoint, retframecond)
854861
} else {
@@ -857,7 +864,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
857864
if err != nil {
858865
return err
859866
}
860-
pcs, err = removeInlinedCalls(pcs, &fr, bi)
867+
pcs, err = removeInlinedCalls(pcs, fr, bi)
861868
if err != nil {
862869
return err
863870
}
@@ -866,6 +873,24 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error {
866873
}
867874
}
868875
}
876+
877+
// Set a step-out breakpoint for the first range-over-func body on the
878+
// stack, this breakpoint will never cause a stop because the associated
879+
// callback always returns false.
880+
// Its purpose is to inactivate all the breakpoints for the current
881+
// range-over-func body function so that if the iterator re-calls it we
882+
// don't end up inside the prologue.
883+
if !rangeFrames[0].Inlined {
884+
bp, err := dbp.SetBreakpoint(0, rangeFrames[1].Call.PC, NextBreakpoint, astutil.And(sameGCond, frameoffCondition(&rangeFrames[1])))
885+
if err == nil {
886+
bplet := bp.Breaklets[len(bp.Breaklets)-1]
887+
bplet.callback = func(th Thread, p *Target) (bool, error) {
888+
rangeFrameInactivateNextBreakpoints(p, rangeFrames[0].Call.Fn)
889+
return false, nil
890+
}
891+
}
892+
}
893+
869894
topframe, retframe = rangeFrames[len(rangeFrames)-2], rangeFrames[len(rangeFrames)-1]
870895
}
871896

@@ -1657,3 +1682,26 @@ func (t *Target) handleHardcodedBreakpoints(grp *TargetGroup, trapthread Thread,
16571682
}
16581683
return nil
16591684
}
1685+
1686+
func rangeFrameInactivateNextBreakpoints(p *Target, fn *Function) {
1687+
pc, err := FirstPCAfterPrologue(p, fn, false)
1688+
if err != nil {
1689+
logflags.DebuggerLogger().Errorf("Error inactivating next breakpoints after exiting a range-over-func body: %v", err)
1690+
return
1691+
}
1692+
1693+
for _, bp := range p.Breakpoints().M {
1694+
if bp.Addr < fn.Entry || bp.Addr >= fn.End || bp.Addr == pc {
1695+
continue
1696+
}
1697+
for _, bplet := range bp.Breaklets {
1698+
if bplet.Kind != NextBreakpoint {
1699+
continue
1700+
}
1701+
// We set to NextInactivatedBreakpoint instead of deleting them because
1702+
// we can't delete breakpoints (or breakpointlets) while breakpoint
1703+
// conditions are being evaluated.
1704+
bplet.Kind = NextInactivatedBreakpoint
1705+
}
1706+
}
1707+
}

0 commit comments

Comments
 (0)