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: cgo gets confused by equivalent #defined types #13636

Open
bits01 opened this Issue Dec 16, 2015 · 6 comments

Comments

Projects
None yet
2 participants
@bits01

bits01 commented Dec 16, 2015

Unfortunately I don't have a simple example for this one. I observed it while working with a .h file that was conditionally including all sorts of other .h files and having its own defines. The problem occurred with __u8 and u8 types. They were both eventually defined as "unsigned char" but cgo considered them different and even got confused in the following case:

  • a C call returned a [16]C.__u8 (checked with a Printf %#v)
  • I created a Go function that took a [16]C.__u8 as the only argument and passed it the value I got above:
    func uuidBytesToString(ub [16]C.__u8) string {...}

When trying to compile it I got this error message:
cannot use cHandle.id.b (type [16]C.__u8) as type [16]C.u8 in argument to uuidBytesToString

It thinks that the func signature is [16]C.u8 instead of [16]C.__u8, but even so these 2 types are eventually unsigned char

@ianlancetaylor ianlancetaylor changed the title from cgo gets confused by equivalent #defined types to cmd/cgo: cgo gets confused by equivalent #defined types Dec 16, 2015

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Dec 16, 2015

We need a test case.

Even then, it is likely that there is nothing we can do. In C, a typedef is an alias. In Go, it's a different type. This gives an inescapable mismatch in the type systems.

Or, the problem may be with macros, in which case it is likely another version of #9601.

@ianlancetaylor ianlancetaylor added this to the Unplanned milestone Dec 16, 2015

@bits01

This comment has been minimized.

bits01 commented Dec 16, 2015

I think it's a combination of typedefs and macro defines, I tried chasing the maze a bit but it's hard to do by hand in particular when there are many conditional includes and defines.
Unfortunately I could not come up with a simple test case and the actual case where it fails is more complicated and proprietary to post.

For now I solved the problem by converting [16]C.__u8 to a Go [16]byte right away and passing that to the function instead. It involves an extra copy, but in this case it's only 16 bytes so not a big deal.

As a general (perhaps temporary) solution, would it be possible to add an experimental cgo directive to declare identical types, e.g. __u8 == u8 == unsigned char?

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Dec 16, 2015

I'm sorry, I'm personally not too interested in temporary solutions, and, more importantly, I'm not interested in adding any solution without some kind of test case.

@bits01

This comment has been minimized.

bits01 commented Dec 16, 2015

I'm attaching a test case, I think it's equivalent to what I'm seeing in my more complicated library.
It requires libuuid-devel to be installed in order to compile it, at least on my CentOS 7.2 x64

issue13636.zip

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Dec 16, 2015

Thanks. When I build this on my Ubuntu Trusty system, using uuid version 2.20.1-5.1ub, I get this:

./main.go:37: cannot use cdev.uuid_le.b (type [16]C.__u8) as type [16]C.unsignedchar in argument to dump

I can recreate this using this standalone test case:

package main

/*
#include <string.h>

typedef unsigned char __u8;
typedef struct {
    __u8 b[16];
} uuid_le;

#define uuid uuid_le
#define __u8 unsigned char

typedef struct {
    uuid uuid;
} device;

device newDevice() {
    device dev;
    memset(&dev, 0, sizeof dev);
    return dev;
}
*/
import "C"

func dump(ub [16]C.__u8) {
}

func main() {
    var cdev C.device = C.newDevice()
    for _, v := range cdev.uuid_le.b {
        _ = v
    }
    dump(cdev.uuid_le.b)
}

In this cut down version, it's clear that the problem is the #define __u8 unsigned char which is from your uuidtest.h file. If you take that out, everything works. Why is it there?

@bits01

This comment has been minimized.

bits01 commented Dec 16, 2015

I'm using some 3rd party code that's supposed to run on multiple platforms, my example just tried to re-create what I think this 3rd party code is doing. The uuid library has its own __u8 definition that comes from linux/types.h I think. Not sure why this 3rd party code needs to #define its own __u8 perhaps because it's multi-platform.
In the end it seems that both the uuid lib and the code define __u8 as unsigned char. Is there anything that cgo could do to recognize that it's really the same thing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment