mirror of
https://github.com/golang/go
synced 2024-09-29 18:24:29 -06:00
e97096661e
It's awkward to get a string value in cgoCheckArg, but SWIG testing revealed that it is possible. The new handling of extra files in the ptr.go test emulates what SWIG does with an exported function that returns a string. Change-Id: I453717f867b8a49499576c28550e7c93053a0cf8 Reviewed-on: https://go-review.googlesource.com/19020 Run-TryBot: Ian Lance Taylor <iant@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
514 lines
14 KiB
Go
514 lines
14 KiB
Go
// 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 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,
|
|
},
|
|
}
|
|
|
|
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
|
|
}
|