cmd/compile: add cases for StringLen to prove

Tricky index-offset logic had been added for slices,
but not for strings.  This fixes that, and also adds
tests for same behavior in string/slice cases, and adds
a new test for code in prove that had been added but not
explicitly tested.

Fixes #76270.

Change-Id: Ibd92b89e944d86b7f30b4486a9008e6f1ac6af7d
Reviewed-on: https://go-review.googlesource.com/c/go/+/723980
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
David Chase 2025-11-24 15:00:11 -05:00
parent f1e376f342
commit 62cd044a79
3 changed files with 65 additions and 16 deletions

View file

@ -2040,14 +2040,14 @@ func (ft *factsTable) flowLimit(v *Value) {
// //
// slicecap - index >= slicelen - index >= K // slicecap - index >= slicelen - index >= K
// //
// Note that "index" is not useed for indexing in this pattern, but // Note that "index" is not used for indexing in this pattern, but
// in the motivating example (chunked slice iteration) it is. // in the motivating example (chunked slice iteration) it is.
func (ft *factsTable) detectSliceLenRelation(v *Value) { func (ft *factsTable) detectSliceLenRelation(v *Value) {
if v.Op != OpSub64 { if v.Op != OpSub64 {
return return
} }
if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) { if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpStringLen || v.Args[0].Op == OpSliceCap) {
return return
} }
@ -2070,9 +2070,9 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) {
continue continue
} }
var lenOffset *Value var lenOffset *Value
if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
lenOffset = ow.Args[1] lenOffset = ow.Args[1]
} else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
lenOffset = ow.Args[0] lenOffset = ow.Args[0]
} }
if lenOffset == nil || lenOffset.Op != OpConst64 { if lenOffset == nil || lenOffset.Op != OpConst64 {
@ -2332,7 +2332,7 @@ func unsignedSubUnderflows(a, b uint64) bool {
// iteration where the index is not directly compared to the length. // iteration where the index is not directly compared to the length.
// if isReslice, then delta can be equal to K. // if isReslice, then delta can be equal to K.
func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, isReslice bool) bool { func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, isReslice bool) bool {
if bound.Op != OpSliceLen && bound.Op != OpSliceCap { if bound.Op != OpSliceLen && bound.Op != OpStringLen && bound.Op != OpSliceCap {
return false return false
} }
@ -2367,9 +2367,9 @@ func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, i
} }
if ow := o.w; ow.Op == OpAdd64 { if ow := o.w; ow.Op == OpAdd64 {
var lenOffset *Value var lenOffset *Value
if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
lenOffset = ow.Args[1] lenOffset = ow.Args[1]
} else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
lenOffset = ow.Args[0] lenOffset = ow.Args[0]
} }
if lenOffset == nil || lenOffset.Op != OpConst64 { if lenOffset == nil || lenOffset.Op != OpConst64 {

View file

@ -420,7 +420,7 @@ func nobce2(a string) {
useString(a[i:]) // ERROR "Proved IsSliceInBounds$" useString(a[i:]) // ERROR "Proved IsSliceInBounds$"
} }
for i := int64(0); i < int64(len(a))-31337; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" for i := int64(0); i < int64(len(a))-31337; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
useString(a[i:]) // ERROR "Proved IsSliceInBounds$" useString(a[i:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed"
} }
for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Disproved Less64$" "Induction variable: limits \[0,\?\), increment 1$" for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Disproved Less64$" "Induction variable: limits \[0,\?\), increment 1$"
useString(a[i:]) useString(a[i:])

View file

@ -2669,6 +2669,55 @@ func issue76355(s []int, i int) int {
return 0 return 0
} }
func stringDotDotDot(s string) bool {
for i := 0; i < len(s)-2; i++ { // ERROR "Induction variable: limits \[0,[?][)], increment 1"
if s[i] == '.' && // ERROR "Proved IsInBounds"
s[i+1] == '.' && // ERROR "Proved IsInBounds"
s[i+2] == '.' { // ERROR "Proved IsInBounds"
return true
}
}
return false
}
func bytesDotDotDot(s []byte) bool {
for i := 0; i < len(s)-2; i++ { // ERROR "Induction variable"
if s[i] == '.' && // ERROR "Proved IsInBounds"
s[i+1] == '.' && // ERROR "Proved IsInBounds"
s[i+2] == '.' { // ERROR "Proved IsInBounds"
return true
}
}
return false
}
// detectSliceLenRelation matches the pattern where
// 1. v := slicelen - index, OR v := slicecap - index
// AND
// 2. index <= slicelen - K
// THEN
//
// slicecap - index >= slicelen - index >= K
func detectSliceLenRelation(s []byte) bool {
for i := 0; i <= len(s)-3; i++ { // ERROR "Induction variable"
v := len(s) - i
if v >= 3 { // ERROR "Proved Leq"
return true
}
}
return false
}
func detectStringLenRelation(s string) bool {
for i := 0; i <= len(s)-3; i++ { // ERROR "Induction variable"
v := len(s) - i
if v >= 3 { // ERROR "Proved Leq"
return true
}
}
return false
}
//go:noinline //go:noinline
func prove(x int) { func prove(x int) {
} }