-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
cmd/cgo: provide some mechanism for treating certain C pointer types as uintptr #22906
Comments
There already exists But if the check is wrong, the check should be fixed. And if the code is wrong, the code should be fixed. You shouldn't just set |
The definition of a Go pointer is a pointer into Go memory. A Go pointer can have a The checks can be over broad as described at #14210. In order to find the problem, somebody who can recreate it is going to have to look at the actual values in question, to find the Go pointers. |
Thanks for the comments, and I've read a lot of tickets before I've opened this issue. What I don't get is, that I just receive c pointers as parameters and just pass them to another c function, so I expect that Go should not inspect these values at all. (the *C.JNIEnv and C.jarray which are just forwarded back right into the c world) And the fact, that it works reliable on other devices. Btw, we are using good old c with JNI on the same device, which does the same, without ever encountering any crash or unwanted behavior. |
@bradfitz Setting the GODEBUG environment variable does not work on Android. There is no possibility to define any Environment variable for an Android App - it is just not designed to support that. I tried to workaround this by using setenv from https://developer.android.com/reference/android/system/Os.html (and other hacks) before the library is loaded into the App's process using System.loadLibrary (the way the Java/Art-VM loads shared libraries) but the environment variables are always empty (os.Environ()) at the Go side. |
We need to be clear on terminology. When you say you just receive C pointers as parameters, do you mean that those parameters have C types? Or do you mean that they are allocated by calling That is, when you say that you receive C pointers, where do those pointers come from? What allocates the memory to which they point? The fact that the code works on other platforms is interesting but it doesn't really prove anything. There is evidently some difference on this platform. You need to understand that difference before you can conclude that the right decision is to disable the check. |
Code snippet (math.go):
Code snippet (jni.go)
Oberservation 1: runPart1 == true and runPart2 == trueAfter a few runs (~ 40 - 800) I get "always"
This indicates that sending a non-Go-allocated pointer is not allowed, but wait... Oberservation 1: runPart1 == true and runPart2 == falsejust running part 1 is fine, cancelled test after more than 30min, "no" crash at all. Oberservation 2: runPart1 == false and runPart2 == trueAfter a few runs (~ 40 - 700) I get "always"
Observation 3: runPart == false and runPart2 == true and comment memcpy line 103(!)just running part 1 is fine, cancelled test after more than 30min, "no" crash at all. Feelings
Cause analysisI used your comment about the pointer terminology as an occasion to find out what "jbytearray" is. In the end, the typedef resolves over jobject to a void*. [1] https://android.googlesource.com/platform/dalvik/+/tools_r21/vm/Jni.cpp My conclusion / my wishThe cgo checks should never be enabled by default, because Go cannot decide if a pointer is actually a pointer which is allowed to be checked. What else can I do, besides disabling or circumvent "any" cgochecks, to write a reliable cgo program? |
OK, if you a generated number that is not a pointer, then in Go it must not have a pointer type. In Go it must have a type such as I understand your desire, but it can't be done. Go is not C. Go is a garbage collected language, and the garbage collector runs concurrently while the rest of the program is running. The cgo rules were not created to make people's lives difficult; they were created because they are required by the garbage collector. The garbage collector treats every variable with a pointer type as a potential root. If the variable does not hold an actual pointer, if it holds a value that happens to be the same as a pointer to various internal data structures, then the garbage collector will crash. It will crash at a random time during your program's execution. Disabling the cgo checks will hide the problem for a while, until the garbage collector crashes. You need to change your code to not use a pointer type for a value that is not a valid pointer. |
It might be possible to extend the mechanism in https://go-review.googlesource.com/c/go/+/66332 to handle your case. That fix is for Darwin's CFTypeRef types which are declared as pointers, but sometimes contain integers. I'd need more info about the C types, their declarations, and their constructors to be sure. |
@ianlancetaylor Thanks for giving more insight into this. Perhaps it would be a good idea to have a chapter for cgo and unsafe in the https://golang.org/ref/spec. However, Keith recognized the problem, which is indeed the same as in https://go-review.googlesource.com/c/go/+/66332. So I have to insist that the cgo decision - to treat every pointer crossing the FFI to be an unsafe.Pointer - is a fundamental design flaw. Currently there is no way to tell cgo that a c pointer should be an uintptr instead. The approach in gcc.go to hardcode pointer categorization in "badPointerTypedef" can never be a generic solution. Bluntly said, yesterday it was CF*Ref on Darwin, today it is jobject on Dalvik and tomorrow it will be an endless story. We definitely need a change here, so that developers have at least the possibility to have a customizable "badPointerTypedef" somehow. A tedious workaround may be to create wrappers to hide the bad pointers from cgo (if you ever know them before suffering under random crashes) but it costs a lot of effort. I understand that the intention is to protect the runtime's invariance but it is not a requirement for it to work properly - at least for c-owned pointers and in particular these bad pointers. Other ecosystems with GC do not care either, and they are fine. @randall77 Thanks for looking into this. jobject is declared in jni.h, which is more or less the same for all NDKs and for Java SE, e.g. https://github.com/aosp-mirror/platform_prebuilt/blob/master/ndk/android-ndk-r8/platforms/android-5/arch-arm/usr/include/jni.h. jni.h defines also various methods to create jobjects, e.g. NewByteArray |
@randall77 the mechanism for CFTypeRef seems like the way forward, which would make gomobile correct as well. What more information do you need? Valid jobject (and its aliases j*array) values other than nil/0 can only be generated from JNI functions so the only downside is the nil => 0 conversion. |
@worldiety : Thanks for the reference. Where is the code that actually makes one of these jobjects? I'm afraid I don't know my way around the android source tree.
Actually, it is a requirement. If you type an integer as a pointer, bad things can happen. For instance, Go copies stacks. If one of the integers-typed-as-pointers happens to look like a pointer into the Go stack, when the stack is copied the pointer will be adjusted. If the pointer was really an integer then the Go runtime just randomly adjusted your integer for you. |
@randall77 My wording was probably a bit misleading. What I wanted to say is actually the same (as you pointed already out): Go needs his managed (unsafe) pointers to be fine, however it must not use (unsafe) bad pointers from c. Lumping the ownerships of both worlds blindly together (which is what cgo does) is the design failure. Other ecosystems - like the Java-Jni - never try to detect an ownership automatically in the FFI logic like Go does. And this automatism is "not a requirement" for Go to work. I'm not a real Android expert either, but let's try to find a way through it:
Please be aware that JNI is also supported by all Java(TM) VMs as well (on perhaps all cgo targets), so the workaround is not limited to Android. Also the way through the Android ART runtime looks totally different. Proposal A - method level
Proposal B - unit level
|
As we've explained, correct identification of pointers is simply not optional. It would be nice if it were. But Go's garbage collector is designed for optimal handling of Go code, not for Go calling C. Clearly a JNI-style like approach would be a solution, but I think most people find cgo significantly easier to use than JNI. I've changed the title of this issue to come up with some mechanism for telling cgo that certain C pointer types must not be treated as Go pointers. If anybody has any suggestions for how to do that, I would like to hear them. Thanks. |
Random thoughts, in no particular order:
I can't find this Using directives isn't really satisfactory. You'd have to include them in every cgo section of every file (or just package? not sure.) that mentions the type. That's a lot of repetition and prone to error. I don't think we want to map all C pointer types to
Then we want to have
An interesting possibility is to map We already "hide" pointers in some JNI types. For instance, |
I also looked through the Avian VM [1] and Graal VM [2]. At first glance, I was not able to spot bad pointers for jobject, but that does not mean anything. There are also many other closed source blackbox JVMs, as listed in [3]. We should not assume, that they have no bad pointers. [1] https://github.com/ReadyTalk/avian (btw Avian is also able to run on iOS) |
Change https://golang.org/cl/81876 mentions this issue: |
CL 81876 contains a possible fix for |
I posted an idea to a golang-dev thread that might be worth revisiting in light of this issue, #22218, #19928, #21878, #20275, and perhaps #13467. The idea is to define a distinguished Go type, which I'm calling The differences between
The general rules for non-instantiable types would be:
To stitch that together with cgo, we would map any incomplete C struct type (and, if necessary, complete types in a whitelist) to an uninstantiable Go type. Unfortunately, this proposal introduces a backward-incompatibility: the C type |
A simpler approach, I suppose, would be to add another structured comment to indicate the type mapping: // #cgo TYPE_MAP: jobject=uintptr That would give a migration path for unsafe.Void, but could be used even without it. |
The only problem with the structured comment is that it has to appear in every single file that uses cgo with the troublesome type. It's not a fatal problem, but it's less than ideal. |
Given that we can't control the C header files, I don't see an alternative in general. The only other option I can think of is to add a (non-portable) annotation to the declaration in the C header to say “this is a sometimes-pointer”, but if we could make changes to the C header in general we could just change the type from a pointer to the more portable On the other hand, if we could address #13467, we could at least recommend that only one Go source file should import a given C header. That would ensure consistency, but isn't feasible today. |
Well, just for example, we could adopt your |
The jobject type is declared as a pointer, but some JVMs (Dalvik, ART) store non-pointer values in them. In Go, we must use uintptr instead of a real pointer for these types. This is similar to the CoreFoundation types on Darwin which were "fixed" in CL 66332. Update #22906 Update #21897 RELNOTE=yes Change-Id: I0d4c664501d89a696c2fb037c995503caabf8911 Reviewed-on: https://go-review.googlesource.com/81876 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Change https://golang.org/cl/82917 mentions this issue: |
Update #22906 Update #21897 Change-Id: I73709b2fdac6981d4bc2f7dab0767f2dd7be3be5 Reviewed-on: https://go-review.googlesource.com/82917 Reviewed-by: Brad Fitzpatrick <[email protected]>
We are seeing the same issue as described by @worldiety. We are using a library with the same design - it stores integers in pointers. In our case, the library uses incomplete |
Aside from many instances of APIs using void* for opaque types that store non-pointers (I can add OpenGL to the existing list), there is also the case of One such example is wl_resource in libwayland. |
@dominikh, you can always use |
I'm also facing this after being dinged by
Why not have a special type The proposed name For example: package main
/*
extern void my_go_callback(void *arg);
static void my_c_api(void *arg) {
my_go_callback(arg);
}
*/
import "C"
import "fmt"
func main() {
C.my_c_api(C.uintptr_void(uintptr(42)))
}
//export my_go_callback
func my_go_callback(arg C.uintptr_void) {
fmt.Printf("got arg: %d\n", uintptr(arg))
} (Aside from not requiring wrappers, this approach does not rely on |
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?go version go1.9.2 darwin/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/tschinke/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/bv/6rt5ck194ls704dz9p4c0hlh0000gn/T/go-build985720357=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
What did you do?
I'm not using goMobile but using jni + cgo directly to get maximum control. The code works without any problems on ARM but fails on x86 randomly. At first it looks like the usual cgo check, however I'm quite confident that this is a bug. One variant is this:
E/Go: panic: runtime error: cgo argument has Go pointer to Go pointer
E/Go: goroutine 17 [running, locked to thread]:
E/Go: main.GetPrimitiveArrayCritical.func1(0x5d12ef60, 0x72d00005, 0x72c68a80)
E/Go: /Users/tschinke/repos/libs_wdy/go_snappy/go/src/wdy/jni.go:1852 +0x79
E/Go: main.GetPrimitiveArrayCritical(0x5d12ef60, 0x72d00005, 0x62811b64)
E/Go: /Users/tschinke/repos/libs_wdy/go_snappy/go/src/wdy/jni.go:1852 +0x23
E/Go: main.Java_de_worldiety_snappy_go_GoSnappy_uncompress2(0x5d12ef60, 0x7c700001, 0x72d00005, 0x0, 0x8, 0x18)
E/Go: /Users/tschinke/repos/libs_wdy/go_snappy/go/src/wdy/math.go:79 +0x27
E/Go: main._cgoexpwrap_3ee6d16a6a1f_Java_de_worldiety_snappy_go_GoSnappy_uncompress2(0x5d12ef60, 0x7c700001, 0x72d00005, 0x0, 0x8, 0x0)
E/Go: command-line-arguments/_obj/_cgo_gotypes.go:3055 +0x6a
But the cgo statement is wrong, math.go:79 looks like this:
77: //export Java_de_worldiety_snappy_go_GoSnappy_uncompress2
78: func Java_de_worldiety_snappy_go_GoSnappy_uncompress2(env *C.JNIEnv, clazz C.jclass, compressed C.jbyteArray, compressedOffset C.jint, compressedSize C.jint) C.jbyteArray {
79: ptrComp := GetPrimitiveArrayCritical(env, C.jarray(compressed))
To be complete, the GetPrimitiveArrayCritical method is declared like this:
// jni.h:
// void * (JNICALL *GetPrimitiveArrayCritical)(JNIEnv *env, jarray array, jboolean *isCopy);
func GetPrimitiveArrayCritical(env *C.JNIEnv, array C.jarray) unsafe.Pointer {
return C._GoJniGetPrimitiveArrayCritical(env, array)
}
And this:
static void* _GoJniGetPrimitiveArrayCritical(JNIEnv* env, jarray array)
{
return (*env)->GetPrimitiveArrayCritical(env, array, JNI_FALSE);
}
Please correct me, but I can't see any Go-Pointer at all.
What did you expect to see?
a) cgo check should not randomly fail
b) cgo check should be consistent across targets (x86 and arm)
c) a possibility to disable the cgo check at runtime, which works for an android library. Setting the environment variable through Android (Os.setenv or libcore) has no effect, and to set it from Go itself is to late. Give us some public variable like GOGC.
What did you see instead?
Random panics on Dell Venue 8, running Android 4.4 on x86 (32bit), but works on ARMs without problems.
The text was updated successfully, but these errors were encountered: