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