4

I'm currently writing a Go wrapper around a C library. That C library uses opaque struct pointers to hide information across the interface. However, the underlying implementation stores size_t values in there. This leads to runtime errors in the resulting program. A minimum working example to reproduce the problem looks like this:

main.go:

package main

/*
#include "stddef.h"
// Create an opaque type to hide the details of the underlying data structure.
typedef struct HandlePrivate *Handle;

// In reality, the implementation uses a type derived from size_t for the Handle.
Handle getInvalidPointer() {
    size_t actualHandle = 1;
    return (Handle) actualHandle;
}
 */
import "C"

// Create a temporary slice containing invalid pointers.
// The idea is that the local variable slice can be garbage collected at the end of the function call.
// When the slice is scanned for linked objects, the GC comes across the invalid pointers.
func getTempSlice() {
    slice := make([]C.Handle, 1000000)
    for i, _ := range slice {
        slice[i] = C.getInvalidPointer()
    }
}

func main() {
    getTempSlice()
}

Running this program will lead to the following error

runtime: writebarrierptr *0xc42006c000 = 0x1
fatal error: bad pointer in write barrier
[...stack trace omitted...]

Note that the errors disappear when the GC is disabled by setting the environment variable GOGC=off.

My question is which is the best way to solve or work around this problem. The library stores integer values in pointers for the sake of information hiding and this seems to confuse the GC. For obvious reasons I don't want to start messing with the library itself but rather absorb this behaviour in my wrapping layer.

My environment is Ubuntu 16.04, with gcc 5.4.0 and Go 1.9.2.

Documentation of cgo

Johannes P
  • 888
  • 8
  • 16

1 Answers1

2

I can reproduce the error for go1.8.5 and go1.9.2. I cannot reproduce the error for tip: devel +f01b928 Sat Nov 11 06:17:48 2017 +0000 (effectively go1.10alpha).


// Create a temporary slice containing invalid pointers.
// The idea is that the local variable slice can be garbage collected at the end of the function call.
// When the slice is scanned for linked objects, the GC comes across the invalid pointers.

A Go mantra is do not ignore errors. However, you seem to assume that that the GC will gracefully ignore errors. The GC should complain loudly (go1.8.5 and go1.9.2). At worst, with undefined behavior that may vary from release to release, the GC may appear to ignore errors (go devel).

The Go compiler sees a pointer and the Go runtime GC expects a valid pointer.

// go tool cgo
// type _Ctype_Handle *_Ctype_struct_HandlePrivate
// var handle _Ctype_Handle
var handle C.Handle
// main._Ctype_Handle <nil> 0x0
fmt.Fprintf(os.Stderr, "%[1]T %[1]v %[1]p\n", handle)

slice := make([]C.Handle, 1000000)
for i, _ := range slice {
    slice[i] = C.getInvalidPointer()
}

Use type uintptr. For example,

package main

import "unsafe"

/*
#include "stddef.h"
// Create an opaque type to hide the details of the underlying data structure.
typedef struct HandlePrivate *Handle;

// In reality, the implementation uses a type derived from size_t for the Handle.
Handle getInvalidPointer() {
    size_t actualHandle = 1;
    return (Handle) actualHandle;
}
*/
import "C"

// Create a temporary slice of C pointers as Go integer type uintptr.
func getTempSlice() {
    slice := make([]uintptr, 1000000)
    for i, _ := range slice {
        slice[i] = uintptr(unsafe.Pointer(C.getInvalidPointer()))
    }
}

func main() {
    getTempSlice()
}
peterSO
  • 158,998
  • 31
  • 281
  • 276
  • I have the same bug on my arch with "go version go1.9.2 linux/amd64" with the OP code. Error: "runtime: writebarrierptr *0xc42007a000 = 0x1 fatal error: bad pointer in write barrier" – Stargateur Nov 11 '17 at 15:59