Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

syscall: (*Proc).Call does not keep arguments live #16035

Closed
ToadKing opened this issue Jun 10, 2016 · 33 comments
Closed

syscall: (*Proc).Call does not keep arguments live #16035

ToadKing opened this issue Jun 10, 2016 · 33 comments

Comments

@ToadKing
Copy link

@ToadKing ToadKing commented Jun 10, 2016

The following test program should give the output of the ImageState value for Windows, which for most systems is IMAGE_STATE_COMPLETE. However, when trying to get the value into a slice it is possible for GC to mess up the slice and not get the updated value from the syscall.

Test program:

package main

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

var (
    advapi32 = syscall.NewLazyDLL("advapi32.dll")

    regGetValue = advapi32.NewProc("RegGetValueW")
)

const (
    HKEY_LOCAL_MACHINE = 0x80000002

    RRF_RT_REG_SZ = 0x00000002
)

func RegGetValue(hKey uintptr, lpSubKey *uint16, lpValue *uint16, dwFlags uint32, pdwType *uint32) (string, uint32, error) {
    var buf [1024]uint16
    bufSizeBytes := uint32(1024 * 2)
    runtime.GC() // Everything works correctly if this line is removed
    ret, _, callErr := regGetValue.Call(uintptr(hKey), uintptr(unsafe.Pointer(lpSubKey)), uintptr(unsafe.Pointer(lpValue)), uintptr(dwFlags), uintptr(unsafe.Pointer(pdwType)), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufSizeBytes)))
    if ret != 0 {
        return "", bufSizeBytes, callErr
    }
    return syscall.UTF16ToString(buf[:]), bufSizeBytes, nil
}

func main() {
    key, _ := syscall.UTF16PtrFromString("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Setup\\\\State")
    value, _ := syscall.UTF16PtrFromString("ImageState")
    str, _, _ := RegGetValue(
        HKEY_LOCAL_MACHINE,
        key,
        value,
        RRF_RT_REG_SZ,
        nil,
    )
    fmt.Printf("str: %s\n", str)
}

Expected response: str: IMAGE_STATE_COMPLETE

Actual response: str:

If the regGetValue.Call(...) line is replace with syscall.Syscall9(regGetValue.Addr(), ...) then the code works as intended.

Environment:

go version go1.6.2 windows/amd64

set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=
set GORACE=
set GOROOT=C:\go162
set GOTOOLDIR=C:\go162\pkg\tool\windows_amd64
set GO15VENDOREXPERIMENT=1
set CC=gcc
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0
set CXX=g++
set CGO_ENABLED=1
@ToadKing ToadKing changed the title (*syscall.Proc).Call has weird behavior with slices and GC syscall: (*Proc).Call has weird behavior with slices and GC Jun 10, 2016
@ToadKing
Copy link
Author

@ToadKing ToadKing commented Jun 10, 2016

OS version: Windows 10 [10.0.10586]

The bug also happens in go1.7beta1

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 10, 2016

The basic problem is that there is nothing keeping lpSubKey and lpValue live. The call to regGetValue.Call doesn't keep them live, because they are converted to uintptr, so the pointer values are gone. Since they are not live, the garbage collector can collect them.

This doesn't happen when you call syscall.Syscall9 directly because there is an exception for functions implemented in assembler: they explicitly keep uintptr values live. This was implemented in https://golang.org/cl/18584 for issue #13372.

Basically, the syscall.Proc.Call method is hard to use correctly. Code has to explicitly ensure that values remain live after the call.

@ianlancetaylor ianlancetaylor changed the title syscall: (*Proc).Call has weird behavior with slices and GC syscall: (*Proc).Call does not keep arguments live Jun 10, 2016
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 10, 2016

The immediate workaround for your program is to add something like this after the call to regGetValue.Call:

runtime.KeepAlive(lpSubKey)
runtime.KeepAlive(lpValue)

(the runtime.KeepAlive function is new in Go 1.7).

