Skip to content

Commit

Permalink
cmd/compile: fix //go:uintptrescapes for basic method calls
Browse files Browse the repository at this point in the history
The logic for keeping arguments alive for calls to //go:uintptrescapes
functions was only applying to direct function calls. This CL changes
it to also apply to direct method calls, which should address most
uses of Proc.Call and LazyProc.Call.

It's still an open question (#34684) whether other call forms (e.g.,
method expressions, or indirect calls via function values, method
values, or interfaces).

Fixes #34474.

Change-Id: I874f97145972b0e237a4c9e8926156298f4d6ce0
Reviewed-on: https://go-review.googlesource.com/c/go/+/198043
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
  • Loading branch information
mdempsky committed Nov 5, 2019
1 parent 6cbd737 commit b3bd7ab
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/gc/order.go
Expand Up @@ -411,7 +411,7 @@ func (o *Order) call(n *Node) {
n.Right = o.expr(n.Right, nil) // ODDDARG temp
o.exprList(n.List)

if n.Op != OCALLFUNC {
if n.Op != OCALLFUNC && n.Op != OCALLMETH {
return
}
keepAlive := func(i int) {
Expand Down
40 changes: 31 additions & 9 deletions test/uintptrescapes2.go
@@ -1,4 +1,4 @@
// errorcheck -0 -m -live
// errorcheck -0 -l -m -live

// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
Expand All @@ -13,31 +13,53 @@ import (
)

//go:uintptrescapes
//go:noinline
func F1(a uintptr) {} // ERROR "escaping uintptr"

//go:uintptrescapes
//go:noinline
func F2(a ...uintptr) {} // ERROR "escaping ...uintptr"

//go:uintptrescapes
//go:noinline
func F3(uintptr) {} // ERROR "escaping uintptr"

//go:uintptrescapes
//go:noinline
func F4(...uintptr) {} // ERROR "escaping ...uintptr"

func G() {
type T struct{}

//go:uintptrescapes
func (T) M1(a uintptr) {} // ERROR "escaping uintptr"

//go:uintptrescapes
func (T) M2(a ...uintptr) {} // ERROR "escaping ...uintptr" "leaking param: a"

func TestF1() {
var t int // ERROR "moved to heap"
F1(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to F1: .?autotmp" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}

func TestF3() {
var t2 int // ERROR "moved to heap"
F3(uintptr(unsafe.Pointer(&t2))) // ERROR "live at call to F3: .?autotmp"
F3(uintptr(unsafe.Pointer(&t2))) // ERROR "live at call to F3: .?autotmp" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}

func H() {
func TestM1() {
var t T
var v int // ERROR "moved to heap"
t.M1(uintptr(unsafe.Pointer(&v))) // ERROR "live at call to T.M1: .?autotmp" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}

func TestF2() {
var v int // ERROR "moved to heap"
F2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F2: .?autotmp" "escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}

func TestF4() {
var v2 int // ERROR "moved to heap"
F4(0, 1, uintptr(unsafe.Pointer(&v2)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F4: .?autotmp" "escapes to heap"
F4(0, 1, uintptr(unsafe.Pointer(&v2)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F4: .?autotmp" "escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}

func TestM2() {
var t T
var v int // ERROR "moved to heap"
t.M2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to T.M2: .?autotmp" "escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$"
}
63 changes: 63 additions & 0 deletions test/uintptrescapes3.go
@@ -0,0 +1,63 @@
// run

// Copyright 2019 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 that //go:uintptrescapes works for methods.

package main

import (
"fmt"
"runtime"
"unsafe"
)

var callback func()

//go:noinline
//go:uintptrescapes
func F(ptr uintptr) { callback() }

//go:noinline
//go:uintptrescapes
func Fv(ptrs ...uintptr) { callback() }

type T struct{}

//go:noinline
//go:uintptrescapes
func (T) M(ptr uintptr) { callback() }

//go:noinline
//go:uintptrescapes
func (T) Mv(ptrs ...uintptr) { callback() }

// Each test should pass uintptr(ptr) as an argument to a function call,
// which in turn should call callback. The callback checks that ptr is kept alive.
var tests = []func(ptr unsafe.Pointer){
func(ptr unsafe.Pointer) { F(uintptr(ptr)) },
func(ptr unsafe.Pointer) { Fv(uintptr(ptr)) },
func(ptr unsafe.Pointer) { T{}.M(uintptr(ptr)) },
func(ptr unsafe.Pointer) { T{}.Mv(uintptr(ptr)) },
}

func main() {
for i, test := range tests {
finalized := false

ptr := new([64]byte)
runtime.SetFinalizer(ptr, func(*[64]byte) {
finalized = true
})

callback = func() {
runtime.GC()
if finalized {
fmt.Printf("test #%d failed\n", i)
}
}
test(unsafe.Pointer(ptr))
}
}

0 comments on commit b3bd7ab

Please sign in to comment.