From: Jorropo Date: Fri, 4 Jul 2025 06:54:14 +0000 (+0200) Subject: cmd/compile: check domination of loop return in both controls X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=8cd85e602a90cb97051fe95d5243557b566630e6;p=gostls13.git cmd/compile: check domination of loop return in both controls Fixes #74473 Change-Id: I72ff6b95955ae9407271508aa80f230dcf1b6c74 Reviewed-on: https://go-review.googlesource.com/c/go/+/685816 Reviewed-by: Mark Freeman Auto-Submit: Jorropo LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index dd1f39dbef..aa6cc48cac 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -37,19 +37,20 @@ type indVar struct { // - the minimum bound // - the increment value // - the "next" value (SSA value that is Phi'd into the induction variable every loop) +// - the header's edge returning from the body // // Currently, we detect induction variables that match (Phi min nxt), // with nxt being (Add inc ind). // If it can't parse the induction variable correctly, it returns (nil, nil, nil). -func parseIndVar(ind *Value) (min, inc, nxt *Value) { +func parseIndVar(ind *Value) (min, inc, nxt *Value, loopReturn Edge) { if ind.Op != OpPhi { return } if n := ind.Args[0]; (n.Op == OpAdd64 || n.Op == OpAdd32 || n.Op == OpAdd16 || n.Op == OpAdd8) && (n.Args[0] == ind || n.Args[1] == ind) { - min, nxt = ind.Args[1], n + min, nxt, loopReturn = ind.Args[1], n, ind.Block.Preds[0] } else if n := ind.Args[1]; (n.Op == OpAdd64 || n.Op == OpAdd32 || n.Op == OpAdd16 || n.Op == OpAdd8) && (n.Args[0] == ind || n.Args[1] == ind) { - min, nxt = ind.Args[0], n + min, nxt, loopReturn = ind.Args[0], n, ind.Block.Preds[1] } else { // Not a recognized induction variable. return @@ -111,13 +112,13 @@ func findIndVar(f *Func) []indVar { // See if this is really an induction variable less := true - init, inc, nxt := parseIndVar(ind) + init, inc, nxt, loopReturn := parseIndVar(ind) if init == nil { // We failed to parse the induction variable. Before punting, we want to check // whether the control op was written with the induction variable on the RHS // instead of the LHS. This happens for the downwards case, like: // for i := len(n)-1; i >= 0; i-- - init, inc, nxt = parseIndVar(limit) + init, inc, nxt, loopReturn = parseIndVar(limit) if init == nil { // No recognized induction variable on either operand continue @@ -145,6 +146,20 @@ func findIndVar(f *Func) []indVar { continue } + // startBody is the edge that eventually returns to the loop header. + var startBody Edge + switch { + case sdom.IsAncestorEq(b.Succs[0].b, loopReturn.b): + startBody = b.Succs[0] + case sdom.IsAncestorEq(b.Succs[1].b, loopReturn.b): + // if x { goto exit } else { goto entry } is identical to if !x { goto entry } else { goto exit } + startBody = b.Succs[1] + less = !less + inclusive = !inclusive + default: + continue + } + // Increment sign must match comparison direction. // When incrementing, the termination comparison must be ind />= limit. @@ -172,14 +187,14 @@ func findIndVar(f *Func) []indVar { // First condition: loop entry has a single predecessor, which // is the header block. This implies that b.Succs[0] is // reached iff ind < limit. - if len(b.Succs[0].b.Preds) != 1 { - // b.Succs[1] must exit the loop. + if len(startBody.b.Preds) != 1 { + // the other successor must exit the loop. continue } - // Second condition: b.Succs[0] dominates nxt so that + // Second condition: startBody.b dominates nxt so that // nxt is computed when inc < limit. - if !sdom.IsAncestorEq(b.Succs[0].b, nxt.Block) { + if !sdom.IsAncestorEq(startBody.b, nxt.Block) { // inc+ind can only be reached through the branch that enters the loop. continue } @@ -298,7 +313,7 @@ func findIndVar(f *Func) []indVar { nxt: nxt, min: min, max: max, - entry: b.Succs[0].b, + entry: startBody.b, flags: flags, }) b.Logf("found induction variable %v (inc = %v, min = %v, max = %v)\n", ind, inc, min, max) diff --git a/test/prove.go b/test/prove.go index 5a4be3a5d5..ef7690bbde 100644 --- a/test/prove.go +++ b/test/prove.go @@ -2319,6 +2319,17 @@ func resliceBytes(b []byte) byte { return 0 } +func issue74473(s []uint) { + i := 0 + for { + if i >= len(s) { // ERROR "Induction variable: limits \[0,\?\), increment 1$" + break + } + _ = s[i] // ERROR "Proved IsInBounds$" + i++ + } +} + //go:noinline func useInt(a int) { }