I'm not sure what to do about the general case, though. Right now the Call method is a hazard.

@ianlancetaylor ianlancetaylor added this to the Go1.7 milestone Jun 10, 2016
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 10, 2016

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jun 12, 2016

I'm not sure what to do about the general case, though. Right now the Call method is a hazard.

It has always been that way - when you use unsafe package you need to know what you're doing. syscall.Syscall and syscall.(*Proc).Call is how we call external code on windows - there is no way around it.

Use of syscall.Syscall and syscall.(*Proc).Call has always been undocumented. From what I understand, we didn't document these rules because we still have not decided what should or should not be allowed.

@ianlancetaylor I am surprised you are recommending to use new runtime.KeepAlive function. I specifically asked if runtime.KeepAlive is suitable for such things (see https://groups.google.com/forum/#!searchin/golang-dev/KeepAlive$20runtime/golang-dev/AlMCfgQLkdo/PSYsA9x3DQAJ), but was told that runtime.KeepAlive is to control when finalizers run, not to control garbage collector.

I am not sure what you're planing to do on this issue. Especially considering it is marked as go1.7. I am sure we have similar issues already, for example #4318, #6907

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 12, 2016

@alexbrainman I don't see an inconsistency in the use of runtime.KeepAlive. In the e-mail thread you link to, you asked whether runtime.KeepAlive would provide access to those bytes until the program exits. It won't. Matthew said that it's not an malloc/free mechanism, and that is correct.

My suggestion here is not to use runtime.KeepAlive to control the garbage collector. It's to use it to control when values are live. The problem here is that lpSubKey and lpValue are not live throughout RegGetValue, which means they can be collected. Adding runtime.KeepAlive after the call to regGetValue.Call will keep them live past the point of the call, so they won't be collected.

It's very much the same kind of thing as finalizers, although there is no actual finalizer. Think of an implicit finalizer being discarding the values. runtime.KeepAlive will prevent that implicit finalizer from running.

What do you think of https://golang.org/cl/24030?

@gopherbot
Copy link

@gopherbot gopherbot commented Jun 12, 2016

CL https://golang.org/cl/24030 mentions this issue.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jun 12, 2016

My suggestion here is not to use runtime.KeepAlive to control the garbage collector. It's to use it to control when values are live.

Does runtime.KeepAlive(lpSubKey) also means that address of what lpSubKey points to will not change until runtime.KeepAlive(lpSubKey) is executed? If yes, then I think runtime.KeepAlive is not a good name for that function. If runtime.KeepAlive(lpSubKey) does not tell GC to keep whatever pointed by lpSubKey at the same address, then I don't see how calling runtime.KeepAlive(lpSubKey) in the example above is helpful - if GC moves the object before regGetValue.Call returns, then regGetValue.Call could be corrupting Go's memory.

What do you think of https://golang.org/cl/24030?

I am trying to understand what runtime.KeepAlive does before I can decide on your CL. Lets continue that conversation here.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 12, 2016

No, runtime.KeepAlive does not ensure that the address of the memory does not change. It only ensures that the memory is not collected. That is a fair question. We have no way in Go to say that the address of memory does not change, and we do not want to add any such feature.

However, my current thinking is that it is not an issue here, because 1) currently memory never moves anyhow; 2) if we ever do permit memory to move, the syscall.Syscall function will require special treatment anyhow. Any pointer passed into syscall.Syscall, regardless of whether it is converted to uintptr, must not move until syscall.Syscall returns.

So the point of the runtime.KeepAlive is not to keep the pointer in one place. When and if that becomes important, we will have to make that a feature of syscall.Syscall itself. The point of the runtime.KeepAlive is to keep the pointer from being collected too early--where, as the original example shows, too early actually means before we call syscall.Syscall at all.

@ToadKing
Copy link
Author

@ToadKing ToadKing commented Jun 12, 2016

