diff options
Diffstat (limited to 'libgo/misc/cgo/errors/ptr.go')
-rw-r--r-- | libgo/misc/cgo/errors/ptr.go | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/libgo/misc/cgo/errors/ptr.go b/libgo/misc/cgo/errors/ptr.go new file mode 100644 index 00000000000..4dafbdf3c01 --- /dev/null +++ b/libgo/misc/cgo/errors/ptr.go @@ -0,0 +1,576 @@ +// Copyright 2015 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. + +// Tests that cgo detects invalid pointer passing at runtime. + +package main + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" +) + +// ptrTest is the tests without the boilerplate. +type ptrTest struct { + name string // for reporting + c string // the cgo comment + imports []string // a list of imports + support string // supporting functions + body string // the body of the main function + extra []extra // extra files + fail bool // whether the test should fail + expensive bool // whether the test requires the expensive check +} + +type extra struct { + name string + contents string +} + +var ptrTests = []ptrTest{ + { + // Passing a pointer to a struct that contains a Go pointer. + name: "ptr1", + c: `typedef struct s { int *p; } s; void f(s *ps) {}`, + body: `C.f(&C.s{new(C.int)})`, + fail: true, + }, + { + // Passing a pointer to a struct that contains a Go pointer. + name: "ptr2", + c: `typedef struct s { int *p; } s; void f(s *ps) {}`, + body: `p := &C.s{new(C.int)}; C.f(p)`, + fail: true, + }, + { + // Passing a pointer to an int field of a Go struct + // that (irrelevantly) contains a Go pointer. + name: "ok1", + c: `struct s { int i; int *p; }; void f(int *p) {}`, + body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.i)`, + fail: false, + }, + { + // Passing a pointer to a pointer field of a Go struct. + name: "ptr-field", + c: `struct s { int i; int *p; }; void f(int **p) {}`, + body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.p)`, + fail: true, + }, + { + // Passing a pointer to a pointer field of a Go + // struct, where the field does not contain a Go + // pointer, but another field (irrelevantly) does. + name: "ptr-field-ok", + c: `struct s { int *p1; int *p2; }; void f(int **p) {}`, + body: `p := &C.struct_s{p1: nil, p2: new(C.int)}; C.f(&p.p1)`, + fail: false, + }, + { + // Passing the address of a slice with no Go pointers. + name: "slice-ok-1", + c: `void f(void **p) {}`, + imports: []string{"unsafe"}, + body: `s := []unsafe.Pointer{nil}; C.f(&s[0])`, + fail: false, + }, + { + // Passing the address of a slice with a Go pointer. + name: "slice-ptr-1", + c: `void f(void **p) {}`, + imports: []string{"unsafe"}, + body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f(&s[0])`, + fail: true, + }, + { + // Passing the address of a slice with a Go pointer, + // where we are passing the address of an element that + // is not a Go pointer. + name: "slice-ptr-2", + c: `void f(void **p) {}`, + imports: []string{"unsafe"}, + body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f(&s[0])`, + fail: true, + }, + { + // Passing the address of a slice that is an element + // in a struct only looks at the slice. + name: "slice-ok-2", + c: `void f(void **p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; s []unsafe.Pointer }`, + body: `i := 0; p := &S{p:&i, s:[]unsafe.Pointer{nil}}; C.f(&p.s[0])`, + fail: false, + }, + { + // Passing the address of a slice of an array that is + // an element in a struct, with a type conversion. + name: "slice-ok-3", + c: `void f(void* p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; a [4]byte }`, + body: `i := 0; p := &S{p:&i}; s := p.a[:]; C.f(unsafe.Pointer(&s[0]))`, + fail: false, + }, + { + // Passing the address of a slice of an array that is + // an element in a struct, with a type conversion. + name: "slice-ok-4", + c: `typedef void* PV; void f(PV p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; a [4]byte }`, + body: `i := 0; p := &S{p:&i}; C.f(C.PV(unsafe.Pointer(&p.a[0])))`, + fail: false, + }, + { + // Passing the address of a static variable with no + // pointers doesn't matter. + name: "varok", + c: `void f(char** parg) {}`, + support: `var hello = [...]C.char{'h', 'e', 'l', 'l', 'o'}`, + body: `parg := [1]*C.char{&hello[0]}; C.f(&parg[0])`, + fail: false, + }, + { + // Passing the address of a static variable with + // pointers does matter. + name: "var", + c: `void f(char*** parg) {}`, + support: `var hello = [...]*C.char{new(C.char)}`, + body: `parg := [1]**C.char{&hello[0]}; C.f(&parg[0])`, + fail: true, + }, + { + // Storing a Go pointer into C memory should fail. + name: "barrier", + c: `#include <stdlib.h> + char **f1() { return malloc(sizeof(char*)); } + void f2(char **p) {}`, + body: `p := C.f1(); *p = new(C.char); C.f2(p)`, + fail: true, + expensive: true, + }, + { + // Storing a Go pointer into C memory by assigning a + // large value should fail. + name: "barrier-struct", + c: `#include <stdlib.h> + struct s { char *a[10]; }; + struct s *f1() { return malloc(sizeof(struct s)); } + void f2(struct s *p) {}`, + body: `p := C.f1(); p.a = [10]*C.char{new(C.char)}; C.f2(p)`, + fail: true, + expensive: true, + }, + { + // Storing a Go pointer into C memory using a slice + // copy should fail. + name: "barrier-slice", + c: `#include <stdlib.h> + struct s { char *a[10]; }; + struct s *f1() { return malloc(sizeof(struct s)); } + void f2(struct s *p) {}`, + body: `p := C.f1(); copy(p.a[:], []*C.char{new(C.char)}); C.f2(p)`, + fail: true, + expensive: true, + }, + { + // A very large value uses a GC program, which is a + // different code path. + name: "barrier-gcprog-array", + c: `#include <stdlib.h> + struct s { char *a[32769]; }; + struct s *f1() { return malloc(sizeof(struct s)); } + void f2(struct s *p) {}`, + body: `p := C.f1(); p.a = [32769]*C.char{new(C.char)}; C.f2(p)`, + fail: true, + expensive: true, + }, + { + // Similar case, with a source on the heap. + name: "barrier-gcprog-array-heap", + c: `#include <stdlib.h> + struct s { char *a[32769]; }; + struct s *f1() { return malloc(sizeof(struct s)); } + void f2(struct s *p) {} + void f3(void *p) {}`, + imports: []string{"unsafe"}, + body: `p := C.f1(); n := &[32769]*C.char{new(C.char)}; p.a = *n; C.f2(p); n[0] = nil; C.f3(unsafe.Pointer(n))`, + fail: true, + expensive: true, + }, + { + // A GC program with a struct. + name: "barrier-gcprog-struct", + c: `#include <stdlib.h> + struct s { char *a[32769]; }; + struct s2 { struct s f; }; + struct s2 *f1() { return malloc(sizeof(struct s2)); } + void f2(struct s2 *p) {}`, + body: `p := C.f1(); p.f = C.struct_s{[32769]*C.char{new(C.char)}}; C.f2(p)`, + fail: true, + expensive: true, + }, + { + // Similar case, with a source on the heap. + name: "barrier-gcprog-struct-heap", + c: `#include <stdlib.h> + struct s { char *a[32769]; }; + struct s2 { struct s f; }; + struct s2 *f1() { return malloc(sizeof(struct s2)); } + void f2(struct s2 *p) {} + void f3(void *p) {}`, + imports: []string{"unsafe"}, + body: `p := C.f1(); n := &C.struct_s{[32769]*C.char{new(C.char)}}; p.f = *n; C.f2(p); n.a[0] = nil; C.f3(unsafe.Pointer(n))`, + fail: true, + expensive: true, + }, + { + // Exported functions may not return Go pointers. + name: "export1", + c: `extern unsigned char *GoFn();`, + support: `//export GoFn + func GoFn() *byte { return new(byte) }`, + body: `C.GoFn()`, + fail: true, + }, + { + // Returning a C pointer is fine. + name: "exportok", + c: `#include <stdlib.h> + extern unsigned char *GoFn();`, + support: `//export GoFn + func GoFn() *byte { return (*byte)(C.malloc(1)) }`, + body: `C.GoFn()`, + }, + { + // Passing a Go string is fine. + name: "pass-string", + c: `#include <stddef.h> + typedef struct { const char *p; ptrdiff_t n; } gostring; + gostring f(gostring s) { return s; }`, + imports: []string{"unsafe"}, + body: `s := "a"; r := C.f(*(*C.gostring)(unsafe.Pointer(&s))); if *(*string)(unsafe.Pointer(&r)) != s { panic(r) }`, + }, + { + // Passing a slice of Go strings fails. + name: "pass-string-slice", + c: `void f(void *p) {}`, + imports: []string{"strings", "unsafe"}, + support: `type S struct { a [1]string }`, + body: `s := S{a:[1]string{strings.Repeat("a", 2)}}; C.f(unsafe.Pointer(&s.a[0]))`, + fail: true, + }, + { + // Exported functions may not return strings. + name: "ret-string", + c: `extern void f();`, + imports: []string{"strings"}, + support: `//export GoStr + func GoStr() string { return strings.Repeat("a", 2) }`, + body: `C.f()`, + extra: []extra{ + { + "call.c", + `#include <stddef.h> + typedef struct { const char *p; ptrdiff_t n; } gostring; + extern gostring GoStr(); + void f() { GoStr(); }`, + }, + }, + fail: true, + }, + { + // Don't check non-pointer data. + // Uses unsafe code to get a pointer we shouldn't check. + // Although we use unsafe, the uintptr represents an integer + // that happens to have the same representation as a pointer; + // that is, we are testing something that is not unsafe. + name: "ptrdata1", + c: `#include <stdlib.h> + void f(void* p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; a [8*8]byte; u uintptr }`, + body: `i := 0; p := &S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(*p)))); *q = *p; C.f(unsafe.Pointer(q))`, + fail: false, + }, + { + // Like ptrdata1, but with a type that uses a GC program. + name: "ptrdata2", + c: `#include <stdlib.h> + void f(void* p) {}`, + imports: []string{"unsafe"}, + support: `type S struct { p *int; a [32769*8]byte; q *int; u uintptr }`, + body: `i := 0; p := S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(p)))); *q = p; C.f(unsafe.Pointer(q))`, + fail: false, + }, + { + // Check deferred pointers when they are used, not + // when the defer statement is run. + name: "defer", + c: `typedef struct s { int *p; } s; void f(s *ps) {}`, + body: `p := &C.s{}; defer C.f(p); p.p = new(C.int)`, + fail: true, + }, + { + // Check a pointer to a union if the union has any + // pointer fields. + name: "union1", + c: `typedef union { char **p; unsigned long i; } u; void f(u *pu) {}`, + imports: []string{"unsafe"}, + body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`, + fail: true, + }, + { + // Don't check a pointer to a union if the union does + // not have any pointer fields. + // Like ptrdata1 above, the uintptr represents an + // integer that happens to have the same + // representation as a pointer. + name: "union2", + c: `typedef union { unsigned long i; } u; void f(u *pu) {}`, + imports: []string{"unsafe"}, + body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`, + fail: false, + }, +} + +func main() { + os.Exit(doTests()) +} + +func doTests() int { + gopath, err := ioutil.TempDir("", "cgoerrors") + if err != nil { + fmt.Fprintln(os.Stderr, err) + return 2 + } + defer os.RemoveAll(gopath) + + if err := os.MkdirAll(filepath.Join(gopath, "src"), 0777); err != nil { + fmt.Fprintln(os.Stderr, err) + return 2 + } + + workers := runtime.NumCPU() + 1 + + var wg sync.WaitGroup + c := make(chan int) + errs := make(chan int) + for i := 0; i < workers; i++ { + wg.Add(1) + go func() { + worker(gopath, c, errs) + wg.Done() + }() + } + + for i := range ptrTests { + c <- i + } + close(c) + + go func() { + wg.Wait() + close(errs) + }() + + tot := 0 + for e := range errs { + tot += e + } + return tot +} + +func worker(gopath string, c, errs chan int) { + e := 0 + for i := range c { + if !doOne(gopath, i) { + e++ + } + } + if e > 0 { + errs <- e + } +} + +func doOne(gopath string, i int) bool { + t := &ptrTests[i] + + dir := filepath.Join(gopath, "src", fmt.Sprintf("dir%d", i)) + if err := os.Mkdir(dir, 0777); err != nil { + fmt.Fprintln(os.Stderr, err) + return false + } + + name := filepath.Join(dir, fmt.Sprintf("t%d.go", i)) + f, err := os.Create(name) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return false + } + + b := bufio.NewWriter(f) + fmt.Fprintln(b, `package main`) + fmt.Fprintln(b) + fmt.Fprintln(b, `/*`) + fmt.Fprintln(b, t.c) + fmt.Fprintln(b, `*/`) + fmt.Fprintln(b, `import "C"`) + fmt.Fprintln(b) + for _, imp := range t.imports { + fmt.Fprintln(b, `import "`+imp+`"`) + } + if len(t.imports) > 0 { + fmt.Fprintln(b) + } + if len(t.support) > 0 { + fmt.Fprintln(b, t.support) + fmt.Fprintln(b) + } + fmt.Fprintln(b, `func main() {`) + fmt.Fprintln(b, t.body) + fmt.Fprintln(b, `}`) + + if err := b.Flush(); err != nil { + fmt.Fprintf(os.Stderr, "flushing %s: %v\n", name, err) + return false + } + if err := f.Close(); err != nil { + fmt.Fprintf(os.Stderr, "closing %s: %v\n", name, err) + return false + } + + for _, e := range t.extra { + if err := ioutil.WriteFile(filepath.Join(dir, e.name), []byte(e.contents), 0644); err != nil { + fmt.Fprintf(os.Stderr, "writing %s: %v\n", e.name, err) + return false + } + } + + ok := true + + cmd := exec.Command("go", "build") + cmd.Dir = dir + cmd.Env = addEnv("GOPATH", gopath) + buf, err := cmd.CombinedOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "test %s failed to build: %v\n%s", t.name, err, buf) + return false + } + + exe := filepath.Join(dir, filepath.Base(dir)) + cmd = exec.Command(exe) + cmd.Dir = dir + + if t.expensive { + cmd.Env = cgocheckEnv("1") + buf, err := cmd.CombinedOutput() + if err != nil { + var errbuf bytes.Buffer + if t.fail { + fmt.Fprintf(&errbuf, "test %s marked expensive but failed when not expensive: %v\n", t.name, err) + } else { + fmt.Fprintf(&errbuf, "test %s failed unexpectedly with GODEBUG=cgocheck=1: %v\n", t.name, err) + } + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } + + cmd = exec.Command(exe) + cmd.Dir = dir + } + + if t.expensive { + cmd.Env = cgocheckEnv("2") + } + + buf, err = cmd.CombinedOutput() + + if t.fail { + if err == nil { + var errbuf bytes.Buffer + fmt.Fprintf(&errbuf, "test %s did not fail as expected\n", t.name) + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } else if !bytes.Contains(buf, []byte("Go pointer")) { + var errbuf bytes.Buffer + fmt.Fprintf(&errbuf, "test %s output does not contain expected error (failed with %v)\n", t.name, err) + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } + } else { + if err != nil { + var errbuf bytes.Buffer + fmt.Fprintf(&errbuf, "test %s failed unexpectedly: %v\n", t.name, err) + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } + + if !t.expensive && ok { + // Make sure it passes with the expensive checks. + cmd := exec.Command(exe) + cmd.Dir = dir + cmd.Env = cgocheckEnv("2") + buf, err := cmd.CombinedOutput() + if err != nil { + var errbuf bytes.Buffer + fmt.Fprintf(&errbuf, "test %s failed unexpectedly with expensive checks: %v\n", t.name, err) + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } + } + } + + if t.fail && ok { + cmd = exec.Command(exe) + cmd.Dir = dir + cmd.Env = cgocheckEnv("0") + buf, err := cmd.CombinedOutput() + if err != nil { + var errbuf bytes.Buffer + fmt.Fprintf(&errbuf, "test %s failed unexpectedly with GODEBUG=cgocheck=0: %v\n", t.name, err) + reportTestOutput(&errbuf, t.name, buf) + os.Stderr.Write(errbuf.Bytes()) + ok = false + } + } + + return ok +} + +func reportTestOutput(w io.Writer, name string, buf []byte) { + fmt.Fprintf(w, "=== test %s output ===\n", name) + fmt.Fprintf(w, "%s", buf) + fmt.Fprintf(w, "=== end of test %s output ===\n", name) +} + +func cgocheckEnv(val string) []string { + return addEnv("GODEBUG", "cgocheck="+val) +} + +func addEnv(key, val string) []string { + env := []string{key + "=" + val} + look := key + "=" + for _, e := range os.Environ() { + if !strings.HasPrefix(e, look) { + env = append(env, e) + } + } + return env +} |