// 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)