Would it be possible to apply the special behavior for uintptr(unsafe.Pointer(...)) to syscall.(*Proc).Call instead of just syscall.Syscall? Without it syscall.(*Proc).Call is just secretly dangerous to use for any call that uses pointers and in all honesty should probably be removed in that case. That or make sure go vet catches usage like these since as of the 1.7 beta it does not.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 12, 2016

Possible? Yes. But a bit of risky move at this stage of the release. The special handling of syscall.Syscall is implemented based on the fact that syscall.Syscall is written in assembler. In assembler there is no way to force a pointer to be live, so there is some argument in support of doing it in the compiler.

Of course, although Proc.Call and LazyProc.Call are written in Go, it's not clear to me that there is a good way that they could keep their pointers live.

But now that I think about it, because Proc.Call calls syscall.Syscall directly, it is possible that the compiler support for syscall.Syscall is ensuring that Proc.Call doesn't suffer from this problem. It may be that only LazyProc.Call suffers from it, because it does not call syscall.Syscall, but instead calls Proc.Call. Could somebody with a Windows system see if they can recreate the problem in the original report, and then see if they can still recreate it using Proc rather than LazyProc? (That is, I think, call LoadDLL rather than NewLazyDLL).

@ToadKing
Copy link
Author

@ToadKing ToadKing commented Jun 12, 2016

The issue does not appear to happen when using LoadDLL/FindProc.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 12, 2016

Thanks. Could somebody test whether https://golang.org/cl/24031 fixes the problem?

@gopherbot
Copy link

@gopherbot gopherbot commented Jun 12, 2016

CL https://golang.org/cl/24031 mentions this issue.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jun 13, 2016

  1. currently memory never moves anyhow;

I don't think this is true. If Go object lives on goroutine stack, the stack moves and object moves with the stack.

In fact that is what, I think, is happening here.

I have changed the program to handle syscall error properly first. So I end up with this:

c:\Users\Alex\dev\src\issues\issue16035>type main.go
package main

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

var (
        advapi32 = syscall.NewLazyDLL("advapi32.dll")

        regGetValue = advapi32.NewProc("RegGetValueW")
)

const (
        HKEY_LOCAL_MACHINE = 0x80000002

        RRF_RT_REG_SZ = 0x00000002
)

func RegGetValue(hKey uintptr, lpSubKey *uint16, lpValue *uint16, dwFlags uint32, pdwType *uint32) (string, uint32, error) {
        var buf [1024]uint16
        bufSizeBytes := uint32(1024 * 2)
        runtime.GC() // Everything works correctly if this line is removed
        ret, _, _ := regGetValue.Call(uintptr(hKey), uintptr(unsafe.Pointer(lpSubKey)), uintptr(unsafe.Pointer(lpValue)), uintptr(dwFlags), uintptr(unsafe.Pointer(pdwType)), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufSizeBytes)))
        if ret != 0 {
                return "", bufSizeBytes, syscall.Errno(ret)
        }
        return syscall.UTF16ToString(buf[:]), bufSizeBytes, nil
}

func main() {
        key, _ := syscall.UTF16PtrFromString("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Setup\\\\State")
        value, _ := syscall.UTF16PtrFromString("ImageState")
        str, _, err := RegGetValue(
                HKEY_LOCAL_MACHINE,
                key,
                value,
                RRF_RT_REG_SZ,
                nil,
        )
        if err != nil {
                fmt.Printf("RegGetValue failed: %v\n", err)
                return
        }
        fmt.Printf("str: %s\n", str)
}

c:\Users\Alex\dev\src\issues\issue16035>hg st

c:\Users\Alex\dev\src\issues\issue16035>

Then I run the program:

c:\Users\Alex\dev\src\issues\issue16035>go run -gcflags="-m" main.go
# command-line-arguments
.\main.go:28: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:26: RegGetValue &buf[0] does not escape
.\main.go:26: RegGetValue &bufSizeBytes does not escape
.\main.go:26: RegGetValue ... argument does not escape
.\main.go:30: RegGetValue buf does not escape
.\main.go:44: err escapes to heap
.\main.go:47: str escapes to heap
.\main.go:44: main ... argument does not escape
.\main.go:47: main ... argument does not escape
<autogenerated>:1: leaking param: .this
str:

