case "throw":
v.budget -= inlineExtraThrowCost
break opSwitch
- case "panicrangeexit":
+ case "panicrangestate":
cheap = true
}
// Special case for reflect.noescape. It does just type
Inl *Inline
- // funcLitGen and goDeferGen track how many closures have been
- // created in this function for function literals and go/defer
- // wrappers, respectively. Used by closureName for creating unique
- // function names.
- //
+ // RangeParent, if non-nil, is the first non-range body function containing
+ // the closure for the body of a range function.
+ RangeParent *Func
+
+ // funcLitGen, rangeLitGen and goDeferGen track how many closures have been
+ // created in this function for function literals, range-over-func loops,
+ // and go/defer wrappers, respectively. Used by closureName for creating
+ // unique function names.
// Tracking goDeferGen separately avoids wrappers throwing off
// function literal numbering (e.g., runtime/trace_test.TestTraceSymbolize.func11).
- funcLitGen int32
- goDeferGen int32
+ funcLitGen int32
+ rangeLitGen int32
+ goDeferGen int32
Label int32 // largest auto-generated label in this function
// closureName generates a new unique name for a closure within outerfn at pos.
func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym {
+ if outerfn != nil && outerfn.OClosure != nil && outerfn.OClosure.Func.RangeParent != nil {
+ outerfn = outerfn.OClosure.Func.RangeParent
+ }
pkg := types.LocalPkg
outer := "glob."
- var prefix string
+ var prefix string = "."
switch why {
default:
base.FatalfAt(pos, "closureName: bad Op: %v", why)
case OCLOSURE:
if outerfn == nil || outerfn.OClosure == nil {
- prefix = "func"
+ prefix = ".func"
}
+ case ORANGE:
+ prefix = "-range"
case OGO:
- prefix = "gowrap"
+ prefix = ".gowrap"
case ODEFER:
- prefix = "deferwrap"
+ prefix = ".deferwrap"
}
gen := &globClosgen
pkg = outerfn.Sym().Pkg
outer = FuncName(outerfn)
- if why == OCLOSURE {
+ switch why {
+ case OCLOSURE:
gen = &outerfn.funcLitGen
- } else {
+ case ORANGE:
+ gen = &outerfn.rangeLitGen
+ default:
gen = &outerfn.goDeferGen
}
}
}
*gen++
- return pkg.Lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen))
+ return pkg.Lookup(fmt.Sprintf("%s%s%d", outer, prefix, *gen))
}
// NewClosureFunc creates a new Func to represent a function literal
clo.pos = cpos
clo.SetType(typ)
clo.SetTypecheck(1)
+ if why == ORANGE {
+ clo.Func.RangeParent = outerfn
+ if outerfn.OClosure != nil && outerfn.OClosure.Func.RangeParent != nil {
+ clo.Func.RangeParent = outerfn.OClosure.Func.RangeParent
+ }
+ }
fn.OClosure = clo
fn.Nname.Defn = fn
_32bit uintptr // size on 32bit platforms
_64bit uintptr // size on 64bit platforms
}{
- {Func{}, 168, 288},
+ {Func{}, 176, 296},
{Name{}, 96, 168},
}
// checkFiles configures and runs the types2 checker on the given
// parsed source files and then returns the result.
-func checkFiles(m posMap, noders []*noder) (*types2.Package, *types2.Info) {
+// The map result value indicates which closures are generated from the bodies of range function loops.
+func checkFiles(m posMap, noders []*noder) (*types2.Package, *types2.Info, map[*syntax.FuncLit]bool) {
if base.SyntaxErrors() != 0 {
base.ErrorExit()
}
// If we do the rewrite in the back end, like between typecheck and walk,
// then the new implicit closure will not have a unified IR inline body,
// and bodyReaderFor will fail.
- rangefunc.Rewrite(pkg, info, files)
+ rangeInfo := rangefunc.Rewrite(pkg, info, files)
- return pkg, info
+ return pkg, info, rangeInfo
}
// A cycleFinder detects anonymous interface cycles (go.dev/issue/56103).
return false
}
- fn := r.inlClosureFunc(origPos, typ)
+ fn := r.inlClosureFunc(origPos, typ, ir.OCLOSURE)
fn.SetWrapper(true)
clo := fn.OClosure
origPos := r.pos()
sig := r.signature(nil)
r.suppressInlPos--
+ why := ir.OCLOSURE
+ if r.Bool() {
+ why = ir.ORANGE
+ }
- fn := r.inlClosureFunc(origPos, sig)
+ fn := r.inlClosureFunc(origPos, sig, why)
fn.ClosureVars = make([]*ir.Name, 0, r.Len())
for len(fn.ClosureVars) < cap(fn.ClosureVars) {
// inlClosureFunc constructs a new closure function, but correctly
// handles inlining.
-func (r *reader) inlClosureFunc(origPos src.XPos, sig *types.Type) *ir.Func {
+func (r *reader) inlClosureFunc(origPos src.XPos, sig *types.Type, why ir.Op) *ir.Func {
curfn := r.inlCaller
if curfn == nil {
curfn = r.curfn
}
// TODO(mdempsky): Remove hard-coding of typecheck.Target.
- return ir.NewClosureFunc(origPos, r.inlPos(origPos), ir.OCLOSURE, sig, curfn, typecheck.Target)
+ return ir.NewClosureFunc(origPos, r.inlPos(origPos), why, sig, curfn, typecheck.Target)
}
func (r *reader) exprList() []ir.Node {
// writes an export data package stub representing them,
// and returns the result.
func writePkgStub(m posMap, noders []*noder) string {
- pkg, info := checkFiles(m, noders)
+ pkg, info, otherInfo := checkFiles(m, noders)
- pw := newPkgWriter(m, pkg, info)
+ pw := newPkgWriter(m, pkg, info, otherInfo)
pw.collectDecls(noders)
type pkgWriter struct {
pkgbits.PkgEncoder
- m posMap
- curpkg *types2.Package
- info *types2.Info
+ m posMap
+ curpkg *types2.Package
+ info *types2.Info
+ rangeFuncBodyClosures map[*syntax.FuncLit]bool // non-public information, e.g., which functions are closures range function bodies?
// Indices for previously written syntax and types2 things.
// newPkgWriter returns an initialized pkgWriter for the specified
// package.
-func newPkgWriter(m posMap, pkg *types2.Package, info *types2.Info) *pkgWriter {
+func newPkgWriter(m posMap, pkg *types2.Package, info *types2.Info, otherInfo map[*syntax.FuncLit]bool) *pkgWriter {
return &pkgWriter{
PkgEncoder: pkgbits.NewPkgEncoder(base.Debug.SyncFrames),
- m: m,
- curpkg: pkg,
- info: info,
+ m: m,
+ curpkg: pkg,
+ info: info,
+ rangeFuncBodyClosures: otherInfo,
pkgsIdx: make(map[*types2.Package]pkgbits.Index),
objsIdx: make(map[types2.Object]pkgbits.Index),
w.Sync(pkgbits.SyncFuncLit)
w.pos(expr)
w.signature(sig)
+ w.Bool(w.p.rangeFuncBodyClosures[expr])
w.Len(len(closureVars))
for _, cv := range closureVars {
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build goexperiment.rangefunc
-
package rangefunc_test
import (
+ "fmt"
+ "regexp"
"slices"
"testing"
)
+type Seq[T any] func(yield func(T) bool)
type Seq2[T1, T2 any] func(yield func(T1, T2) bool)
-// OfSliceIndex returns a Seq over the elements of s. It is equivalent
+// 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) {
}
}
+// SwallowPanicOfSliceIndex hides panics and converts them to normal return
+func SwallowPanicOfSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
+ return func(yield func(int, T) bool) {
+ for i, v := range s {
+ done := false
+ func() {
+ defer func() {
+ if r := recover(); r != nil {
+ done = true
+ }
+ }()
+ done = !yield(i, v)
+ }()
+ if done {
+ 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
+ }
+}
+
// CooperativeBadOfSliceIndex calls the loop body from a goroutine after
// a ping on a channel, and returns recover()on that same channel.
func CooperativeBadOfSliceIndex[T any, S ~[]T](s S, proceed chan any) Seq2[int, T] {
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
}
}
-// Check wraps the function body passed to iterator forall
+const DONE = 0 // body of loop has exited in a non-panic way
+const READY = 1 // body of loop has not exited yet, is not running
+const PANIC = 2 // body of loop is either currently running, or has panicked
+const EXHAUSTED = 3 // iterator function return, i.e., sequence is "exhausted"
+
+const MISSING_PANIC = 4 // overload "READY" for panic call
+
+// Check2 wraps the function body passed to iterator forall
// in code that ensures that it cannot (successfully) be called
// either after body return false (control flow out of loop) or
// forall itself returns (the iteration is now done).
//
// Note that this can catch errors before the inserted checks.
-func Check[U, V any](forall Seq2[U, V]) Seq2[U, V] {
+func Check2[U, V any](forall Seq2[U, V]) Seq2[U, V] {
return func(body func(U, V) bool) {
- ret := true
+ state := READY
forall(func(u U, v V) bool {
- if !ret {
- panic("Checked iterator access after exit")
+ if state != READY {
+ panic(fail[state])
+ }
+ state = PANIC
+ ret := body(u, v)
+ if ret {
+ state = READY
+ } else {
+ state = DONE
+ }
+ return ret
+ })
+ if state == PANIC {
+ panic(fail[MISSING_PANIC])
+ }
+ state = EXHAUSTED
+ }
+}
+
+func Check[U any](forall Seq[U]) Seq[U] {
+ return func(body func(U) bool) {
+ state := READY
+ forall(func(u U) bool {
+ if state != READY {
+ panic(fail[state])
+ }
+ state = PANIC
+ ret := body(u)
+ if ret {
+ state = READY
+ } else {
+ state = DONE
}
- ret = body(u, v)
return ret
})
- ret = false
+ if state == PANIC {
+ panic(fail[MISSING_PANIC])
+ }
+ state = EXHAUSTED
+ }
+}
+
+func matchError(r any, x string) bool {
+ if r == nil {
+ return false
+ }
+ if x == "" {
+ return true
}
+ if p, ok := r.(errorString); ok {
+ return p.Error() == x
+ }
+ if p, ok := r.(error); ok {
+ e, err := regexp.Compile(x)
+ if err != nil {
+ panic(fmt.Errorf("Bad regexp '%s' passed to matchError", x))
+ }
+ return e.MatchString(p.Error())
+ }
+ return false
+}
+
+func matchErrorHelper(t *testing.T, r any, x string) {
+ if matchError(r, x) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v', expected '%s'", r, x)
+ }
+}
+
+// An errorString represents a runtime error described by a single string.
+type errorString string
+
+func (e errorString) Error() string {
+ return string(e)
+}
+
+const (
+ // RERR_ is for runtime error, and may be regexps/substrings, to simplify use of tests with tools
+ RERR_DONE = "runtime error: range function continued iteration after loop body exit"
+ RERR_PANIC = "runtime error: range function continued iteration after loop body panic"
+ RERR_EXHAUSTED = "runtime error: range function continued iteration after whole loop exit"
+ RERR_MISSING = "runtime error: range function recovered a loop body panic and did not resume panicking"
+
+ // CERR_ is for checked errors in the Check combinator defined above, and should be literal strings
+ CERR_PFX = "checked rangefunc error: "
+ CERR_DONE = CERR_PFX + "loop iteration after body done"
+ CERR_PANIC = CERR_PFX + "loop iteration after panic"
+ CERR_EXHAUSTED = CERR_PFX + "loop iteration after iterator exit"
+ CERR_MISSING = CERR_PFX + "loop iterator swallowed panic"
+)
+
+var fail []error = []error{
+ errorString(CERR_DONE),
+ errorString(CERR_PFX + "loop iterator, unexpected error"),
+ errorString(CERR_PANIC),
+ errorString(CERR_EXHAUSTED),
+ errorString(CERR_MISSING),
}
func TestCheck(t *testing.T) {
i := 0
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
}()
- for _, x := range Check(BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
+ for _, x := range Check2(BadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
i += x
if i > 4*9 {
break
}
proceed <- true
if r := <-proceed; r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
}
}
-func TestCheckCooperativeBadOfSliceIndex(t *testing.T) {
+func TestCooperativeBadOfSliceIndexCheck(t *testing.T) {
i := 0
proceed := make(chan any)
- for _, x := range Check(CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed)) {
+ for _, x := range Check2(CooperativeBadOfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, proceed)) {
i += x
if i >= 36 {
break
}
proceed <- true
if r := <-proceed; r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+
} else {
t.Error("Wanted to see a failure")
}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
trickItZero.fail()
}
-func TestCheckTrickyIterZero(t *testing.T) {
+func TestTrickyIterZeroCheck(t *testing.T) {
trickItZero := TrickyIterator{}
i := 0
- for _, x := range Check(trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
+ for _, x := range Check2(trickItZero.iterZero([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})) {
i += x
if i >= 36 {
break
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, CERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
} else {
t.Error("Wanted to see a failure")
}
trickItZero.fail()
}
+func TestTrickyIterEcho(t *testing.T) {
+ trickItAll := TrickyIterator{}
+ i := 0
+ for _, x := range trickItAll.iterAll([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("first loop i=%d", i)
+ i += x
+ if i >= 10 {
+ break
+ }
+ }
+
+ if i != 10 {
+ t.Errorf("Expected i == 10, saw %d instead", i)
+ } else {
+ t.Logf("i = %d", i)
+ }
+
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Error("Wanted to see a failure")
+ }
+ }()
+
+ i = 0
+ for _, x := range trickItAll.iterEcho([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("second loop i=%d", i)
+ if x >= 5 {
+ break
+ }
+ }
+
+}
+
+func TestTrickyIterEcho2(t *testing.T) {
+ trickItAll := TrickyIterator{}
+ var i int
+
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_EXHAUSTED) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Error("Wanted to see a failure")
+ }
+ }()
+
+ for k := range 2 {
+ i = 0
+ for _, x := range trickItAll.iterEcho([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ t.Logf("k,x,i=%d,%d,%d", k, x, i)
+ i += x
+ if i >= 10 {
+ break
+ }
+ }
+ t.Logf("i = %d", i)
+
+ if i != 10 {
+ t.Errorf("Expected i == 10, saw %d instead", i)
+ }
+ }
+}
+
// TestBreak1 should just work, with well-behaved iterators.
// (The misbehaving iterator detector should not trigger.)
func TestBreak1(t *testing.T) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
var expect = []int{1000, 10, 2, 4}
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("Expected %v, got %v", expect, result)
}
}
}
+func TestPanickyIterator1(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3, 4}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, "Panicky iterator panicking") {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, z := range PanickyOfSliceIndex([]int{1, 2, 3, 4}) {
+ result = append(result, z)
+ if z == 4 {
+ break
+ }
+ }
+}
+
+func TestPanickyIterator1Check(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3, 4}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, "Panicky iterator panicking") {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ }()
+ for _, z := range Check2(PanickyOfSliceIndex([]int{1, 2, 3, 4})) {
+ result = append(result, z)
+ if z == 4 {
+ break
+ }
+ }
+}
+
+func TestPanickyIterator2(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ 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 TestPanickyIterator2Check(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result was %v", result)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(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 Check2(VeryBadOfSliceIndex([]int{10, 20})) {
+ result = append(result, y)
+
+ // converts early exit into a panic --> 1, 2
+ for k, z := range Check2(PanickyOfSliceIndex([]int{1, 2})) { // iterator panics
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+
+func TestPanickyIterator3(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2, 200, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ t.Errorf("Unexpected panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ 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
+ // This is cross-checked against the checked iterator below; the combinator should behave the same.
+ for _, y := range VeryBadOfSliceIndex([]int{10, 20}) {
+ result = append(result, y)
+
+ for k, z := range OfSliceIndex([]int{1, 2}) { // iterator does not panic
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+func TestPanickyIterator3Check(t *testing.T) {
+ var result []int
+ var expect = []int{100, 10, 1, 2, 200, 10, 1, 2}
+ defer func() {
+ if r := recover(); r != nil {
+ t.Errorf("Unexpected panic '%v'", r)
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(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 Check2(VeryBadOfSliceIndex([]int{10, 20})) {
+ result = append(result, y)
+
+ for k, z := range Check2(OfSliceIndex([]int{1, 2})) { // iterator does not panic
+ result = append(result, z)
+ if k == 1 {
+ break Y
+ }
+ }
+ }
+ }
+}
+
+func TestPanickyIterator4(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range SwallowPanicOfSliceIndex([]int{1, 2, 3, 4}) {
+ result = append(result, x)
+ if x == 3 {
+ panic("x is 3")
+ }
+ }
+
+}
+func TestPanickyIterator4Check(t *testing.T) {
+ var result []int
+ var expect = []int{1, 2, 3}
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_MISSING) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ }
+ if !slices.Equal(expect, result) {
+ t.Errorf("Expected %v, got %v", expect, result)
+ }
+ }()
+ for _, x := range Check2(SwallowPanicOfSliceIndex([]int{1, 2, 3, 4})) {
+ result = append(result, x)
+ if x == 3 {
+ panic("x is 3")
+ }
+ }
+
+}
+
// veryBad tests that a loop nest behaves sensibly in the face of a
// "very bad" iterator. In this case, "sensibly" means that the
// break out of X still occurs after the very bad iterator finally
return result
}
-// checkVeryBad wraps a "very bad" iterator with Check,
+// veryBadCheck wraps a "very bad" iterator with Check,
// demonstrating that the very bad iterator also hides panics
// thrown by Check.
-func checkVeryBad(s []int) []int {
+func veryBadCheck(s []int) []int {
var result []int
X:
for _, x := range OfSliceIndex([]int{1, 2, 3}) {
result = append(result, x)
- for _, y := range Check(VeryBadOfSliceIndex(s)) {
+ for _, y := range Check2(VeryBadOfSliceIndex(s)) {
result = append(result, y)
break X
}
// TestCheckVeryBad checks the behavior of an extremely poorly behaved iterator,
// which also suppresses the exceptions from "Check"
-func TestCheckVeryBad(t *testing.T) {
- result := checkVeryBad([]int{10, 20, 30, 40}) // even length
+func TestVeryBadCheck(t *testing.T) {
+ result := veryBadCheck([]int{10, 20, 30, 40}) // even length
expect := []int{1, 10}
if !slices.Equal(expect, result) {
defer func() {
if r := recover(); r != nil {
- t.Logf("Saw expected panic '%v'", r)
+ if matchError(r, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
if !slices.Equal(expect, result) {
t.Errorf("(Inner) Expected %v, got %v", expect, result)
}
return
}
+// testReturn4 has no bad iterators, but exercises return variable rewriting
+// differs from testReturn1 because deferred append to "result" does not change
+// the return value in this case.
+func testReturn4(t *testing.T) (_ []int, _ []int, err any) {
+ var result []int
+ defer func() {
+ err = recover()
+ }()
+ for _, x := range OfSliceIndex([]int{-1, -2, -3, -4, -5}) {
+ result = append(result, x)
+ if x == -4 {
+ break
+ }
+ defer func() {
+ result = append(result, x*10)
+ }()
+ for _, y := range OfSliceIndex([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ if y == 3 {
+ return result, result, nil
+ }
+ result = append(result, y)
+ }
+ result = append(result, x)
+ }
+ return
+}
+
// TestReturns checks that returns through bad iterators behave properly,
// for inner and outer bad iterators.
func TestReturns(t *testing.T) {
var result []int
+ var result2 []int
var expect = []int{-1, 1, 2, -10}
+ var expect2 = []int{-1, 1, 2}
var err any
result, err = testReturn1(t)
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testReturn3(t)
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
+ result, result2, err = testReturn4(t)
+ if !slices.Equal(expect2, result) {
+ t.Errorf("Expected %v, got %v", expect2, result)
+ }
+ if !slices.Equal(expect2, result2) {
+ t.Errorf("Expected %v, got %v", expect2, result2)
+ }
+ if err != nil {
+ t.Errorf("Unexpected error %v", err)
+ }
}
// testGotoA1 tests loop-nest-internal goto, no bad iterators.
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testGotoA3(t)
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
}
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ if matchError(err, RERR_DONE) {
+ t.Logf("Saw expected panic '%v'", err)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", err)
+ }
}
result, err = testGotoB3(t)
if err == nil {
t.Errorf("Missing expected error")
} else {
- t.Logf("Saw expected panic '%v'", err)
+ matchErrorHelper(t, err, RERR_DONE)
+ }
+}
+
+// once returns an iterator that runs its loop body once with the supplied value
+func once[T any](x T) Seq[T] {
+ return func(yield func(T) bool) {
+ yield(x)
+ }
+}
+
+// terrify converts an iterator into one that panics with the supplied string
+// if/when the loop body terminates early (returns false, for break, goto, outer
+// continue, or return).
+func terrify[T any](s string, forall Seq[T]) Seq[T] {
+ return func(yield func(T) bool) {
+ forall(func(v T) bool {
+ if !yield(v) {
+ panic(s)
+ }
+ return true
+ })
+ }
+}
+
+func use[T any](T) {
+}
+
+// f runs a not-rangefunc iterator that recovers from a panic that follows execution of a return.
+// what does f return?
+func f() string {
+ defer func() { recover() }()
+ defer panic("f panic")
+ for _, s := range []string{"f return"} {
+ return s
+ }
+ return "f not reached"
+}
+
+// g runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// what does g return?
+func g() string {
+ defer func() { recover() }()
+ for s := range terrify("g panic", once("g return")) {
+ return s
+ }
+ return "g not reached"
+}
+
+// h runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// the panic occurs in the rangefunc iterator itself.
+// what does h return?
+func h() (hashS string) {
+ defer func() { recover() }()
+ for s := range terrify("h panic", once("h return")) {
+ hashS := s
+ use(hashS)
+ return s
+ }
+ return "h not reached"
+}
+
+func j() (hashS string) {
+ defer func() { recover() }()
+ for s := range terrify("j panic", once("j return")) {
+ hashS = s
+ return
+ }
+ return "j not reached"
+}
+
+// k runs a rangefunc iterator that recovers from a panic that follows execution of a return.
+// the panic occurs in the rangefunc iterator itself.
+// k includes an additional mechanism to for making the return happen
+// what does k return?
+func k() (hashS string) {
+ _return := func(s string) { hashS = s }
+
+ defer func() { recover() }()
+ for s := range terrify("k panic", once("k return")) {
+ _return(s)
+ return
+ }
+ return "k not reached"
+}
+
+func m() (hashS string) {
+ _return := func(s string) { hashS = s }
+
+ defer func() { recover() }()
+ for s := range terrify("m panic", once("m return")) {
+ defer _return(s)
+ return s + ", but should be replaced in a defer"
+ }
+ return "m not reached"
+}
+
+func n() string {
+ defer func() { recover() }()
+ for s := range terrify("n panic", once("n return")) {
+ return s + func(s string) string {
+ defer func() { recover() }()
+ for s := range terrify("n closure panic", once(s)) {
+ return s
+ }
+ return "n closure not reached"
+ }(" and n closure return")
+ }
+ return "n not reached"
+}
+
+type terrifyTestCase struct {
+ f func() string
+ e string
+}
+
+func TestPanicReturns(t *testing.T) {
+ tcs := []terrifyTestCase{
+ {f, "f return"},
+ {g, "g return"},
+ {h, "h return"},
+ {k, "k return"},
+ {j, "j return"},
+ {m, "m return"},
+ {n, "n return and n closure return"},
+ }
+
+ for _, tc := range tcs {
+ got := tc.f()
+ if got != tc.e {
+ t.Errorf("Got %s expected %s", got, tc.e)
+ } else {
+ t.Logf("Got expected %s", got)
+ }
+ }
+}
+
+// twice calls yield twice, the first time defer-recover-saving any panic,
+// for re-panicking later if the second call to yield does not also panic.
+// If the first call panicked, the second call ought to also panic because
+// it was called after a panic-termination of the loop body.
+func twice[T any](x, y T) Seq[T] {
+ return func(yield func(T) bool) {
+ var p any
+ done := false
+ func() {
+ defer func() {
+ p = recover()
+ }()
+ done = !yield(x)
+ }()
+ if done {
+ return
+ }
+ yield(y)
+ if p != nil {
+ // do not swallow the panic
+ panic(p)
+ }
+ }
+}
+
+func TestRunBodyAfterPanic(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, RERR_PANIC) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result")
+ }
+ }()
+ for x := range twice(0, 1) {
+ if x == 0 {
+ panic("x is zero")
+ }
+ }
+}
+
+func TestRunBodyAfterPanicCheck(t *testing.T) {
+ defer func() {
+ if r := recover(); r != nil {
+ if matchError(r, CERR_PANIC) {
+ t.Logf("Saw expected panic '%v'", r)
+ } else {
+ t.Errorf("Saw wrong panic '%v'", r)
+ }
+ } else {
+ t.Errorf("Wanted to see a failure, result")
+ }
+ }()
+ for x := range Check(twice(0, 1)) {
+ if x == 0 {
+ panic("x is zero")
+ }
+ }
+}
+
+func TestTwoLevelReturn(t *testing.T) {
+ f := func() int {
+ for a := range twice(0, 1) {
+ for b := range twice(0, 2) {
+ x := a + b
+ t.Logf("x=%d", x)
+ if x == 3 {
+ return x
+ }
+ }
+ }
+ return -1
+ }
+ y := f()
+ if y != 3 {
+ t.Errorf("Expected y=3, got y=%d\n", y)
+ }
+}
+
+func TestTwoLevelReturnCheck(t *testing.T) {
+ f := func() int {
+ for a := range Check(twice(0, 1)) {
+ for b := range Check(twice(0, 2)) {
+ x := a + b
+ t.Logf("a=%d, b=%d, x=%d", a, b, x)
+ if x == 3 {
+ return x
+ }
+ }
+ }
+ return -1
+ }
+ y := f()
+ if y != 3 {
+ t.Errorf("Expected y=3, got y=%d\n", y)
}
}
which causes the return we want.
-Return with arguments is more involved. We need somewhere to store the
-arguments while we break out of f, so we add them to the var
-declaration, like:
-
- {
- var (
- #next int
- #r1 type1
- #r2 type2
- )
- f(func(x T1) bool {
- ...
- {
- // return a, b
- #r1, #r2 = a, b
- #next = -2
- return false
- }
- ...
- return true
- })
- if #next == -2 { return #r1, #r2 }
- }
-
-TODO: What about:
-
- func f() (x bool) {
- for range g(&x) {
- return true
- }
- }
-
- func g(p *bool) func(func() bool) {
- return func(yield func() bool) {
- yield()
- // Is *p true or false here?
+Return with arguments is more involved, and has to deal with
+corner cases involving panic, defer, and recover. The results
+of the enclosing function or closure are rewritten to give them
+names if they don't have them already, and the names are assigned
+at the return site.
+
+ func foo() (#rv1 A, #rv2 B) {
+
+ {
+ var (
+ #next int
+ )
+ f(func(x T1) bool {
+ ...
+ {
+ // return a, b
+ #rv1, #rv2 = a, b
+ #next = -1
+ return false
+ }
+ ...
+ return true
+ })
+ if #next == -1 { return }
}
- }
-
-With this rewrite the "return true" is not visible after yield returns,
-but maybe it should be?
# Checking
it does not call the loop body again after it has returned false or
after the entire loop has exited (it might retain a copy of the body
function, or pass it to another goroutine) -- each generated loop has
-its own #exitK flag that is checked before each iteration, and set both
-at any early exit and after the iteration completes.
+its own #stateK variable that is used to check for permitted call
+patterns to the yield function for a loop body.
+
+The state values are:
+
+const DONE = 0 // body of loop has exited in a non-panic way
+const READY = 1 // body of loop has not exited yet, is not running
+const PANIC = 2 // body of loop is either currently running, or has panicked
+const EXHAUSTED = 3 // iterator function call, e.g. f(func(x t){...}), returned so the sequence is "exhausted".
+
+const MISSING_PANIC = 4 // used to report errors.
+
+The value of #stateK transitions
+(1) before calling the iterator function,
+
+ var #stateN = READY
+
+(2) after the iterator function call returns,
+
+ if #stateN == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #stateN = EXHAUSTED
+
+(3) at the beginning of the iteration of the loop body,
+
+ if #stateN != READY { runtime.panicrangestate(#stateN) }
+ #stateN = PANIC
+
+(4) when loop iteration continues, and
+
+ #stateN = READY
+ [return true]
+
+(5) when control flow exits the loop body.
+
+ #stateN = DONE
+ [return false]
For example:
becomes
- {
- var #exit1 bool
- f(func(x T1) bool {
- if #exit1 { runtime.panicrangeexit() }
- ...
- if ... { #exit1 = true ; return false }
- ...
- return true
- })
- #exit1 = true
- }
+ {
+ var #state1 = READY
+ f(func(x T1) bool {
+ if #state1 != READY { runtime.panicrangestate(#state1) }
+ #state1 = PANIC
+ ...
+ if ... { #state1 = DONE ; return false }
+ ...
+ #state1 = READY
+ return true
+ })
+ if #state1 == PANIC {
+ // the code for the loop body did not return normally
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state1 = EXHAUSTED
+ }
# Nested Loops
{
var (
#next int
- #r1 type1
- #r2 type2
)
- var #exit1 bool
- f(func() {
- if #exit1 { runtime.panicrangeexit() }
- var #exit2 bool
- g(func() {
- if #exit2 { runtime.panicrangeexit() }
+ var #state1 = READY
+ f(func() bool {
+ if #state1 != READY { runtime.panicrangestate(#state1) }
+ #state1 = PANIC
+ var #state2 = READY
+ g(func() bool {
+ if #state2 != READY { runtime.panicrangestate(#state2) }
...
{
// return a, b
- #r1, #r2 = a, b
- #next = -2
- #exit1, #exit2 = true, true
+ #rv1, #rv2 = a, b
+ #next = -1
+ #state2 = DONE
return false
}
...
+ #state2 = READY
return true
})
- #exit2 = true
+ if #state2 == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state2 = EXHAUSTED
if #next < 0 {
+ #state1 = DONE
return false
}
+ #state1 = READY
return true
})
- #exit1 = true
- if #next == -2 {
- return #r1, #r2
+ if #state1 == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state1 = EXHAUSTED
+ if #next == -1 {
+ return
}
}
-Note that the #next < 0 after the inner loop handles both kinds of
-return with a single check.
-
# Labeled break/continue of range-over-func loops
For a labeled break or continue of an outer range-over-func, we
-use positive #next values. Any such labeled break or continue
+use positive #next values.
+
+Any such labeled break or continue
really means "do N breaks" or "do N breaks and 1 continue".
-We encode that as perLoopStep*N or perLoopStep*N+1 respectively.
+
+The positive #next value tells which level of loop N to target
+with a break or continue, where perLoopStep*N means break out of
+level N and perLoopStep*N-1 means continue into level N. The
+outermost loop has level 1, therefore #next == perLoopStep means
+to break from the outermost loop, and #next == perLoopStep-1 means
+to continue the outermost loop.
Loops that might need to propagate a labeled break or continue
add one or both of these to the #next checks:
- if #next >= 2 {
- #next -= 2
- return false
- }
-
- if #next == 1 {
- #next = 0
- return true
- }
+ // N == depth of this loop, one less than the one just exited.
+ if #next != 0 {
+ if #next >= perLoopStep*N-1 { // break or continue this loop
+ if #next >= perLoopStep*N+1 { // error checking
+ // TODO reason about what exactly can appear
+ // here given full or partial checking.
+ runtime.panicrangestate(DONE)
+ }
+ rv := #next & 1 == 1 // code generates into #next&1
+ #next = 0
+ return rv
+ }
+ return false // or handle returns and gotos
+ }
-For example
+For example (with perLoopStep == 2)
- F: for range f {
- for range g {
+ F: for range f { // 1, 2
+ for range g { // 3, 4
for range h {
...
break F
{
var #next int
- var #exit1 bool
- f(func() {
- if #exit1 { runtime.panicrangeexit() }
- var #exit2 bool
- g(func() {
- if #exit2 { runtime.panicrangeexit() }
- var #exit3 bool
- h(func() {
- if #exit3 { runtime.panicrangeexit() }
+ var #state1 = READY
+ f(func() { // 1,2
+ if #state1 != READY { runtime.panicrangestate(#state1) }
+ #state1 = PANIC
+ var #state2 = READY
+ g(func() { // 3,4
+ if #state2 != READY { runtime.panicrangestate(#state2) }
+ #state2 = PANIC
+ var #state3 = READY
+ h(func() { // 5,6
+ if #state3 != READY { runtime.panicrangestate(#state3) }
+ #state3 = PANIC
...
{
// break F
- #next = 4
- #exit1, #exit2, #exit3 = true, true, true
+ #next = 2
+ #state3 = DONE
return false
}
...
{
// continue F
- #next = 3
- #exit2, #exit3 = true, true
+ #next = 1
+ #state3 = DONE
return false
}
...
+ #state3 = READY
return true
})
- #exit3 = true
- if #next >= 2 {
- #next -= 2
+ if #state3 == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state3 = EXHAUSTED
+ if #next != 0 {
+ // no breaks or continues targeting this loop
+ #state2 = DONE
return false
}
return true
})
- #exit2 = true
- if #next >= 2 {
- #next -= 2
+ if #state2 == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state2 = EXHAUSTED
+ if #next != 0 { // just exited g, test for break/continue applied to f/F
+ if #next >= 1 {
+ if #next >= 3 { runtime.panicrangestate(DONE) } // error
+ rv := #next&1 == 1
+ #next = 0
+ return rv
+ }
+ #state1 = DONE
return false
}
- if #next == 1 {
- #next = 0
- return true
- }
...
return true
})
- #exit1 = true
+ if #state1 == PANIC {
+ panic(runtime.panicrangestate(MISSING_PANIC))
+ }
+ #state1 = EXHAUSTED
}
Note that the post-h checks only consider a break,
# Gotos and other labeled break/continue
The final control flow translations are goto and break/continue of a
-non-range-over-func statement. In both cases, we may need to break out
-of one or more range-over-func loops before we can do the actual
+non-range-over-func statement. In both cases, we may need to break
+out of one or more range-over-func loops before we can do the actual
control flow statement. Each such break/continue/goto L statement is
-assigned a unique negative #next value (below -2, since -1 and -2 are
-for the two kinds of return). Then the post-checks for a given loop
-test for the specific codes that refer to labels directly targetable
-from that block. Otherwise, the generic
+assigned a unique negative #next value (since -1 is return). Then
+the post-checks for a given loop test for the specific codes that
+refer to labels directly targetable from that block. Otherwise, the
+generic
if #next < 0 { return false }
Top: print("start\n")
{
var #next int
- var #exit1 bool
+ var #state1 = READY
f(func() {
- if #exit1 { runtime.panicrangeexit() }
- var #exit2 bool
+ if #state1 != READY{ runtime.panicrangestate(#state1) }
+ #state1 = PANIC
+ var #state2 = READY
g(func() {
- if #exit2 { runtime.panicrangeexit() }
+ if #state2 != READY { runtime.panicrangestate(#state2) }
+ #state2 = PANIC
...
- var #exit3 bool
+ var #state3 bool = READY
h(func() {
- if #exit3 { runtime.panicrangeexit() }
+ if #state3 != READY { runtime.panicrangestate(#state3) }
+ #state3 = PANIC
...
{
// goto Top
#next = -3
- #exit1, #exit2, #exit3 = true, true, true
+ #state3 = DONE
return false
}
...
+ #state3 = READY
return true
})
- #exit3 = true
if #next < 0 {
+ #state2 = DONE
return false
}
+ #state2 = READY
return true
})
- #exit2 = true
+ if #state2 == PANIC {runtime.panicrangestate(MISSING_PANIC)}
+ #state2 = EXHAUSTED
if #next < 0 {
+ #state1 = DONE
return false
}
+ #state1 = READY
return true
})
- #exit1 = true
+ if #state1 == PANIC {runtime.panicrangestate(MISSING_PANIC)}
+ #state1 = EXHAUSTED
if #next == -3 {
#next = 0
goto Top
type rewriter struct {
pkg *types2.Package
info *types2.Info
+ sig *types2.Signature
outer *syntax.FuncType
body *syntax.BlockStmt
rewritten map[*syntax.ForStmt]syntax.Stmt
// Declared variables in generated code for outermost loop.
- declStmt *syntax.DeclStmt
- nextVar types2.Object
- retVars []types2.Object
- defers types2.Object
- exitVarCount int // exitvars are referenced from their respective loops
+ declStmt *syntax.DeclStmt
+ nextVar types2.Object
+ retVars []types2.Object
+ defers types2.Object
+ stateVarCount int // stateVars are referenced from their respective loops
+
+ rangefuncBodyClosures map[*syntax.FuncLit]bool
}
// A branch is a single labeled branch.
// A forLoop describes a single range-over-func loop being processed.
type forLoop struct {
nfor *syntax.ForStmt // actual syntax
- exitFlag *types2.Var // #exit variable for this loop
- exitFlagDecl *syntax.VarDecl
+ stateVar *types2.Var // #state variable for this loop
+ stateVarDecl *syntax.VarDecl
+ depth int // outermost loop has depth 1, otherwise depth = depth(parent)+1
checkRet bool // add check for "return" after loop
- checkRetArgs bool // add check for "return args" after loop
checkBreak bool // add check for "break" after loop
checkContinue bool // add check for "continue" after loop
checkBranch []branch // add check for labeled branch after loop
}
+type State int
+
+const (
+ DONE = State(iota) // body of loop has exited in a non-panic way
+ READY // body of loop has not exited yet, is not running
+ PANIC // body of loop is either currently running, or has panicked
+ EXHAUSTED // iterator function return, i.e., sequence is "exhausted"
+ MISSING_PANIC // an error code, not really a state.
+)
+
// Rewrite rewrites all the range-over-funcs in the files.
-func Rewrite(pkg *types2.Package, info *types2.Info, files []*syntax.File) {
+// It returns the set of function literals generated from rangefunc loop bodies.
+// This allows for rangefunc loop bodies to be distingushed by debuggers.
+func Rewrite(pkg *types2.Package, info *types2.Info, files []*syntax.File) map[*syntax.FuncLit]bool {
+ ri := make(map[*syntax.FuncLit]bool)
for _, file := range files {
syntax.Inspect(file, func(n syntax.Node) bool {
switch n := n.(type) {
case *syntax.FuncDecl:
- rewriteFunc(pkg, info, n.Type, n.Body)
+ sig, _ := info.Defs[n.Name].Type().(*types2.Signature)
+ rewriteFunc(pkg, info, n.Type, n.Body, sig, ri)
return false
case *syntax.FuncLit:
- rewriteFunc(pkg, info, n.Type, n.Body)
+ sig, _ := info.Types[n].Type.(*types2.Signature)
+ if sig == nil {
+ tv := n.GetTypeInfo()
+ sig = tv.Type.(*types2.Signature)
+ }
+ rewriteFunc(pkg, info, n.Type, n.Body, sig, ri)
return false
}
return true
})
}
+ return ri
}
// rewriteFunc rewrites all the range-over-funcs in a single function (a top-level func or a func literal).
// The typ and body are the function's type and body.
-func rewriteFunc(pkg *types2.Package, info *types2.Info, typ *syntax.FuncType, body *syntax.BlockStmt) {
+func rewriteFunc(pkg *types2.Package, info *types2.Info, typ *syntax.FuncType, body *syntax.BlockStmt, sig *types2.Signature, ri map[*syntax.FuncLit]bool) {
if body == nil {
return
}
r := &rewriter{
- pkg: pkg,
- info: info,
- outer: typ,
- body: body,
+ pkg: pkg,
+ info: info,
+ outer: typ,
+ body: body,
+ sig: sig,
+ rangefuncBodyClosures: ri,
}
syntax.Inspect(body, r.inspect)
if (base.Flag.W != 0) && r.forStack != nil {
func (r *rewriter) inspect(n syntax.Node) bool {
switch n := n.(type) {
case *syntax.FuncLit:
- rewriteFunc(r.pkg, r.info, n.Type, n.Body)
+ sig, _ := r.info.Types[n].Type.(*types2.Signature)
+ if sig == nil {
+ tv := n.GetTypeInfo()
+ sig = tv.Type.(*types2.Signature)
+ }
+ rewriteFunc(r.pkg, r.info, n.Type, n.Body, sig, r.rangefuncBodyClosures)
return false
default:
// Push n onto stack.
r.stack = append(r.stack, n)
if nfor, ok := forRangeFunc(n); ok {
- loop := &forLoop{nfor: nfor}
+ loop := &forLoop{nfor: nfor, depth: 1 + len(r.forStack)}
r.forStack = append(r.forStack, loop)
r.startLoop(loop)
}
r.rewritten = make(map[*syntax.ForStmt]syntax.Stmt)
}
if r.checkFuncMisuse() {
- // declare the exit flag for this loop's body
- loop.exitFlag, loop.exitFlagDecl = r.exitVar(loop.nfor.Pos())
+ // declare the state flag for this loop's body
+ loop.stateVar, loop.stateVarDecl = r.stateVar(loop.nfor.Pos())
}
}
}
// Attach the token as an "extra" argument to the defer.
- x.DeferAt = r.useVar(r.defers)
+ x.DeferAt = r.useObj(r.defers)
setPos(x.DeferAt, x.Pos())
return x
}
-func (r *rewriter) exitVar(pos syntax.Pos) (*types2.Var, *syntax.VarDecl) {
- r.exitVarCount++
+func (r *rewriter) stateVar(pos syntax.Pos) (*types2.Var, *syntax.VarDecl) {
+ r.stateVarCount++
- name := fmt.Sprintf("#exit%d", r.exitVarCount)
- typ := r.bool.Type()
+ name := fmt.Sprintf("#state%d", r.stateVarCount)
+ typ := r.int.Type()
obj := types2.NewVar(pos, r.pkg, name, typ)
n := syntax.NewName(pos, name)
setValueType(n, typ)
r.info.Defs[n] = obj
- return obj, &syntax.VarDecl{NameList: []*syntax.Name{n}}
+ return obj, &syntax.VarDecl{NameList: []*syntax.Name{n}, Values: r.stateConst(READY)}
}
// editReturn returns the replacement for the return statement x.
// See the "Return" section in the package doc comment above for more context.
func (r *rewriter) editReturn(x *syntax.ReturnStmt) syntax.Stmt {
- // #next = -1 is return with no arguments; -2 is return with arguments.
- var next int
- if x.Results == nil {
- next = -1
- r.forStack[0].checkRet = true
- } else {
- next = -2
- r.forStack[0].checkRetArgs = true
- }
-
- // Tell the loops along the way to check for a return.
- for _, loop := range r.forStack[1:] {
- loop.checkRet = true
- }
-
- // Assign results, set #next, and return false.
bl := &syntax.BlockStmt{}
+
if x.Results != nil {
- if r.retVars == nil {
+ // rewrite "return val" into "assign to named result; return"
+ if len(r.outer.ResultList) > 0 {
+ // Make sure that result parameters all have names
for i, a := range r.outer.ResultList {
- obj := r.declVar(fmt.Sprintf("#r%d", i+1), a.Type.GetTypeInfo().Type, nil)
- r.retVars = append(r.retVars, obj)
+ if a.Name == nil || a.Name.Value == "_" {
+ r.generateParamName(r.outer.ResultList, i) // updates a.Name
+ }
}
}
- bl.List = append(bl.List, &syntax.AssignStmt{Lhs: r.useList(r.retVars), Rhs: x.Results})
+ // Assign to named results
+ results := []types2.Object{}
+ for _, a := range r.outer.ResultList {
+ results = append(results, r.info.Defs[a.Name])
+ }
+ bl.List = append(bl.List, &syntax.AssignStmt{Lhs: r.useList(results), Rhs: x.Results})
+ x.Results = nil
}
+
+ next := -1 // return
+
+ // Tell the loops along the way to check for a return.
+ for _, loop := range r.forStack {
+ loop.checkRet = true
+ }
+
+ // Set #next, and return false.
+
bl.List = append(bl.List, &syntax.AssignStmt{Lhs: r.next(), Rhs: r.intConst(next)})
if r.checkFuncMisuse() {
- // mark all enclosing loop bodies as exited
- for i := 0; i < len(r.forStack); i++ {
- bl.List = append(bl.List, r.setExitedAt(i))
- }
+ // mark this loop as exited, the others (which will be exited if iterators do not interfere) have not, yet.
+ bl.List = append(bl.List, r.setState(DONE, x.Pos()))
}
- bl.List = append(bl.List, &syntax.ReturnStmt{Results: r.useVar(r.false)})
+ bl.List = append(bl.List, &syntax.ReturnStmt{Results: r.useObj(r.false)})
setPos(bl, x.Pos())
return bl
}
var ret *syntax.ReturnStmt
if x.Tok == syntax.Goto || i < 0 {
// goto Label
- // or break/continue of labeled non-range-over-func loop.
+ // or break/continue of labeled non-range-over-func loop (x.Label != nil).
// We may be able to leave it alone, or we may have to break
// out of one or more nested loops and then use #next to signal
// to complete the break/continue/goto.
exitFrom = i + 1
// Mark loop we exit to get to targ to check for that branch.
- // When i==-1 that's the outermost func body
- top := r.forStack[i+1]
+ // When i==-1 / exitFrom == 0 that's the outermost func body.
+ top := r.forStack[exitFrom]
top.checkBranch = append(top.checkBranch, branch{x.Tok, label})
// Mark loops along the way to check for a plain return, so they break.
- for j := i + 2; j < len(r.forStack); j++ {
+ for j := exitFrom + 1; j < len(r.forStack); j++ {
r.forStack[j].checkRet = true
}
// In the innermost loop, use a plain "return false".
- ret = &syntax.ReturnStmt{Results: r.useVar(r.false)}
+ ret = &syntax.ReturnStmt{Results: r.useObj(r.false)}
} else {
// break/continue of labeled range-over-func loop.
- depth := len(r.forStack) - 1 - i
-
- // For continue of innermost loop, use "return true".
- // Otherwise we are breaking the innermost loop, so "return false".
-
- if depth == 0 && x.Tok == syntax.Continue {
- ret = &syntax.ReturnStmt{Results: r.useVar(r.true)}
- setPos(ret, x.Pos())
- return ret
- }
- ret = &syntax.ReturnStmt{Results: r.useVar(r.false)}
-
- // If this is a simple break, mark this loop as exited and return false.
- // No adjustments to #next.
- if depth == 0 {
+ if exitFrom == len(r.forStack) {
+ // Simple break or continue.
+ // Continue returns true, break returns false, optionally both adjust state,
+ // neither modifies #next.
+ var state State
+ if x.Tok == syntax.Continue {
+ ret = &syntax.ReturnStmt{Results: r.useObj(r.true)}
+ state = READY
+ } else {
+ ret = &syntax.ReturnStmt{Results: r.useObj(r.false)}
+ state = DONE
+ }
var stmts []syntax.Stmt
if r.checkFuncMisuse() {
- stmts = []syntax.Stmt{r.setExited(), ret}
+ stmts = []syntax.Stmt{r.setState(state, x.Pos()), ret}
} else {
stmts = []syntax.Stmt{ret}
}
return bl
}
+ ret = &syntax.ReturnStmt{Results: r.useObj(r.false)}
+
// The loop inside the one we are break/continue-ing
// needs to make that happen when we break out of it.
if x.Tok == syntax.Continue {
r.forStack[exitFrom].checkContinue = true
} else {
- exitFrom = i
+ exitFrom = i // exitFrom--
r.forStack[exitFrom].checkBreak = true
}
// Set next to break the appropriate number of times;
// the final time may be a continue, not a break.
- next = perLoopStep * depth
+ next = perLoopStep * (i + 1)
if x.Tok == syntax.Continue {
next--
}
}
if r.checkFuncMisuse() {
- // Set #exitK for this loop and those exited by the control flow.
- for i := exitFrom; i < len(r.forStack); i++ {
- bl.List = append(bl.List, r.setExitedAt(i))
- }
+ // Set #stateK for this loop.
+ // The exterior loops have not exited yet, and the iterator might interfere.
+ bl.List = append(bl.List, r.setState(DONE, x.Pos()))
}
bl.List = append(bl.List, ret)
})
// Assign numbers to all the labels we observed.
- used := -2
+ used := -1 // returns use -1
for _, l := range labels {
used -= 3
r.branchNext[branch{syntax.Break, l}] = used
block.List = append(block.List, r.declStmt)
}
- // declare the exitFlag here so it has proper scope and zeroing
+ // declare the state variable here so it has proper scope and initialization
if r.checkFuncMisuse() {
- exitFlagDecl := &syntax.DeclStmt{DeclList: []syntax.Decl{loop.exitFlagDecl}}
- block.List = append(block.List, exitFlagDecl)
+ stateVarDecl := &syntax.DeclStmt{DeclList: []syntax.Decl{loop.stateVarDecl}}
+ setPos(stateVarDecl, start)
+ block.List = append(block.List, stateVarDecl)
}
// iteratorFunc(bodyFunc)
block.List = append(block.List, call)
if r.checkFuncMisuse() {
- // iteratorFunc has exited, mark the exit flag for the body
- block.List = append(block.List, r.setExited())
+ // iteratorFunc has exited, check for swallowed panic, and set body state to EXHAUSTED
+ nif := &syntax.IfStmt{
+ Cond: r.cond(syntax.Eql, r.useObj(loop.stateVar), r.stateConst(PANIC)),
+ Then: &syntax.BlockStmt{
+ List: []syntax.Stmt{r.callPanic(start, r.stateConst(MISSING_PANIC))},
+ },
+ }
+ setPos(nif, end)
+ block.List = append(block.List, nif)
+ block.List = append(block.List, r.setState(EXHAUSTED, end))
}
block.List = append(block.List, checks...)
r.rewritten[nfor] = block
}
-func (r *rewriter) setExited() *syntax.AssignStmt {
- return r.setExitedAt(len(r.forStack) - 1)
+func (r *rewriter) cond(op syntax.Operator, x, y syntax.Expr) *syntax.Operation {
+ cond := &syntax.Operation{Op: op, X: x, Y: y}
+ tv := syntax.TypeAndValue{Type: r.bool.Type()}
+ tv.SetIsValue()
+ cond.SetTypeInfo(tv)
+ return cond
+}
+
+func (r *rewriter) setState(val State, pos syntax.Pos) *syntax.AssignStmt {
+ ss := r.setStateAt(len(r.forStack)-1, val)
+ setPos(ss, pos)
+ return ss
}
-func (r *rewriter) setExitedAt(index int) *syntax.AssignStmt {
+func (r *rewriter) setStateAt(index int, stateVal State) *syntax.AssignStmt {
loop := r.forStack[index]
return &syntax.AssignStmt{
- Lhs: r.useVar(loop.exitFlag),
- Rhs: r.useVar(r.true),
+ Lhs: r.useObj(loop.stateVar),
+ Rhs: r.stateConst(stateVal),
}
}
Rbrace: end,
},
}
+ r.rangefuncBodyClosures[bodyFunc] = true
setPos(bodyFunc, start)
for i := 0; i < ftyp.Params().Len(); i++ {
paramVar = types2.NewVar(start, r.pkg, fmt.Sprintf("#p%d", 1+i), typ)
if i < len(lhs) {
x := lhs[i]
- as := &syntax.AssignStmt{Lhs: x, Rhs: r.useVar(paramVar)}
+ as := &syntax.AssignStmt{Lhs: x, Rhs: r.useObj(paramVar)}
as.SetPos(x.Pos())
setPos(as.Rhs, x.Pos())
bodyFunc.Body.List = append(bodyFunc.Body.List, as)
loop := r.forStack[len(r.forStack)-1]
if r.checkFuncMisuse() {
- bodyFunc.Body.List = append(bodyFunc.Body.List, r.assertNotExited(start, loop))
+ bodyFunc.Body.List = append(bodyFunc.Body.List, r.assertReady(start, loop))
+ bodyFunc.Body.List = append(bodyFunc.Body.List, r.setState(PANIC, start))
}
// Original loop body (already rewritten by editStmt during inspect).
bodyFunc.Body.List = append(bodyFunc.Body.List, body...)
- // return true to continue at end of loop body
- ret := &syntax.ReturnStmt{Results: r.useVar(r.true)}
+ // end of loop body, set state to READY and return true to continue iteration
+ if r.checkFuncMisuse() {
+ bodyFunc.Body.List = append(bodyFunc.Body.List, r.setState(READY, end))
+ }
+ ret := &syntax.ReturnStmt{Results: r.useObj(r.true)}
ret.SetPos(end)
bodyFunc.Body.List = append(bodyFunc.Body.List, ret)
}
did[br] = true
doBranch := &syntax.BranchStmt{Tok: br.tok, Label: &syntax.Name{Value: br.label}}
- list = append(list, r.ifNext(syntax.Eql, r.branchNext[br], doBranch))
+ list = append(list, r.ifNext(syntax.Eql, r.branchNext[br], true, doBranch))
}
}
+
+ curLoop := loop.depth - 1
+ curLoopIndex := curLoop - 1
+
if len(r.forStack) == 1 {
- if loop.checkRetArgs {
- list = append(list, r.ifNext(syntax.Eql, -2, retStmt(r.useList(r.retVars))))
- }
if loop.checkRet {
- list = append(list, r.ifNext(syntax.Eql, -1, retStmt(nil)))
+ list = append(list, r.ifNext(syntax.Eql, -1, false, retStmt(nil)))
}
} else {
- if loop.checkRetArgs || loop.checkRet {
+
+ // Idealized check, implemented more simply for now.
+
+ // // N == depth of this loop, one less than the one just exited.
+ // if #next != 0 {
+ // if #next >= perLoopStep*N-1 { // this loop
+ // if #next >= perLoopStep*N+1 { // error checking
+ // runtime.panicrangestate(DONE)
+ // }
+ // rv := #next & 1 == 1 // code generates into #next&1
+ // #next = 0
+ // return rv
+ // }
+ // return false // or handle returns and gotos
+ // }
+
+ if loop.checkRet {
// Note: next < 0 also handles gotos handled by outer loops.
// We set checkRet in that case to trigger this check.
- list = append(list, r.ifNext(syntax.Lss, 0, retStmt(r.useVar(r.false))))
+ if r.checkFuncMisuse() {
+ list = append(list, r.ifNext(syntax.Lss, 0, false, r.setStateAt(curLoopIndex, DONE), retStmt(r.useObj(r.false))))
+ } else {
+ list = append(list, r.ifNext(syntax.Lss, 0, false, retStmt(r.useObj(r.false))))
+ }
}
- if loop.checkBreak {
- list = append(list, r.ifNext(syntax.Geq, perLoopStep, retStmt(r.useVar(r.false))))
+
+ depthStep := perLoopStep * (curLoop)
+
+ if r.checkFuncMisuse() {
+ list = append(list, r.ifNext(syntax.Gtr, depthStep, false, r.callPanic(pos, r.stateConst(DONE))))
+ } else {
+ list = append(list, r.ifNext(syntax.Gtr, depthStep, true))
}
- if loop.checkContinue {
- list = append(list, r.ifNext(syntax.Eql, perLoopStep-1, retStmt(r.useVar(r.true))))
+
+ if r.checkFuncMisuse() {
+ if loop.checkContinue {
+ list = append(list, r.ifNext(syntax.Eql, depthStep-1, true, r.setStateAt(curLoopIndex, READY), retStmt(r.useObj(r.true))))
+ }
+
+ if loop.checkBreak {
+ list = append(list, r.ifNext(syntax.Eql, depthStep, true, r.setStateAt(curLoopIndex, DONE), retStmt(r.useObj(r.false))))
+ }
+
+ if loop.checkContinue || loop.checkBreak {
+ list = append(list, r.ifNext(syntax.Gtr, 0, false, r.setStateAt(curLoopIndex, DONE), retStmt(r.useObj(r.false))))
+ }
+
+ } else {
+ if loop.checkContinue {
+ list = append(list, r.ifNext(syntax.Eql, depthStep-1, true, retStmt(r.useObj(r.true))))
+ }
+ if loop.checkBreak {
+ list = append(list, r.ifNext(syntax.Eql, depthStep, true, retStmt(r.useObj(r.false))))
+ }
+ if loop.checkContinue || loop.checkBreak {
+ list = append(list, r.ifNext(syntax.Gtr, 0, false, retStmt(r.useObj(r.false))))
+ }
}
}
// ifNext returns the statement:
//
-// if #next op c { adjust; then }
-//
-// When op is >=, adjust is #next -= c.
-// When op is == and c is not -1 or -2, adjust is #next = 0.
-// Otherwise adjust is omitted.
-func (r *rewriter) ifNext(op syntax.Operator, c int, then syntax.Stmt) syntax.Stmt {
- nif := &syntax.IfStmt{
- Cond: &syntax.Operation{Op: op, X: r.next(), Y: r.intConst(c)},
- Then: &syntax.BlockStmt{
- List: []syntax.Stmt{then},
- },
- }
- tv := syntax.TypeAndValue{Type: r.bool.Type()}
- tv.SetIsValue()
- nif.Cond.SetTypeInfo(tv)
-
- if op == syntax.Geq {
- sub := &syntax.AssignStmt{
- Op: syntax.Sub,
- Lhs: r.next(),
- Rhs: r.intConst(c),
- }
- nif.Then.List = []syntax.Stmt{sub, then}
- }
- if op == syntax.Eql && c != -1 && c != -2 {
+// if #next op c { [#next = 0;] thens... }
+func (r *rewriter) ifNext(op syntax.Operator, c int, zeroNext bool, thens ...syntax.Stmt) syntax.Stmt {
+ var thenList []syntax.Stmt
+ if zeroNext {
clr := &syntax.AssignStmt{
Lhs: r.next(),
Rhs: r.intConst(0),
}
- nif.Then.List = []syntax.Stmt{clr, then}
+ thenList = append(thenList, clr)
+ }
+ for _, then := range thens {
+ thenList = append(thenList, then)
+ }
+ nif := &syntax.IfStmt{
+ Cond: r.cond(op, r.next(), r.intConst(c)),
+ Then: &syntax.BlockStmt{
+ List: thenList,
+ },
}
-
return nif
}
x.SetTypeInfo(tv)
}
-// assertNotExited returns the statement:
+// assertReady returns the statement:
//
-// if #exitK { runtime.panicrangeexit() }
+// if #stateK != READY { runtime.panicrangestate(#stateK) }
//
-// where #exitK is the exit guard for loop.
-func (r *rewriter) assertNotExited(start syntax.Pos, loop *forLoop) syntax.Stmt {
- callPanicExpr := &syntax.CallExpr{
- Fun: runtimeSym(r.info, "panicrangeexit"),
- }
- setValueType(callPanicExpr, nil) // no result type
-
- callPanic := &syntax.ExprStmt{X: callPanicExpr}
-
+// where #stateK is the state variable for loop.
+func (r *rewriter) assertReady(start syntax.Pos, loop *forLoop) syntax.Stmt {
nif := &syntax.IfStmt{
- Cond: r.useVar(loop.exitFlag),
+ Cond: r.cond(syntax.Neq, r.useObj(loop.stateVar), r.stateConst(READY)),
Then: &syntax.BlockStmt{
- List: []syntax.Stmt{callPanic},
+ List: []syntax.Stmt{r.callPanic(start, r.useObj(loop.stateVar))},
},
}
setPos(nif, start)
return nif
}
+func (r *rewriter) callPanic(start syntax.Pos, arg syntax.Expr) syntax.Stmt {
+ callPanicExpr := &syntax.CallExpr{
+ Fun: runtimeSym(r.info, "panicrangestate"),
+ ArgList: []syntax.Expr{arg},
+ }
+ setValueType(callPanicExpr, nil) // no result type
+ return &syntax.ExprStmt{X: callPanicExpr}
+}
+
// next returns a reference to the #next variable.
func (r *rewriter) next() *syntax.Name {
if r.nextVar == nil {
r.nextVar = r.declVar("#next", r.int.Type(), nil)
}
- return r.useVar(r.nextVar)
+ return r.useObj(r.nextVar)
}
// forRangeFunc checks whether n is a range-over-func.
return lit
}
-// useVar returns syntax for a reference to decl, which should be its declaration.
-func (r *rewriter) useVar(obj types2.Object) *syntax.Name {
+func (r *rewriter) stateConst(s State) *syntax.BasicLit {
+ return r.intConst(int(s))
+}
+
+// useObj returns syntax for a reference to decl, which should be its declaration.
+func (r *rewriter) useObj(obj types2.Object) *syntax.Name {
n := syntax.NewName(nopos, obj.Name())
tv := syntax.TypeAndValue{Type: obj.Type()}
tv.SetIsValue()
func (r *rewriter) useList(vars []types2.Object) syntax.Expr {
var new []syntax.Expr
for _, obj := range vars {
- new = append(new, r.useVar(obj))
+ new = append(new, r.useObj(obj))
}
if len(new) == 1 {
return new[0]
return &syntax.ListExpr{ElemList: new}
}
+func (r *rewriter) makeVarName(pos syntax.Pos, name string, typ types2.Type) (*types2.Var, *syntax.Name) {
+ obj := types2.NewVar(pos, r.pkg, name, typ)
+ n := syntax.NewName(pos, name)
+ tv := syntax.TypeAndValue{Type: typ}
+ tv.SetIsValue()
+ n.SetTypeInfo(tv)
+ r.info.Defs[n] = obj
+ return obj, n
+}
+
+func (r *rewriter) generateParamName(results []*syntax.Field, i int) {
+ obj, n := r.sig.RenameResult(results, i)
+ r.info.Defs[n] = obj
+}
+
// declVar declares a variable with a given name type and initializer value.
func (r *rewriter) declVar(name string, typ types2.Type, init syntax.Expr) *types2.Var {
if r.declStmt == nil {
r.declStmt = &syntax.DeclStmt{}
}
stmt := r.declStmt
- obj := types2.NewVar(stmt.Pos(), r.pkg, name, typ)
- n := syntax.NewName(stmt.Pos(), name)
- tv := syntax.TypeAndValue{Type: typ}
- tv.SetIsValue()
- n.SetTypeInfo(tv)
- r.info.Defs[n] = obj
+ obj, n := r.makeVarName(stmt.Pos(), name, typ)
stmt.DeclList = append(stmt.DeclList, &syntax.VarDecl{
NameList: []*syntax.Name{n},
// Note: Type is ignored
return obj
}
-// declType declares a type with the given name and type.
-// This is more like "type name = typ" than "type name typ".
-func declType(pos syntax.Pos, name string, typ types2.Type) *syntax.Name {
- n := syntax.NewName(pos, name)
- n.SetTypeInfo(syntax.TypeAndValue{Type: typ})
- return n
-}
-
// runtimePkg is a fake runtime package that contains what we need to refer to in package runtime.
var runtimePkg = func() *types2.Package {
var nopos syntax.Pos
pkg := types2.NewPackage("runtime", "runtime")
anyType := types2.Universe.Lookup("any").Type()
+ intType := types2.Universe.Lookup("int").Type()
// func deferrangefunc() unsafe.Pointer
obj := types2.NewFunc(nopos, pkg, "deferrangefunc", types2.NewSignatureType(nil, nil, nil, nil, types2.NewTuple(types2.NewParam(nopos, pkg, "extra", anyType)), false))
pkg.Scope().Insert(obj)
- // func panicrangeexit()
- obj = types2.NewFunc(nopos, pkg, "panicrangeexit", types2.NewSignatureType(nil, nil, nil, nil, nil, false))
+ // func panicrangestate()
+ obj = types2.NewFunc(nopos, pkg, "panicrangestate", types2.NewSignatureType(nil, nil, nil, types2.NewTuple(types2.NewParam(nopos, pkg, "state", intType)), nil, false))
pkg.Scope().Insert(obj)
return pkg
s.callResult(n, callNormal)
if n.Op() == ir.OCALLFUNC && n.Fun.Op() == ir.ONAME && n.Fun.(*ir.Name).Class == ir.PFUNC {
if fn := n.Fun.Sym().Name; base.Flag.CompilingRuntime && fn == "throw" ||
- n.Fun.Sym().Pkg == ir.Pkgs.Runtime && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block" || fn == "panicmakeslicelen" || fn == "panicmakeslicecap" || fn == "panicunsafeslicelen" || fn == "panicunsafeslicenilptr" || fn == "panicunsafestringlen" || fn == "panicunsafestringnilptr") {
+ n.Fun.Sym().Pkg == ir.Pkgs.Runtime &&
+ (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block" ||
+ fn == "panicmakeslicelen" || fn == "panicmakeslicecap" || fn == "panicunsafeslicelen" ||
+ fn == "panicunsafeslicenilptr" || fn == "panicunsafestringlen" || fn == "panicunsafestringnilptr" ||
+ fn == "panicrangestate") {
m := s.mem()
b := s.endBlock()
b.Kind = ssa.BlockExit
func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool)
func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool)
-// panic for iteration after exit in range func
-func panicrangeexit()
+// panic for various rangefunc iterator errors
+func panicrangestate(state int)
// defer in range over func
func deferrangefunc() interface{}
{"interfaceSwitch", funcTag, 70},
{"ifaceeq", funcTag, 72},
{"efaceeq", funcTag, 72},
- {"panicrangeexit", funcTag, 9},
- {"deferrangefunc", funcTag, 73},
- {"rand32", funcTag, 74},
- {"makemap64", funcTag, 76},
- {"makemap", funcTag, 77},
- {"makemap_small", funcTag, 78},
- {"mapaccess1", funcTag, 79},
- {"mapaccess1_fast32", funcTag, 80},
- {"mapaccess1_fast64", funcTag, 81},
- {"mapaccess1_faststr", funcTag, 82},
- {"mapaccess1_fat", funcTag, 83},
- {"mapaccess2", funcTag, 84},
- {"mapaccess2_fast32", funcTag, 85},
- {"mapaccess2_fast64", funcTag, 86},
- {"mapaccess2_faststr", funcTag, 87},
- {"mapaccess2_fat", funcTag, 88},
- {"mapassign", funcTag, 79},
- {"mapassign_fast32", funcTag, 80},
- {"mapassign_fast32ptr", funcTag, 89},
- {"mapassign_fast64", funcTag, 81},
- {"mapassign_fast64ptr", funcTag, 89},
- {"mapassign_faststr", funcTag, 82},
- {"mapiterinit", funcTag, 90},
- {"mapdelete", funcTag, 90},
- {"mapdelete_fast32", funcTag, 91},
- {"mapdelete_fast64", funcTag, 92},
- {"mapdelete_faststr", funcTag, 93},
- {"mapiternext", funcTag, 94},
- {"mapclear", funcTag, 95},
- {"makechan64", funcTag, 97},
- {"makechan", funcTag, 98},
- {"chanrecv1", funcTag, 100},
- {"chanrecv2", funcTag, 101},
- {"chansend1", funcTag, 103},
- {"closechan", funcTag, 104},
- {"chanlen", funcTag, 105},
- {"chancap", funcTag, 105},
- {"writeBarrier", varTag, 107},
- {"typedmemmove", funcTag, 108},
- {"typedmemclr", funcTag, 109},
- {"typedslicecopy", funcTag, 110},
- {"selectnbsend", funcTag, 111},
- {"selectnbrecv", funcTag, 112},
- {"selectsetpc", funcTag, 113},
- {"selectgo", funcTag, 114},
+ {"panicrangestate", funcTag, 73},
+ {"deferrangefunc", funcTag, 74},
+ {"rand32", funcTag, 75},
+ {"makemap64", funcTag, 77},
+ {"makemap", funcTag, 78},
+ {"makemap_small", funcTag, 79},
+ {"mapaccess1", funcTag, 80},
+ {"mapaccess1_fast32", funcTag, 81},
+ {"mapaccess1_fast64", funcTag, 82},
+ {"mapaccess1_faststr", funcTag, 83},
+ {"mapaccess1_fat", funcTag, 84},
+ {"mapaccess2", funcTag, 85},
+ {"mapaccess2_fast32", funcTag, 86},
+ {"mapaccess2_fast64", funcTag, 87},
+ {"mapaccess2_faststr", funcTag, 88},
+ {"mapaccess2_fat", funcTag, 89},
+ {"mapassign", funcTag, 80},
+ {"mapassign_fast32", funcTag, 81},
+ {"mapassign_fast32ptr", funcTag, 90},
+ {"mapassign_fast64", funcTag, 82},
+ {"mapassign_fast64ptr", funcTag, 90},
+ {"mapassign_faststr", funcTag, 83},
+ {"mapiterinit", funcTag, 91},
+ {"mapdelete", funcTag, 91},
+ {"mapdelete_fast32", funcTag, 92},
+ {"mapdelete_fast64", funcTag, 93},
+ {"mapdelete_faststr", funcTag, 94},
+ {"mapiternext", funcTag, 95},
+ {"mapclear", funcTag, 96},
+ {"makechan64", funcTag, 98},
+ {"makechan", funcTag, 99},
+ {"chanrecv1", funcTag, 101},
+ {"chanrecv2", funcTag, 102},
+ {"chansend1", funcTag, 104},
+ {"closechan", funcTag, 105},
+ {"chanlen", funcTag, 106},
+ {"chancap", funcTag, 106},
+ {"writeBarrier", varTag, 108},
+ {"typedmemmove", funcTag, 109},
+ {"typedmemclr", funcTag, 110},
+ {"typedslicecopy", funcTag, 111},
+ {"selectnbsend", funcTag, 112},
+ {"selectnbrecv", funcTag, 113},
+ {"selectsetpc", funcTag, 114},
+ {"selectgo", funcTag, 115},
{"block", funcTag, 9},
- {"makeslice", funcTag, 115},
- {"makeslice64", funcTag, 116},
- {"makeslicecopy", funcTag, 117},
- {"growslice", funcTag, 119},
- {"unsafeslicecheckptr", funcTag, 120},
+ {"makeslice", funcTag, 116},
+ {"makeslice64", funcTag, 117},
+ {"makeslicecopy", funcTag, 118},
+ {"growslice", funcTag, 120},
+ {"unsafeslicecheckptr", funcTag, 121},
{"panicunsafeslicelen", funcTag, 9},
{"panicunsafeslicenilptr", funcTag, 9},
- {"unsafestringcheckptr", funcTag, 121},
+ {"unsafestringcheckptr", funcTag, 122},
{"panicunsafestringlen", funcTag, 9},
{"panicunsafestringnilptr", funcTag, 9},
- {"memmove", funcTag, 122},
- {"memclrNoHeapPointers", funcTag, 123},
- {"memclrHasPointers", funcTag, 123},
- {"memequal", funcTag, 124},
- {"memequal0", funcTag, 125},
- {"memequal8", funcTag, 125},
- {"memequal16", funcTag, 125},
- {"memequal32", funcTag, 125},
- {"memequal64", funcTag, 125},
- {"memequal128", funcTag, 125},
- {"f32equal", funcTag, 126},
- {"f64equal", funcTag, 126},
- {"c64equal", funcTag, 126},
- {"c128equal", funcTag, 126},
- {"strequal", funcTag, 126},
- {"interequal", funcTag, 126},
- {"nilinterequal", funcTag, 126},
- {"memhash", funcTag, 127},
- {"memhash0", funcTag, 128},
- {"memhash8", funcTag, 128},
- {"memhash16", funcTag, 128},
- {"memhash32", funcTag, 128},
- {"memhash64", funcTag, 128},
- {"memhash128", funcTag, 128},
- {"f32hash", funcTag, 129},
- {"f64hash", funcTag, 129},
- {"c64hash", funcTag, 129},
- {"c128hash", funcTag, 129},
- {"strhash", funcTag, 129},
- {"interhash", funcTag, 129},
- {"nilinterhash", funcTag, 129},
- {"int64div", funcTag, 130},
- {"uint64div", funcTag, 131},
- {"int64mod", funcTag, 130},
- {"uint64mod", funcTag, 131},
- {"float64toint64", funcTag, 132},
- {"float64touint64", funcTag, 133},
- {"float64touint32", funcTag, 134},
- {"int64tofloat64", funcTag, 135},
- {"int64tofloat32", funcTag, 137},
- {"uint64tofloat64", funcTag, 138},
- {"uint64tofloat32", funcTag, 139},
- {"uint32tofloat64", funcTag, 140},
- {"complex128div", funcTag, 141},
- {"getcallerpc", funcTag, 142},
- {"getcallersp", funcTag, 142},
+ {"memmove", funcTag, 123},
+ {"memclrNoHeapPointers", funcTag, 124},
+ {"memclrHasPointers", funcTag, 124},
+ {"memequal", funcTag, 125},
+ {"memequal0", funcTag, 126},
+ {"memequal8", funcTag, 126},
+ {"memequal16", funcTag, 126},
+ {"memequal32", funcTag, 126},
+ {"memequal64", funcTag, 126},
+ {"memequal128", funcTag, 126},
+ {"f32equal", funcTag, 127},
+ {"f64equal", funcTag, 127},
+ {"c64equal", funcTag, 127},
+ {"c128equal", funcTag, 127},
+ {"strequal", funcTag, 127},
+ {"interequal", funcTag, 127},
+ {"nilinterequal", funcTag, 127},
+ {"memhash", funcTag, 128},
+ {"memhash0", funcTag, 129},
+ {"memhash8", funcTag, 129},
+ {"memhash16", funcTag, 129},
+ {"memhash32", funcTag, 129},
+ {"memhash64", funcTag, 129},
+ {"memhash128", funcTag, 129},
+ {"f32hash", funcTag, 130},
+ {"f64hash", funcTag, 130},
+ {"c64hash", funcTag, 130},
+ {"c128hash", funcTag, 130},
+ {"strhash", funcTag, 130},
+ {"interhash", funcTag, 130},
+ {"nilinterhash", funcTag, 130},
+ {"int64div", funcTag, 131},
+ {"uint64div", funcTag, 132},
+ {"int64mod", funcTag, 131},
+ {"uint64mod", funcTag, 132},
+ {"float64toint64", funcTag, 133},
+ {"float64touint64", funcTag, 134},
+ {"float64touint32", funcTag, 135},
+ {"int64tofloat64", funcTag, 136},
+ {"int64tofloat32", funcTag, 138},
+ {"uint64tofloat64", funcTag, 139},
+ {"uint64tofloat32", funcTag, 140},
+ {"uint32tofloat64", funcTag, 141},
+ {"complex128div", funcTag, 142},
+ {"getcallerpc", funcTag, 143},
+ {"getcallersp", funcTag, 143},
{"racefuncenter", funcTag, 31},
{"racefuncexit", funcTag, 9},
{"raceread", funcTag, 31},
{"racewrite", funcTag, 31},
- {"racereadrange", funcTag, 143},
- {"racewriterange", funcTag, 143},
- {"msanread", funcTag, 143},
- {"msanwrite", funcTag, 143},
- {"msanmove", funcTag, 144},
- {"asanread", funcTag, 143},
- {"asanwrite", funcTag, 143},
- {"checkptrAlignment", funcTag, 145},
- {"checkptrArithmetic", funcTag, 147},
- {"libfuzzerTraceCmp1", funcTag, 148},
- {"libfuzzerTraceCmp2", funcTag, 149},
- {"libfuzzerTraceCmp4", funcTag, 150},
- {"libfuzzerTraceCmp8", funcTag, 151},
- {"libfuzzerTraceConstCmp1", funcTag, 148},
- {"libfuzzerTraceConstCmp2", funcTag, 149},
- {"libfuzzerTraceConstCmp4", funcTag, 150},
- {"libfuzzerTraceConstCmp8", funcTag, 151},
- {"libfuzzerHookStrCmp", funcTag, 152},
- {"libfuzzerHookEqualFold", funcTag, 152},
- {"addCovMeta", funcTag, 154},
+ {"racereadrange", funcTag, 144},
+ {"racewriterange", funcTag, 144},
+ {"msanread", funcTag, 144},
+ {"msanwrite", funcTag, 144},
+ {"msanmove", funcTag, 145},
+ {"asanread", funcTag, 144},
+ {"asanwrite", funcTag, 144},
+ {"checkptrAlignment", funcTag, 146},
+ {"checkptrArithmetic", funcTag, 148},
+ {"libfuzzerTraceCmp1", funcTag, 149},
+ {"libfuzzerTraceCmp2", funcTag, 150},
+ {"libfuzzerTraceCmp4", funcTag, 151},
+ {"libfuzzerTraceCmp8", funcTag, 152},
+ {"libfuzzerTraceConstCmp1", funcTag, 149},
+ {"libfuzzerTraceConstCmp2", funcTag, 150},
+ {"libfuzzerTraceConstCmp4", funcTag, 151},
+ {"libfuzzerTraceConstCmp8", funcTag, 152},
+ {"libfuzzerHookStrCmp", funcTag, 153},
+ {"libfuzzerHookEqualFold", funcTag, 153},
+ {"addCovMeta", funcTag, 155},
{"x86HasPOPCNT", varTag, 6},
{"x86HasSSE41", varTag, 6},
{"x86HasFMA", varTag, 6},
{"armHasVFPv4", varTag, 6},
{"arm64HasATOMICS", varTag, 6},
- {"asanregisterglobals", funcTag, 123},
+ {"asanregisterglobals", funcTag, 124},
}
func runtimeTypes() []*types.Type {
- var typs [155]*types.Type
+ var typs [156]*types.Type
typs[0] = types.ByteType
typs[1] = types.NewPtr(typs[0])
typs[2] = types.Types[types.TANY]
typs[70] = newSig(params(typs[1], typs[1]), params(typs[15], typs[1]))
typs[71] = types.NewPtr(typs[5])
typs[72] = newSig(params(typs[71], typs[7], typs[7]), params(typs[6]))
- typs[73] = newSig(nil, params(typs[10]))
- typs[74] = newSig(nil, params(typs[60]))
- typs[75] = types.NewMap(typs[2], typs[2])
- typs[76] = newSig(params(typs[1], typs[22], typs[3]), params(typs[75]))
- typs[77] = newSig(params(typs[1], typs[15], typs[3]), params(typs[75]))
- typs[78] = newSig(nil, params(typs[75]))
- typs[79] = newSig(params(typs[1], typs[75], typs[3]), params(typs[3]))
- typs[80] = newSig(params(typs[1], typs[75], typs[60]), params(typs[3]))
- typs[81] = newSig(params(typs[1], typs[75], typs[24]), params(typs[3]))
- typs[82] = newSig(params(typs[1], typs[75], typs[28]), params(typs[3]))
- typs[83] = newSig(params(typs[1], typs[75], typs[3], typs[1]), params(typs[3]))
- typs[84] = newSig(params(typs[1], typs[75], typs[3]), params(typs[3], typs[6]))
- typs[85] = newSig(params(typs[1], typs[75], typs[60]), params(typs[3], typs[6]))
- typs[86] = newSig(params(typs[1], typs[75], typs[24]), params(typs[3], typs[6]))
- typs[87] = newSig(params(typs[1], typs[75], typs[28]), params(typs[3], typs[6]))
- typs[88] = newSig(params(typs[1], typs[75], typs[3], typs[1]), params(typs[3], typs[6]))
- typs[89] = newSig(params(typs[1], typs[75], typs[7]), params(typs[3]))
- typs[90] = newSig(params(typs[1], typs[75], typs[3]), nil)
- typs[91] = newSig(params(typs[1], typs[75], typs[60]), nil)
- typs[92] = newSig(params(typs[1], typs[75], typs[24]), nil)
- typs[93] = newSig(params(typs[1], typs[75], typs[28]), nil)
- typs[94] = newSig(params(typs[3]), nil)
- typs[95] = newSig(params(typs[1], typs[75]), nil)
- typs[96] = types.NewChan(typs[2], types.Cboth)
- typs[97] = newSig(params(typs[1], typs[22]), params(typs[96]))
- typs[98] = newSig(params(typs[1], typs[15]), params(typs[96]))
- typs[99] = types.NewChan(typs[2], types.Crecv)
- typs[100] = newSig(params(typs[99], typs[3]), nil)
- typs[101] = newSig(params(typs[99], typs[3]), params(typs[6]))
- typs[102] = types.NewChan(typs[2], types.Csend)
- typs[103] = newSig(params(typs[102], typs[3]), nil)
- typs[104] = newSig(params(typs[102]), nil)
- typs[105] = newSig(params(typs[2]), params(typs[15]))
- typs[106] = types.NewArray(typs[0], 3)
- typs[107] = types.NewStruct([]*types.Field{types.NewField(src.NoXPos, Lookup("enabled"), typs[6]), types.NewField(src.NoXPos, Lookup("pad"), typs[106]), types.NewField(src.NoXPos, Lookup("cgo"), typs[6]), types.NewField(src.NoXPos, Lookup("alignme"), typs[24])})
- typs[108] = newSig(params(typs[1], typs[3], typs[3]), nil)
- typs[109] = newSig(params(typs[1], typs[3]), nil)
- typs[110] = newSig(params(typs[1], typs[3], typs[15], typs[3], typs[15]), params(typs[15]))
- typs[111] = newSig(params(typs[102], typs[3]), params(typs[6]))
- typs[112] = newSig(params(typs[3], typs[99]), params(typs[6], typs[6]))
- typs[113] = newSig(params(typs[71]), nil)
- typs[114] = newSig(params(typs[1], typs[1], typs[71], typs[15], typs[15], typs[6]), params(typs[15], typs[6]))
- typs[115] = newSig(params(typs[1], typs[15], typs[15]), params(typs[7]))
- typs[116] = newSig(params(typs[1], typs[22], typs[22]), params(typs[7]))
- typs[117] = newSig(params(typs[1], typs[15], typs[15], typs[7]), params(typs[7]))
- typs[118] = types.NewSlice(typs[2])
- typs[119] = newSig(params(typs[3], typs[15], typs[15], typs[15], typs[1]), params(typs[118]))
- typs[120] = newSig(params(typs[1], typs[7], typs[22]), nil)
- typs[121] = newSig(params(typs[7], typs[22]), nil)
- typs[122] = newSig(params(typs[3], typs[3], typs[5]), nil)
- typs[123] = newSig(params(typs[7], typs[5]), nil)
- typs[124] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6]))
- typs[125] = newSig(params(typs[3], typs[3]), params(typs[6]))
- typs[126] = newSig(params(typs[7], typs[7]), params(typs[6]))
- typs[127] = newSig(params(typs[3], typs[5], typs[5]), params(typs[5]))
- typs[128] = newSig(params(typs[7], typs[5]), params(typs[5]))
- typs[129] = newSig(params(typs[3], typs[5]), params(typs[5]))
- typs[130] = newSig(params(typs[22], typs[22]), params(typs[22]))
- typs[131] = newSig(params(typs[24], typs[24]), params(typs[24]))
- typs[132] = newSig(params(typs[20]), params(typs[22]))
- typs[133] = newSig(params(typs[20]), params(typs[24]))
- typs[134] = newSig(params(typs[20]), params(typs[60]))
- typs[135] = newSig(params(typs[22]), params(typs[20]))
- typs[136] = types.Types[types.TFLOAT32]
- typs[137] = newSig(params(typs[22]), params(typs[136]))
- typs[138] = newSig(params(typs[24]), params(typs[20]))
- typs[139] = newSig(params(typs[24]), params(typs[136]))
- typs[140] = newSig(params(typs[60]), params(typs[20]))
- typs[141] = newSig(params(typs[26], typs[26]), params(typs[26]))
- typs[142] = newSig(nil, params(typs[5]))
- typs[143] = newSig(params(typs[5], typs[5]), nil)
- typs[144] = newSig(params(typs[5], typs[5], typs[5]), nil)
- typs[145] = newSig(params(typs[7], typs[1], typs[5]), nil)
- typs[146] = types.NewSlice(typs[7])
- typs[147] = newSig(params(typs[7], typs[146]), nil)
- typs[148] = newSig(params(typs[64], typs[64], typs[17]), nil)
- typs[149] = newSig(params(typs[58], typs[58], typs[17]), nil)
- typs[150] = newSig(params(typs[60], typs[60], typs[17]), nil)
- typs[151] = newSig(params(typs[24], typs[24], typs[17]), nil)
- typs[152] = newSig(params(typs[28], typs[28], typs[17]), nil)
- typs[153] = types.NewArray(typs[0], 16)
- typs[154] = newSig(params(typs[7], typs[60], typs[153], typs[28], typs[15], typs[64], typs[64]), params(typs[60]))
+ typs[73] = newSig(params(typs[15]), nil)
+ typs[74] = newSig(nil, params(typs[10]))
+ typs[75] = newSig(nil, params(typs[60]))
+ typs[76] = types.NewMap(typs[2], typs[2])
+ typs[77] = newSig(params(typs[1], typs[22], typs[3]), params(typs[76]))
+ typs[78] = newSig(params(typs[1], typs[15], typs[3]), params(typs[76]))
+ typs[79] = newSig(nil, params(typs[76]))
+ typs[80] = newSig(params(typs[1], typs[76], typs[3]), params(typs[3]))
+ typs[81] = newSig(params(typs[1], typs[76], typs[60]), params(typs[3]))
+ typs[82] = newSig(params(typs[1], typs[76], typs[24]), params(typs[3]))
+ typs[83] = newSig(params(typs[1], typs[76], typs[28]), params(typs[3]))
+ typs[84] = newSig(params(typs[1], typs[76], typs[3], typs[1]), params(typs[3]))
+ typs[85] = newSig(params(typs[1], typs[76], typs[3]), params(typs[3], typs[6]))
+ typs[86] = newSig(params(typs[1], typs[76], typs[60]), params(typs[3], typs[6]))
+ typs[87] = newSig(params(typs[1], typs[76], typs[24]), params(typs[3], typs[6]))
+ typs[88] = newSig(params(typs[1], typs[76], typs[28]), params(typs[3], typs[6]))
+ typs[89] = newSig(params(typs[1], typs[76], typs[3], typs[1]), params(typs[3], typs[6]))
+ typs[90] = newSig(params(typs[1], typs[76], typs[7]), params(typs[3]))
+ typs[91] = newSig(params(typs[1], typs[76], typs[3]), nil)
+ typs[92] = newSig(params(typs[1], typs[76], typs[60]), nil)
+ typs[93] = newSig(params(typs[1], typs[76], typs[24]), nil)
+ typs[94] = newSig(params(typs[1], typs[76], typs[28]), nil)
+ typs[95] = newSig(params(typs[3]), nil)
+ typs[96] = newSig(params(typs[1], typs[76]), nil)
+ typs[97] = types.NewChan(typs[2], types.Cboth)
+ typs[98] = newSig(params(typs[1], typs[22]), params(typs[97]))
+ typs[99] = newSig(params(typs[1], typs[15]), params(typs[97]))
+ typs[100] = types.NewChan(typs[2], types.Crecv)
+ typs[101] = newSig(params(typs[100], typs[3]), nil)
+ typs[102] = newSig(params(typs[100], typs[3]), params(typs[6]))
+ typs[103] = types.NewChan(typs[2], types.Csend)
+ typs[104] = newSig(params(typs[103], typs[3]), nil)
+ typs[105] = newSig(params(typs[103]), nil)
+ typs[106] = newSig(params(typs[2]), params(typs[15]))
+ typs[107] = types.NewArray(typs[0], 3)
+ typs[108] = types.NewStruct([]*types.Field{types.NewField(src.NoXPos, Lookup("enabled"), typs[6]), types.NewField(src.NoXPos, Lookup("pad"), typs[107]), types.NewField(src.NoXPos, Lookup("cgo"), typs[6]), types.NewField(src.NoXPos, Lookup("alignme"), typs[24])})
+ typs[109] = newSig(params(typs[1], typs[3], typs[3]), nil)
+ typs[110] = newSig(params(typs[1], typs[3]), nil)
+ typs[111] = newSig(params(typs[1], typs[3], typs[15], typs[3], typs[15]), params(typs[15]))
+ typs[112] = newSig(params(typs[103], typs[3]), params(typs[6]))
+ typs[113] = newSig(params(typs[3], typs[100]), params(typs[6], typs[6]))
+ typs[114] = newSig(params(typs[71]), nil)
+ typs[115] = newSig(params(typs[1], typs[1], typs[71], typs[15], typs[15], typs[6]), params(typs[15], typs[6]))
+ typs[116] = newSig(params(typs[1], typs[15], typs[15]), params(typs[7]))
+ typs[117] = newSig(params(typs[1], typs[22], typs[22]), params(typs[7]))
+ typs[118] = newSig(params(typs[1], typs[15], typs[15], typs[7]), params(typs[7]))
+ typs[119] = types.NewSlice(typs[2])
+ typs[120] = newSig(params(typs[3], typs[15], typs[15], typs[15], typs[1]), params(typs[119]))
+ typs[121] = newSig(params(typs[1], typs[7], typs[22]), nil)
+ typs[122] = newSig(params(typs[7], typs[22]), nil)
+ typs[123] = newSig(params(typs[3], typs[3], typs[5]), nil)
+ typs[124] = newSig(params(typs[7], typs[5]), nil)
+ typs[125] = newSig(params(typs[3], typs[3], typs[5]), params(typs[6]))
+ typs[126] = newSig(params(typs[3], typs[3]), params(typs[6]))
+ typs[127] = newSig(params(typs[7], typs[7]), params(typs[6]))
+ typs[128] = newSig(params(typs[3], typs[5], typs[5]), params(typs[5]))
+ typs[129] = newSig(params(typs[7], typs[5]), params(typs[5]))
+ typs[130] = newSig(params(typs[3], typs[5]), params(typs[5]))
+ typs[131] = newSig(params(typs[22], typs[22]), params(typs[22]))
+ typs[132] = newSig(params(typs[24], typs[24]), params(typs[24]))
+ typs[133] = newSig(params(typs[20]), params(typs[22]))
+ typs[134] = newSig(params(typs[20]), params(typs[24]))
+ typs[135] = newSig(params(typs[20]), params(typs[60]))
+ typs[136] = newSig(params(typs[22]), params(typs[20]))
+ typs[137] = types.Types[types.TFLOAT32]
+ typs[138] = newSig(params(typs[22]), params(typs[137]))
+ typs[139] = newSig(params(typs[24]), params(typs[20]))
+ typs[140] = newSig(params(typs[24]), params(typs[137]))
+ typs[141] = newSig(params(typs[60]), params(typs[20]))
+ typs[142] = newSig(params(typs[26], typs[26]), params(typs[26]))
+ typs[143] = newSig(nil, params(typs[5]))
+ typs[144] = newSig(params(typs[5], typs[5]), nil)
+ typs[145] = newSig(params(typs[5], typs[5], typs[5]), nil)
+ typs[146] = newSig(params(typs[7], typs[1], typs[5]), nil)
+ typs[147] = types.NewSlice(typs[7])
+ typs[148] = newSig(params(typs[7], typs[147]), nil)
+ typs[149] = newSig(params(typs[64], typs[64], typs[17]), nil)
+ typs[150] = newSig(params(typs[58], typs[58], typs[17]), nil)
+ typs[151] = newSig(params(typs[60], typs[60], typs[17]), nil)
+ typs[152] = newSig(params(typs[24], typs[24], typs[17]), nil)
+ typs[153] = newSig(params(typs[28], typs[28], typs[17]), nil)
+ typs[154] = types.NewArray(typs[0], 16)
+ typs[155] = newSig(params(typs[7], typs[60], typs[154], typs[28], typs[15], typs[64], typs[64]), params(typs[60]))
return typs[:]
}
--- /dev/null
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package types2
+
+import (
+ "cmd/compile/internal/syntax"
+ "fmt"
+)
+
+// This file should not be copied to go/types. See go.dev/issue/67477
+
+// RenameResult takes an array of (result) fields and an index, and if the indexed field
+// does not have a name and if the result in the signature also does not have a name,
+// then the signature and field are renamed to
+//
+// fmt.Sprintf("#rv%d", i+1)`
+//
+// the newly named object is inserted into the signature's scope,
+// and the object and new field name are returned.
+//
+// The intended use for RenameResult is to allow rangefunc to assign results within a closure.
+// This is a hack, as narrowly targeted as possible to discourage abuse.
+func (s *Signature) RenameResult(results []*syntax.Field, i int) (*Var, *syntax.Name) {
+ a := results[i]
+ obj := s.Results().At(i)
+
+ if !(obj.name == "" || obj.name == "_" && a.Name == nil || a.Name.Value == "_") {
+ panic("Cannot change an existing name")
+ }
+
+ pos := a.Pos()
+ typ := a.Type.GetTypeInfo().Type
+
+ name := fmt.Sprintf("#rv%d", i+1)
+ obj.name = name
+ s.scope.Insert(obj)
+ obj.setScopePos(pos)
+
+ tv := syntax.TypeAndValue{Type: typ}
+ tv.SetIsValue()
+
+ n := syntax.NewName(pos, obj.Name())
+ n.SetTypeInfo(tv)
+
+ a.Name = n
+
+ return obj, n
+}
{"runtime.interfaceSwitch", 1},
{"runtime.ifaceeq", 1},
{"runtime.efaceeq", 1},
- {"runtime.panicrangeexit", 1},
+ {"runtime.panicrangestate", 1},
{"runtime.deferrangefunc", 1},
{"runtime.rand32", 1},
{"runtime.makemap64", 1},
{"runtime.chanrecv2", 1},
{"runtime.chansend1", 1},
{"runtime.closechan", 1},
+ {"runtime.chanlen", 1},
+ {"runtime.chancap", 1},
{"runtime.writeBarrier", 0},
{"runtime.typedmemmove", 1},
{"runtime.typedmemclr", 1},
// been set and must not be clobbered.
}
-var rangeExitError = error(errorString("range function continued iteration after exit"))
+var rangeDoneError = error(errorString("range function continued iteration after loop body exit"))
+var rangePanicError = error(errorString("range function continued iteration after loop body panic"))
+var rangeExhaustedError = error(errorString("range function continued iteration after whole loop exit"))
+var rangeMissingPanicError = error(errorString("range function recovered a loop body panic and did not resume panicking"))
//go:noinline
-func panicrangeexit() {
- panic(rangeExitError)
+func panicrangestate(state int) {
+ const (
+ // These duplicate magic numbers in cmd/compile/internal/rangefunc
+ DONE = 0 // body of loop has exited in a non-panic way
+ PANIC = 2 // body of loop is either currently running, or has panicked
+ EXHAUSTED = 3 // iterator function return, i.e., sequence is "exhausted"
+ MISSING_PANIC = 4 // body of loop panicked but iterator function defer-recovered it away
+ )
+ switch state {
+ case DONE:
+ panic(rangeDoneError)
+ case PANIC:
+ panic(rangePanicError)
+ case EXHAUSTED:
+ panic(rangeExhaustedError)
+ case MISSING_PANIC:
+ panic(rangeMissingPanicError)
+ }
+ throw("unexpected state passed to panicrangestate")
}
// deferrangefunc is called by functions that are about to
--- /dev/null
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.rangefunc
+
+package race_test
+
+import (
+ "runtime"
+ "sync/atomic"
+ "testing"
+)
+
+type Seq2[T1, T2 any] func(yield func(T1, T2) bool)
+
+// ofSliceIndex returns a Seq over the elements of s. It is equivalent
+// to range s, except that it splits s into two halves and iterates
+// in two separate goroutines. This is racy if yield is racy, and yield
+// will be racy if it contains an early exit.
+func ofSliceIndex[T any, S ~[]T](s S) Seq2[int, T] {
+ return func(yield func(int, T) bool) {
+ c := make(chan bool, 2)
+ var done atomic.Bool
+ go func() {
+ for i := 0; i < len(s)/2; i++ {
+ if !done.Load() && !yield(i, s[i]) {
+ done.Store(true)
+ c <- false
+ }
+ }
+ c <- true
+ }()
+ go func() {
+ for i := len(s) / 2; i < len(s); i++ {
+ if !done.Load() && !yield(i, s[i]) {
+ done.Store(true)
+ c <- false
+ }
+ }
+ c <- true
+ return
+ }()
+ if !<-c {
+ return
+ }
+ <-c
+ }
+}
+
+// foo is racy, or not, depending on the value of v
+// (0-4 == racy, otherwise, not racy).
+func foo(v int) int64 {
+ var asum atomic.Int64
+ for i, x := range ofSliceIndex([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
+ if i%5 == v {
+ break
+ }
+ asum.Add(x) // don't race on asum
+ runtime.Gosched()
+ }
+ return 100 + asum.Load()
+}
+
+// TestRaceRangeFuncIterator races because x%5 can be equal to 4,
+// therefore foo can early exit.
+func TestRaceRangeFuncIterator(t *testing.T) {
+ x := foo(4)
+ t.Logf("foo(4)=%d", x)
+}
+
+// TestNoRaceRangeFuncIterator does not race because x%5 is never 5,
+// therefore foo's loop will not exit early, and this it will not race.
+func TestNoRaceRangeFuncIterator(t *testing.T) {
+ x := foo(5)
+ t.Logf("foo(5)=%d", x)
+}