// run // Copyright 2026 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. // The stack-allocated slice backing store optimization must not kick in // when the address of a field of a slice element escapes. &s[i].f points // into s's backing store just like &s[i] does, so the backing store has // to be moved to the heap. package main type def struct { ID int64 } //go:noinline func resolve() *int64 { defs := []def{{77}} var matches []def for _, d := range defs { matches = append(matches, d) } switch len(matches) { case 1: return &matches[0].ID default: candidates := make([]int64, 0, len(matches)) for _, m := range matches { candidates = append(candidates, m.ID) } return &candidates[0] } } var sink int64 var escaped []def // resolveNoRange reaches the same bug without ranging over the slice: // here the exclusive->nonexclusive transition is the assignment to // escaped, which is on a different path than the &matches[0].ID. // //go:noinline func resolveNoRange(n int) *int64 { var matches []def for i := range n { matches = append(matches, def{int64(77 + i)}) } if len(matches) == 1 { return &matches[0].ID } escaped = matches return &sink } type elem struct{ a [4]int64 } var escapedElems []elem // resolveViaSliceArr reaches the same bug through a slice of an array // field: &t[0].a[:][0] points into t's backing store just as &t[0].a[0] // does, because the OSLICEARR shares storage with the array. // //go:noinline func resolveViaSliceArr(n int) *int64 { var t []elem for i := range n { t = append(t, elem{a: [4]int64{int64(77 + i)}}) } if len(t) == 1 { return &t[0].a[:][0] } escapedElems = t return &sink } // clobber overwrites the stack frames below main's. // //go:noinline func clobber(n int) { var buf [256]int64 for i := range buf { buf[i] = 0xdeadbeef } if n > 0 { clobber(n - 1) } sink = buf[n&255] } func main() { p := resolve() if *p != 77 { println("resolve: before clobber:", *p) panic("wrong value") } clobber(4) if *p != 77 { println("resolve: after clobber:", *p) panic("value destroyed by unrelated stack traffic") } p = resolveNoRange(1) if *p != 77 { println("resolveNoRange: before clobber:", *p) panic("wrong value") } clobber(4) if *p != 77 { println("resolveNoRange: after clobber:", *p) panic("value destroyed by unrelated stack traffic") } p = resolveViaSliceArr(1) if *p != 77 { println("resolveViaSliceArr: before clobber:", *p) panic("wrong value") } clobber(4) if *p != 77 { println("resolveViaSliceArr: after clobber:", *p) panic("value destroyed by unrelated stack traffic") } }