Skip to content

Commit

Permalink
proc: initial stepping with range-over-func support
Browse files Browse the repository at this point in the history
Initial support for stepping in functions that use the new
range-over-func statement in go1.23.
Does not support:

- inlining
- viewing variables of the enclosing function from a range-over-func
  body closure
- the correct way to find the enclosing function from a range-over-func
  body closure (but it should work most of the time).

Updates go-delve#3733
  • Loading branch information
aarzilli committed May 28, 2024
1 parent 40670aa commit bcd63ef
Show file tree
Hide file tree
Showing 6 changed files with 1,204 additions and 156 deletions.
328 changes: 328 additions & 0 deletions _fixtures/rangeoverfunc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package main

// The tests here are adapted from $GOROOT/src/cmd/compile/internal/rangefunc/rangefunc_test.go

import (
"fmt"
)

/*
THESE
LINES
INTENTIONALLY
LEFT
BLANK
*/

func TestTrickyIterAll() {
trickItAll := TrickyIterator{}
i := 0
for _, x := range trickItAll.iterAll([]int{30, 7, 8, 9, 10}) {
i += x
if i >= 36 {
break
}
}

fmt.Println("Got i = ", i)
}

func TestTrickyIterAll2() {
trickItAll := TrickyIterator{}
i := 0
for _, x := range trickItAll.iterAll([]int{42, 47}) {
i += x
}
fmt.Println(i)
}

func TestBreak1() {
var result []int
for _, x := range OfSliceIndex([]int{-1, -2, -4}) {
if x == -4 {
break
}
for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
if y == 3 {
break
}
result = append(result, y)
}
result = append(result, x)
}
fmt.Println(result)
}

func TestBreak2() {
var result []int
outer:
for _, x := range OfSliceIndex([]int{-1, -2, -4}) {
for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
if y == 3 {
break
}
if x == -4 {
break outer
}
result = append(result, y)
}
result = append(result, x)
}
fmt.Println(result)
}

func TestMultiCont0() {
var result []int

W:
for _, w := range OfSliceIndex([]int{1000, 2000}) {
result = append(result, w)
if w == 2000 {
break
}
for _, x := range OfSliceIndex([]int{100, 200, 300, 400}) {
for _, y := range OfSliceIndex([]int{10, 20, 30, 40}) {
result = append(result, y)
for _, z := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
if z&1 == 1 {
continue
}
result = append(result, z)
if z >= 4 {
continue W // modified to be multilevel
}
}
result = append(result, -y) // should never be executed
}
result = append(result, x)
}
}
fmt.Println(result)
}

func TestPanickyIterator1() {
var result []int
defer func() {
r := recover()
fmt.Println("Recovering", r)
}()
for _, z := range PanickyOfSliceIndex([]int{1, 2, 3, 4}) {
result = append(result, z)
if z == 4 {
break
}
}
fmt.Println(result)
}

func TestPanickyIterator2() {
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}) {
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 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 TestLongReturnWrapper() {
TestLongReturn()
fmt.Println("returned")
}

func TestLongReturn() {
for _, x := range OfSliceIndex([]int{1, 2, 3}) {
for _, y := range OfSliceIndex([]int{10, 20, 30}) {
if y == 10 {
return
}
}
fmt.Println(x)
}
}

func TestGotoA1() {
result := []int{}
for _, x := range OfSliceIndex([]int{-1, -4, -5}) {
result = append(result, x)
if x == -4 {
break
}
for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
if y == 3 {
goto A
}
result = append(result, y)
}
A:
result = append(result, x)
}
fmt.Println(result)
}

func TestGotoB1() {
result := []int{}
for _, x := range OfSliceIndex([]int{-1, -2, -3, -4, -5}) {
result = append(result, x)
if x == -4 {
break
}
for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
if y == 3 {
goto B
}
result = append(result, y)
}
result = append(result, x)
}
B:
result = append(result, 999)
fmt.Println(result)
}

func main() {
TestTrickyIterAll()
TestTrickyIterAll2()
TestBreak1()
TestBreak2()
TestMultiCont0()
TestPanickyIterator1()
TestPanickyIterator2()
TestPanickyIteratorWithNewDefer()
TestLongReturnWrapper()
TestGotoA1()
TestGotoB1()
}

type Seq[T any] func(yield func(T) bool)
type Seq2[T1, T2 any] func(yield func(T1, T2) bool)

type TrickyIterator struct {
yield func(int, int) bool
}

func (ti *TrickyIterator) iterEcho(s []int) Seq2[int, int] {
return func(yield func(int, int) bool) {
for i, v := range s {
if !yield(i, v) {
ti.yield = yield
return
}
if ti.yield != nil && !ti.yield(i, v) {
return
}
}
ti.yield = yield
return
}
}

func (ti *TrickyIterator) iterAll(s []int) Seq2[int, int] {
return func(yield func(int, int) bool) {
ti.yield = yield // Save yield for future abuse
for i, v := range s {
if !yield(i, v) {
return
}
}
return
}
}

func (ti *TrickyIterator) iterZero(s []int) Seq2[int, int] {
return func(yield func(int, int) bool) {
ti.yield = yield // Save yield for future abuse
// Don't call it at all, maybe it won't escape
return
}
}

// OfSliceIndex returns a Seq2 over the elements of s. It is equivalent
// to range s.
func OfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, v := range s {
if !yield(i, v) {
return
}
}
return
}
}

// PanickyOfSliceIndex iterates the slice but panics if it exits the loop early
func PanickyOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, v := range s {
if !yield(i, v) {
panic(fmt.Errorf("Panicky iterator panicking"))
}
}
return
}
}

// VeryBadOfSliceIndex is "very bad" because it ignores the return value from yield
// and just keeps on iterating, and also wraps that call in a defer-recover so it can
// keep on trying after the first panic.
func VeryBadOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, v := range s {
func() {
defer func() {
recover()
}()
yield(i, v)
}()
}
return
}
}
Loading

0 comments on commit bcd63ef

Please sign in to comment.