blob: f8d455b653fe96a837e75d3ea10a082939600c1b [file] [log] [blame]
// errorcheck -0 -d=escapealias=1
//go:build goexperiment.runtimefreegc
// Copyright 2025 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.
// Test recognizing certain patterns of usage,
// currently focused on whether a slice is aliased.
package escapealias
import "runtime"
// Basic examples.
//
// Some of these directly overlap with later tests below, but are presented at the start
// to help show the big picture (before going into more variations).
var alias []int
func basic1() {
// A simple append with no aliasing of s.
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
_ = s
}
func basic2() []int {
// The slice can escape.
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
return s
}
func basic3() {
// A simple example of s being aliased.
// We give up when we see the aliasing.
var s []int
alias = s
s = append(s, 0)
_ = s
}
func basic4() {
// The analysis is conservative, giving up on
// IR nodes it doesn't understand. It does not
// yet understand comparisons, for example.
var s []int
_ = s == nil
s = append(s, 0)
_ = s
}
func basic5() {
// We also give up if s is assigned to another variable.
var s []int
s2 := s
s2 = append(s2, 0)
_ = s2
}
func basic6() {
// A self-assigning append does not create an alias,
// so s is still unaliased when we reach the second append here.
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
s = append(s, 0) // ERROR "append using non-aliased slice"
_ = s
}
func basic7() {
// An append can be unaliased if it happens before aliasing.
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
alias = s
s = append(s, 0)
_ = s
}
func basic8() {
// Aliasing anywhere in a loop means we give up for the whole loop body,
// even if the aliasing is after the append in the loop body.
var s []int
for range 10 {
s = append(s, 0)
alias = s
}
_ = s
}
func basic9() {
// Aliases after a loop do not affect whether this is aliasing in the loop.
var s []int
for range 10 {
s = append(s, 0) // ERROR "append using non-aliased slice"
}
alias = s
_ = s
}
func basic10() {
// We track the depth at which a slice is declared vs. aliased,
// which helps for example with nested loops.
// In this example, the aliasing occurs after both loops are done.
var s []int
for range 10 {
for range 10 {
s = append(s, 0) // ERROR "append using non-aliased slice"
}
}
alias = s
}
func basic11() {
// In contrast, here the aliasing occurs in the outer loop body.
var s []int
for range 10 {
for range 10 {
s = append(s, 0)
}
alias = s
}
}
// Some variations on single appends.
func singleAppend1() []int {
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
return s
}
func singleAppend2() {
var s []int
alias = s
s = append(s, 0)
}
func singleAppend3() {
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
alias = s
}
func singleAppend4() {
var s []int
p := &s
_ = p
s = append(s, 0)
}
func singleAppend5(s []int) {
s = append(s, 0)
}
func singleAppend6() {
var s []int
alias, _ = s, 0
s = append(s, 0)
}
// Examples with variations on slice declarations.
func sliceDeclaration1() {
s := []int{}
s = append(s, 0) // ERROR "append using non-aliased slice"
}
func sliceDeclaration2() {
s := []int{1, 2, 3}
s = append(s, 0) // ERROR "append using non-aliased slice"
}
func sliceDeclaration3() {
s := make([]int, 3)
s = append(s, 0) // ERROR "append using non-aliased slice"
}
func sliceDeclaration4() {
s := []int{}
alias = s
s = append(s, 0)
}
func sliceDeclaration5() {
s := []int{1, 2, 3}
alias = s
s = append(s, 0)
}
func sliceDeclaration6() {
s := make([]int, 3)
alias = s
s = append(s, 0)
}
func sliceDeclaration7() {
s, x := []int{}, 0
s = append(s, x) // ERROR "append using non-aliased slice"
}
// Basic loops. First, a single loop.
func loops1a() {
var s []int
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
func loops1b() {
var s []int
for i := range 10 {
alias = s
s = append(s, i)
}
}
func loops1c() {
var s []int
for i := range 10 {
s = append(s, i)
alias = s
}
}
func loops1d() {
var s []int
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
alias = s
}
func loops1e() {
var s []int
for i := range use(s) {
s = append(s, i)
}
}
func loops1f() {
var s []int
for i := range use(s) {
s = append(s, i)
}
s = append(s, 0)
}
// Nested loops with s declared outside the loops.
func loops2a() {
var s []int
for range 10 {
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
}
func loops2b() {
var s []int
for range 10 {
alias = s
for i := range 10 {
s = append(s, i)
}
}
}
func loops2c() {
var s []int
for range 10 {
for i := range 10 {
s = append(s, i)
}
alias = s
}
}
func loops2d() {
var s []int
for range 10 {
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
alias = s
}
func loops2e() {
var s []int
for range use(s) {
for i := range 10 {
s = append(s, i)
}
s = append(s, 0)
}
s = append(s, 0)
}
func loops2f() {
var s []int
for range 10 {
for i := range use(s) {
s = append(s, i)
}
s = append(s, 0)
}
s = append(s, 0)
}
// Nested loops with s declared inside the first loop.
func loops3a() {
for range 10 {
var s []int
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
}
func loops3b() {
for range 10 {
var s []int
for i := range 10 {
alias = s
s = append(s, i)
}
}
}
func loops3c() {
for range 10 {
var s []int
for i := range 10 {
s = append(s, i)
alias = s
}
}
}
func loops3d() {
for range 10 {
var s []int
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
alias = s
}
}
func loops3e() {
for range 10 {
var s []int
for i := range use(s) {
s = append(s, i)
}
s = append(s, 0)
}
}
// Loops using OFOR instead of ORANGE.
func loops4a() {
var s []int
for i := 0; i < 10; i++ {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
func loops4b() {
var s []int
for i := 0; i < 10; i++ {
alias = s
s = append(s, i)
}
}
func loops4c() {
var s []int
for i := 0; i < 10; i++ {
s = append(s, i)
alias = s
}
}
func loops4d() {
var s []int
for i := 0; i < 10; i++ {
s = append(s, i) // ERROR "append using non-aliased slice"
}
alias = s
}
// Loops with some initialization variations.
func loopsInit1() {
var i int
for s := []int{}; i < 10; i++ {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
func loopsInit2() {
var i int
for s := []int{}; i < 10; i++ {
s = append(s, i)
alias = s
}
}
func loopsInit3() {
var i int
for s := []int{}; i < 10; i++ {
for range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
}
func loopsInit5() {
var i int
for s := []int{}; i < 10; i++ {
for range 10 {
s = append(s, i)
alias = s
}
}
}
func loopsInit5b() {
var i int
for s := []int{}; i < 10; i++ {
for range 10 {
s = append(s, i)
}
alias = s
}
}
func loopsInit6() {
for range 10 {
var i int
for s := []int{}; i < 10; i++ {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
}
func loopsInit7() {
for range 10 {
var i int
for s := []int{}; i < 10; i++ {
s = append(s, i)
alias = s
}
}
}
// Some initialization variations with use of s in the for or range.
func loopsInit8() {
var s []int
for use(s) == 0 {
s = append(s, 0)
}
}
func loopsInit9() {
for s := []int{}; use(s) == 0; {
s = append(s, 0)
}
}
func loopsInit10() {
for s := []int{}; ; use(s) {
s = append(s, 0)
}
}
func loopsInit11() {
var s [][]int
for _, s2 := range s {
s = append(s, s2)
}
}
// Examples of calling functions that get inlined,
// starting with a simple pass-through function.
// TODO(thepudds): we handle many of these starting in https://go.dev/cl/712422
func inlineReturn(param []int) []int {
return param
}
func inline1a() {
var s []int
s = inlineReturn(s)
s = append(s, 0)
}
func inline1b() {
var s []int
for range 10 {
s = inlineReturn(s)
s = append(s, 0)
}
}
func inline1c() {
var s []int
for range 10 {
s = inlineReturn(s)
alias = s
s = append(s, 0)
}
}
func inline1d() {
var s []int
for range 10 {
s = inlineReturn(s)
s = append(s, 0)
alias = s
}
}
// Examples with an inlined function that uses append.
func inlineAppend(param []int) []int {
param = append(param, 0)
// TODO(thepudds): could in theory also handle a direct 'return append(param, 0)'
return param
}
func inline2a() {
var s []int
s = inlineAppend(s)
s = append(s, 0)
}
func inline2b() {
var s []int
for range 10 {
s = inlineAppend(s)
s = append(s, 0)
}
}
func inline2c() {
var s []int
for range 10 {
s = inlineAppend(s)
alias = s
s = append(s, 0)
}
}
func inline2d() {
var s []int
for range 10 {
s = inlineAppend(s)
s = append(s, 0)
alias = s
}
}
// Examples calling non-inlined functions that do and do not escape.
var sink interface{}
//go:noinline
func use(s []int) int { return 0 } // s content does not escape
//go:noinline
func escape(s []int) int { sink = s; return 0 } // s content escapes
func call1() {
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
use(s)
}
// TODO(thepudds): OK to disallow this for now, but would be nice to allow this given use(s) is non-escaping.
func call2() {
var s []int
use(s)
s = append(s, 0)
}
func call3() {
var s []int
s = append(s, use(s))
}
func call4() {
var s []int
for i := range 10 {
s = append(s, i)
use(s)
}
}
func callEscape1() {
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
escape(s)
}
func callEscape2() {
var s []int
escape(s)
s = append(s, 0)
}
func callEscape3() {
var s []int
s = append(s, escape(s))
}
func callEscape4() {
var s []int
for i := range 10 {
s = append(s, i)
escape(s)
}
}
// Examples of some additional expressions we understand.
func expr1() {
var s []int
_ = len(s)
_ = cap(s)
s = append(s, 0) // ERROR "append using non-aliased slice"
}
// Examples of some expressions or statements we do not understand.
// Some of these we could handle in the future, but some likely not.
func notUnderstood1() {
var s []int
s = append(s[:], 0)
}
func notUnderstood2() {
// Note: we must be careful if we analyze slice expressions.
// See related comment about slice expressions in (*aliasAnalysis).analyze.
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
s = s[1:] // s no longer points to the base of the heap object.
s = append(s, 0)
}
func notUnderstood3() {
// The first append is currently the heart of slices.Grow.
var s []int
n := 1000
s = append(s[:cap(s)], make([]int, n)...)[:len(s)]
s = append(s, 0)
}
func notUnderstood4() []int {
// A return statement could be allowed to use the slice in a loop
// because we cannot revisit the append once we return.
var s []int
for i := range 10 {
s = append(s, 0)
if i > 5 {
return s
}
}
return s
}
func notUnderstood5() {
// AddCleanup is an example function call that we do not understand.
// See related comment about specials in (*aliasAnalysis).analyze.
var s []int
runtime.AddCleanup(&s, func(int) {}, 0)
s = append(s, 0)
}
// Examples with closures.
func closure1() {
var s []int // declared outside the closure
f := func() {
for i := range 10 {
s = append(s, i)
}
}
_ = f // avoid calling f, which would just get inlined
}
// TODO(thepudds): it's probably ok that we currently allow this. Could conservatively
// disallow if needed.
func closure2() {
f := func() {
var s []int // declared inside the closure
for i := range 10 {
s = append(s, i) // ERROR "append using non-aliased slice"
}
}
_ = f // avoid calling f, which would just get inlined
}
// Examples with goto and labels.
func goto1() {
var s []int
label:
s = append(s, 0)
alias = s
goto label
}
func goto2() {
var s []int
s = append(s, 0) // ERROR "append using non-aliased slice"
alias = s
label:
goto label
}
func goto3() {
var s []int
label:
for i := range 10 {
s = append(s, i)
}
goto label
}
func break1() {
var s []int
label:
for i := range 10 {
s = append(s, i)
break label
}
}
// Examples with iterators.
func collect[E any](seq Seq[E]) []E {
var result []E
for v := range seq {
result = append(result, v)
}
return result
}
func count(yield func(int) bool) {
for i := range 10 {
if !yield(i) {
return
}
}
}
func iteratorUse1() {
var s []int
s = collect(count)
_ = s
}
func iteratorUse2() {
var s []int
s = collect(count)
s = append(s, 0)
}
type Seq[E any] func(yield func(E) bool)