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