diff --git a/_fixtures/rangeoverfunc.go b/_fixtures/rangeoverfunc.go index e487a985db..cb1cdf6fd8 100644 --- a/_fixtures/rangeoverfunc.go +++ b/_fixtures/rangeoverfunc.go @@ -146,6 +146,33 @@ func TestPanickyIterator2() { } } +func TestPanickyIteratorWithNewDefer() { + var result []int + defer func() { + r := recover() + fmt.Println("Recovering ", r) + }() + for _, x := range OfSliceIndex([]int{100, 200}) { + result = append(result, x) + Y: + // swallows panics and iterates to end BUT `break Y` disables the body, so--> 10, 1, 2 + for _, y := range VeryBadOfSliceIndex([]int{10, 20}) { + defer func() { // This defer will be set on TestPanickyIteratorWithNewDefer from TestPanickyIteratorWithNewDefer-range2 + fmt.Println("y loop defer") + }() + result = append(result, y) + + // converts early exit into a panic --> 1, 2 + for k, z := range PanickyOfSliceIndex([]int{1, 2}) { // iterator panics + result = append(result, z) + if k == 1 { + break Y + } + } + } + } +} + func main() { TestTrickyIterAll() TestTrickyIterAll2() @@ -154,6 +181,7 @@ func main() { TestMultiCont0() TestPanickyIterator1() TestPanickyIterator2() + TestPanickyIteratorWithNewDefer() } type Seq[T any] func(yield func(T) bool) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index cca40b1044..c428124d0c 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -6548,6 +6548,39 @@ func TestRangeOverFuncNext(t *testing.T) { nx(141), // if k == 1 nx(142), // break Y nx(128), // defer func() + nx(129), // r := recover() + nx(130), // fmt.Println + }) + }) + + t.Run("TestPanickyIteratorWithNewDefer", func(t *testing.T) { + testseq2intl(t, fixture, grp, p, nil, []seqTest{ + funcBreak(t, "main.TestPanickyIteratorWithNewDefer"), + {contContinue, 149}, + nx(150), + nx(151), + nx(155), // for _, x := range (x == 100) + nx(155), + nx(156), + nx(157), + nx(159), // for _, y := range (y == 10) + nx(159), + nx(160), + nx(163), // result = append(result, y) + nx(166), // for k, z := range (k == 0, z == 1) + nx(166), + nx(167), // result = append(result, z) + nx(168), // if k == 1 + nx(171), + + nx(166), // for k, z := range (k == 0, z == 1) + nx(167), // result = append(result, z) + nx(168), // if k == 1 + nx(169), // break Y + nx(159), + nx(172), + nx(160), // defer func() + nx(161), // fmt.Println }) }) }) diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 05c6895edd..795f33db78 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -631,6 +631,8 @@ type Defer struct { link *Defer // Next deferred function argSz int64 // Always 0 in Go >=1.17 + rangefunc []*Defer // See explanation in $GOROOT/src/runtime/panic.go, comment to function runtime.deferrangefunc (this is the equivalent of the rangefunc variable and head fields, combined) + variable *Variable Unreadable error } @@ -657,7 +659,7 @@ func (g *G) readDefers(frames []Stackframe) { } if frames[i].TopmostDefer == nil { - frames[i].TopmostDefer = curdefer + frames[i].TopmostDefer = curdefer.topdefer() } if frames[i].SystemStack || curdefer.SP >= uint64(frames[i].Regs.CFA) { @@ -673,13 +675,19 @@ func (g *G) readDefers(frames []Stackframe) { // compared with deferred frames. i++ } else { - frames[i].Defers = append(frames[i].Defers, curdefer) + if len(curdefer.rangefunc) > 0 { + frames[i].Defers = append(frames[i].Defers, curdefer.rangefunc...) + } else { + frames[i].Defers = append(frames[i].Defers, curdefer) + } curdefer = curdefer.Next() } } } -func (d *Defer) load() { +const maxRangeFuncDefers = 10 + +func (d *Defer) load(canrecur bool) { v := d.variable // +rtype _defer v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0}) if v.Unreadable != nil { @@ -714,6 +722,34 @@ func (d *Defer) load() { if linkvar.Addr != 0 { d.link = &Defer{variable: linkvar} } + + if canrecur { + h := v + for _, fieldname := range []string{"head", "u", "value"} { + if h == nil { + return + } + h = h.loadFieldNamed(fieldname) + } + if h != nil { + h := h.newVariable("", h.Addr, pointerTo(linkvar.DwarfType, h.bi.Arch), h.mem).maybeDereference() + if h.Addr != 0 { + hd := &Defer{variable: h} + for { + hd.load(false) + d.rangefunc = append(d.rangefunc, hd) + if hd.link == nil { + break + } + if len(d.rangefunc) > maxRangeFuncDefers { + // We don't have a way to know for sure that we haven't gone completely off-road while loading this list so limit it to an arbitrary maximum size. + break + } + hd = hd.link + } + } + } + } } // errSPDecreased is used when (*Defer).Next detects a corrupted linked @@ -728,13 +764,20 @@ func (d *Defer) Next() *Defer { if d.link == nil { return nil } - d.link.load() + d.link.load(true) if d.link.SP < d.SP { d.link.Unreadable = errSPDecreased } return d.link } +func (d *Defer) topdefer() *Defer { + if len(d.rangefunc) > 0 { + return d.rangefunc[0] + } + return d +} + // EvalScope returns an EvalScope relative to the argument frame of this deferred call. // The argument frame of a deferred call is stored in memory immediately // after the deferred header. diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index e495e32aff..d928460923 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -726,7 +726,7 @@ func next(dbp *Target, stepInto, inlinedStepOut bool) error { if !backward && !topframe.Current.Fn.cu.image.Stripped() { fr := topframe - if len(rangeFrames) != 0 { + if len(rangeFrames) != 0 && !stepInto { fr = rangeFrames[len(rangeFrames)-2] } _, err = setDeferBreakpoint(dbp, text, fr, sameGCond, stepInto) diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 7b26e3c1bd..e092375d4c 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -494,7 +494,7 @@ func (g *G) Defer() *Defer { return nil } d := &Defer{variable: dvar} - d.load() + d.load(true) return d }