Skip to content

Commit

Permalink
[release-branch.go1.17] runtime: count spill slot for frame size at f…
Browse files Browse the repository at this point in the history
…inalizer call

The finalizer is called using reflectcall. When register ABI is
used, the finalizer's argument is passed in register(s). But the
frame size calculation does not include the spill slot. When the
argument actually spills, it may clobber the caller's stack frame.
This CL fixes it.

Updates #51457.
Fixes #51458.

Change-Id: Ibcc7507c518ba65c1c5a7759e5cab0ae3fc7efce
Reviewed-on: https://go-review.googlesource.com/c/go/+/389574
Trust: Cherry Mui <cherryyz@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
(cherry picked from commit 58804ea)
Reviewed-on: https://go-review.googlesource.com/c/go/+/389794
  • Loading branch information
cherrymui authored and heschi committed Mar 14, 2022
1 parent 7dd10d4 commit 88be85f
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 15 deletions.
24 changes: 9 additions & 15 deletions src/runtime/mfinal.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,21 +187,15 @@ func runfinq() {
f := &fb.fin[i-1]

var regs abi.RegArgs
var framesz uintptr
if argRegs > 0 {
// The args can always be passed in registers if they're
// available, because platforms we support always have no
// argument registers available, or more than 2.
//
// But unfortunately because we can have an arbitrary
// amount of returns and it would be complex to try and
// figure out how many of those can get passed in registers,
// just conservatively assume none of them do.
framesz = f.nret
} else {
// Need to pass arguments on the stack too.
framesz = unsafe.Sizeof((interface{})(nil)) + f.nret
}
// The args may be passed in registers or on stack. Even for
// the register case, we still need the spill slots.
// TODO: revisit if we remove spill slots.
//
// Unfortunately because we can have an arbitrary
// amount of returns and it would be complex to try and
// figure out how many of those can get passed in registers,
// just conservatively assume none of them do.
framesz := unsafe.Sizeof((interface{})(nil)) + f.nret
if framecap < framesz {
// The frame does not contain pointers interesting for GC,
// all not yet finalized objects are stored in finq.
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/mfinal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ func TestFinalizerType(t *testing.T) {
{func(x *int) interface{} { return Tintptr(x) }, func(v *int) { finalize(v) }},
{func(x *int) interface{} { return (*Tint)(x) }, func(v *Tint) { finalize((*int)(v)) }},
{func(x *int) interface{} { return (*Tint)(x) }, func(v Tinter) { finalize((*int)(v.(*Tint))) }},
// Test case for argument spill slot.
// If the spill slot was not counted for the frame size, it will (incorrectly) choose
// call32 as the result has (exactly) 32 bytes. When the argument actually spills,
// it clobbers the caller's frame (likely the return PC).
{func(x *int) interface{} { return x }, func(v interface{}) [4]int64 {
print() // force spill
finalize(v.(*int))
return [4]int64{}
}},
}

for i, tt := range finalizerTests {
Expand Down

0 comments on commit 88be85f

Please sign in to comment.