Source file test/fixedbugs/issue81612.go

     1  // run
     2  
     3  // Copyright 2026 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  // The stack-allocated slice backing store optimization must not kick in
     8  // when the address of a field of a slice element escapes. &s[i].f points
     9  // into s's backing store just like &s[i] does, so the backing store has
    10  // to be moved to the heap.
    11  
    12  package main
    13  
    14  type def struct {
    15  	ID int64
    16  }
    17  
    18  //go:noinline
    19  func resolve() *int64 {
    20  	defs := []def{{77}}
    21  	var matches []def
    22  	for _, d := range defs {
    23  		matches = append(matches, d)
    24  	}
    25  	switch len(matches) {
    26  	case 1:
    27  		return &matches[0].ID
    28  	default:
    29  		candidates := make([]int64, 0, len(matches))
    30  		for _, m := range matches {
    31  			candidates = append(candidates, m.ID)
    32  		}
    33  		return &candidates[0]
    34  	}
    35  }
    36  
    37  var sink int64
    38  var escaped []def
    39  
    40  // resolveNoRange reaches the same bug without ranging over the slice:
    41  // here the exclusive->nonexclusive transition is the assignment to
    42  // escaped, which is on a different path than the &matches[0].ID.
    43  //
    44  //go:noinline
    45  func resolveNoRange(n int) *int64 {
    46  	var matches []def
    47  	for i := range n {
    48  		matches = append(matches, def{int64(77 + i)})
    49  	}
    50  	if len(matches) == 1 {
    51  		return &matches[0].ID
    52  	}
    53  	escaped = matches
    54  	return &sink
    55  }
    56  
    57  type elem struct{ a [4]int64 }
    58  
    59  var escapedElems []elem
    60  
    61  // resolveViaSliceArr reaches the same bug through a slice of an array
    62  // field: &t[0].a[:][0] points into t's backing store just as &t[0].a[0]
    63  // does, because the OSLICEARR shares storage with the array.
    64  //
    65  //go:noinline
    66  func resolveViaSliceArr(n int) *int64 {
    67  	var t []elem
    68  	for i := range n {
    69  		t = append(t, elem{a: [4]int64{int64(77 + i)}})
    70  	}
    71  	if len(t) == 1 {
    72  		return &t[0].a[:][0]
    73  	}
    74  	escapedElems = t
    75  	return &sink
    76  }
    77  
    78  // clobber overwrites the stack frames below main's.
    79  //
    80  //go:noinline
    81  func clobber(n int) {
    82  	var buf [256]int64
    83  	for i := range buf {
    84  		buf[i] = 0xdeadbeef
    85  	}
    86  	if n > 0 {
    87  		clobber(n - 1)
    88  	}
    89  	sink = buf[n&255]
    90  }
    91  
    92  func main() {
    93  	p := resolve()
    94  	if *p != 77 {
    95  		println("resolve: before clobber:", *p)
    96  		panic("wrong value")
    97  	}
    98  	clobber(4)
    99  	if *p != 77 {
   100  		println("resolve: after clobber:", *p)
   101  		panic("value destroyed by unrelated stack traffic")
   102  	}
   103  
   104  	p = resolveNoRange(1)
   105  	if *p != 77 {
   106  		println("resolveNoRange: before clobber:", *p)
   107  		panic("wrong value")
   108  	}
   109  	clobber(4)
   110  	if *p != 77 {
   111  		println("resolveNoRange: after clobber:", *p)
   112  		panic("value destroyed by unrelated stack traffic")
   113  	}
   114  
   115  	p = resolveViaSliceArr(1)
   116  	if *p != 77 {
   117  		println("resolveViaSliceArr: before clobber:", *p)
   118  		panic("wrong value")
   119  	}
   120  	clobber(4)
   121  	if *p != 77 {
   122  		println("resolveViaSliceArr: after clobber:", *p)
   123  		panic("value destroyed by unrelated stack traffic")
   124  	}
   125  }
   126  

View as plain text