c:\Users\Alex\dev\src\issues\issue16035>go run -gcflags="-m -N" main.go
# command-line-arguments
.\main.go:28: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:26: RegGetValue &buf[0] does not escape
.\main.go:26: RegGetValue &bufSizeBytes does not escape
.\main.go:26: RegGetValue ... argument does not escape
.\main.go:30: RegGetValue buf does not escape
.\main.go:44: err escapes to heap
.\main.go:47: str escapes to heap
.\main.go:44: main ... argument does not escape
.\main.go:47: main ... argument does not escape
<autogenerated>:1: leaking param: .this
str: IMAGE_STATE_COMPLETE

c:\Users\Alex\dev\src\issues\issue16035>

(note how program succeeds if I use -N flag).

Then I changed it slightly, so that buf escapes to heap, and run it again:

c:\Users\Alex\dev\src\issues\issue16035>hg diff
diff -r 5a2ff4da45a8 main.go
--- a/main.go   Mon Jun 13 14:33:13 2016 +1000
+++ b/main.go   Mon Jun 13 14:43:34 2016 +1000
@@ -22,6 +22,7 @@
 func RegGetValue(hKey uintptr, lpSubKey *uint16, lpValue *uint16, dwFlags uint3
2, pdwType *uint32) (string, uint32, error) {
        var buf [1024]uint16
        bufSizeBytes := uint32(1024 * 2)
+       fmt.Printf("%p\n", &buf)
        runtime.GC() // Everything works correctly if this line is removed
        ret, _, _ := regGetValue.Call(uintptr(hKey), uintptr(unsafe.Pointer(lpSubKey)), uintptr(unsafe.Pointer(lpValue)), uintptr(dwFlags), uintptr(unsafe.Pointer(pdwType)), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufSizeBytes)))
        if ret != 0 {

c:\Users\Alex\dev\src\issues\issue16035>go run -gcflags="-m" main.go
# command-line-arguments
.\main.go:25: &buf escapes to heap
.\main.go:25: &buf escapes to heap
.\main.go:23: moved to heap: buf
.\main.go:29: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:25: RegGetValue ... argument does not escape
.\main.go:27: RegGetValue &buf[0] does not escape
.\main.go:27: RegGetValue &bufSizeBytes does not escape
.\main.go:27: RegGetValue ... argument does not escape
.\main.go:31: RegGetValue buf does not escape
.\main.go:45: err escapes to heap
.\main.go:48: str escapes to heap
.\main.go:45: main ... argument does not escape
.\main.go:48: main ... argument does not escape
<autogenerated>:1: leaking param: .this
0xc042066000
str: IMAGE_STATE_COMPLETE

c:\Users\Alex\dev\src\issues\issue16035>go run -gcflags="-m -N" main.go
# command-line-arguments
.\main.go:25: &buf escapes to heap
.\main.go:25: &buf escapes to heap
.\main.go:23: moved to heap: buf
.\main.go:29: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:25: RegGetValue ... argument does not escape
.\main.go:27: RegGetValue &buf[0] does not escape
.\main.go:27: RegGetValue &bufSizeBytes does not escape
.\main.go:27: RegGetValue ... argument does not escape
.\main.go:31: RegGetValue buf does not escape
.\main.go:45: err escapes to heap
.\main.go:48: str escapes to heap
.\main.go:45: main ... argument does not escape
.\main.go:48: main ... argument does not escape
<autogenerated>:1: leaking param: .this
0xc042066000
str: IMAGE_STATE_COMPLETE

c:\Users\Alex\dev\src\issues\issue16035>

This time program succeeds both times. I suspect that compiler is too clever and rearrange this code to keep buf on stack if possible. And GC moves buf (when it is on stack), before passing buf's old address to the syscall.

Could somebody test whether https://golang.org/cl/24031 fixes the problem?

It does not fixes the problem as far as I am concerned. The program fails in a similar way as before and only suceeds if I add the fmt.Printf("%p\n", &buf) line.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 13, 2016

Thanks for testing the CL. I'm puzzled that we do not see the problem with Proc.Call and only see it with LazyProc.Call. I don't see the essential difference between those methods.

Your tests do suggest that the problem is with buf, which is not what I was assuming. And it's true that the syscall package is careful to always add a call to use for any pointer created in a generated function, to force the pointer to escape.

You're right, of course, about the stack moving. cgo works with a moving stack by always ensuring that the arguments escape, which is also what the generated syscall package functions do.

I'm not sure how to document this.

@adg adg modified the milestones: Go1.7Maybe, Go1.7 Jun 27, 2016
@adg
Copy link
Contributor

@adg adg commented Jun 27, 2016

If the fix is only documentation then we can do this at any point before the 1.7 release. Otherwise it will have to wait for 1.8.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 28, 2016

I've taken another tack here. Could somebody please try https://golang.org/cl/24551 to see if it fixes the problem on Windows? Thanks.

@gopherbot
Copy link

@gopherbot gopherbot commented Jun 28, 2016

CL https://golang.org/cl/24551 mentions this issue.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jun 28, 2016

please try https://golang.org/cl/24551 to see if it fixes the problem on Windows?

I does not fixes the problem for me. Trying CL 24551 against prorgram listed here #16035 (comment) I get:

c:\dev\src\issues\issue16035>go version
go version devel +46c7646 Tue Jun 28 14:19:27 2016 -0700 windows/amd64

c:\dev\src\issues\issue16035>go run -gcflags="-m" main.go
# command-line-arguments
.\main.go:28: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:26: RegGetValue &buf[0] does not escape
.\main.go:26: RegGetValue &bufSizeBytes does not escape
.\main.go:26: RegGetValue ... argument does not escape
.\main.go:30: RegGetValue buf does not escape
.\main.go:44: err escapes to heap
.\main.go:47: str escapes to heap
.\main.go:44: main ... argument does not escape
.\main.go:47: main ... argument does not escape
<autogenerated>:1: leaking param: .this
str:

c:\dev\src\issues\issue16035>

If you provide suggestions on how to debug this, I will. Thank you.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 29, 2016

Hmmm, the magic comment may not work on method calls.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 30, 2016

Actually, methods are fine, it's failing because of the ...uintptr.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 1, 2016

@alexbrainman When you have time, could you try the latest version of https://golang.org/cl/24551 ? Thanks.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jul 1, 2016

try the latest version of https://golang.org/cl/24551

Still broken:

c:\dev\src\issues\issue16035>go version
go version devel +72a3c99 Thu Jun 30 17:45:10 2016 -0700 windows/amd64

c:\dev\src\issues\issue16035>go run -gcflags="-m" main.go
# command-line-arguments
.\main.go:28: syscall.Errno(ret) escapes to heap
.\main.go:22: RegGetValue lpSubKey does not escape
.\main.go:22: RegGetValue lpValue does not escape
.\main.go:22: RegGetValue pdwType does not escape
.\main.go:26: RegGetValue &buf[0] does not escape
.\main.go:26: RegGetValue &bufSizeBytes does not escape
.\main.go:26: RegGetValue ... argument does not escape
.\main.go:30: RegGetValue buf does not escape
.\main.go:44: err escapes to heap
.\main.go:47: str escapes to heap
.\main.go:44: main ... argument does not escape
.\main.go:47: main ... argument does not escape
<autogenerated>:1: leaking param: .this
str:

c:\dev\src\issues\issue16035>

I am happy to try things, if you tell me what. Thank you.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 1, 2016

@alexbrainman OK, that time I actually did break imports. Please try the latest version of https://golang.org/cl/24551. Thanks.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jul 1, 2016

try the latest version of https://golang.org/cl/24551

It works for me now. Thank you.

Can we apply your strategy here https://github.com/golang/sys/blob/master/windows/dll_windows.go#L124 too?

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 1, 2016

Thanks for testing it. Yes, if this gets approved, we can do it in the golang.org/x/sys repo as well.

Unfortunately it won't work if somebody uses an interface with a Call(...uintptr) method. I don't know how to solve that one.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jul 1, 2016

Unfortunately it won't work if somebody uses an interface with a Call(...uintptr) method. I don't know how to solve that one.

I am not overly concerned about solving this. This whole issue is a moving target, as GC changes we will break it again and again. We need to find proper solution to this problem.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 1, 2016

This isn't a GC problem, quite. The test case in the CL doesn't run the garbage collector, but it does fail without the compiler change. The problem is that stack growth requires that we precisely identify all pointers on the stack, but when we call a method like LazyProc.Call we can put a pointer on the stack that looks like a uintptr, and therefore is not modified when the stack grows. If that value had a pointer type, everything would work fine.

One possible proper solution might be to write FindProc and NewProc so that they take a function type, and have them use reflect.MakeFunc to return functions of that type. Then we will only convert to uintptr when calling syscall.Syscall, which already works.

@gopherbot gopherbot closed this in bbe5da4 Jul 6, 2016
@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jul 7, 2016

One possible proper solution might be to write FindProc and NewProc so that they take a function type, and have them use reflect.MakeFunc to return functions of that type. Then we will only convert to uintptr when calling syscall.Syscall, which already works.

Please, tell more. I don't understand what you are proposing.

And why would we need another "proper solution" if you current fix works already?

Thank you.

Alex

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 7, 2016

My solution, which is now submitted, does not work if somebody writes

type I interface {
    Call(...uintptr) (uintptr, uintptr, error)
}

var i I = lazyDLL.NewProc("f")

func F() {
    var buf [1024]byte
    I.Call(uintptr(unsafe.Pointer(&buf[0])))
}

The fact that the call requires special treatment is lost when the call is made through an interface. This is not a problem for syscall.Syscall because that is a function, not a method.

The approach I was suggesting above would be to add variants of FindProc and NewProc to take a function type, as in reflect.TypeOf((func(*byte))(nil)). Then they could write code like

v := reflect.MakeFunc(typ, func(in []Value) []Value {
    r1, r2, err := Syscall(p.Addr(), uintptr(in[0].Pointer()), 0, 0)
    return reflect.ValueOf(r1), reflect.ValueOf(r2), reflect.ValueOf(err)
}
return func(p *byte) (uintptr, uintptr, error) {
    out := v.Call(reflect.ValueOf(p))
    return out[0].(uintptr), out[1].(uintptr), out[2].(error)
}

This approach keeps the pointer value as a pointer down to the call to syscall.Syscall, which is safe.

But, of course, it makes the functions harder to create because you have to construct a type. On the plus side, they are easier to call.

Handling different numbers of arguments is tedious though straightforward.

@alexbrainman
Copy link
Member

@alexbrainman alexbrainman commented Jul 8, 2016

Thanks for explaining. Looks too complicated for a simple task.

Hopefully no one stores syscall.Proc or syscall.LazyDLL in an interface variable.

Alex

@gopherbot
Copy link

@gopherbot gopherbot commented Jul 12, 2016

CL https://golang.org/cl/24870 mentions this issue.

gopherbot pushed a commit to golang/sys that referenced this issue Jul 12, 2016
CL 24551 introduced //go:uintptrescapes comment to make
syscall.Proc.Call and syscall.LazyProc.Call parameters escape.
Use new comment in this package too.

Updates golang/go#16035.

Change-Id: I57ec3b4778195ca4a1ce9a8eec331f0f69285926
Reviewed-on: https://go-review.googlesource.com/24870
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
@golang golang locked and limited conversation to collaborators Jul 12, 2017
@golang golang unlocked this conversation Sep 23, 2019
@golang golang locked as resolved and limited conversation to collaborators Sep 23, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.