mirror of
https://github.com/golang/go
synced 2024-11-19 17:54:48 -07:00
268 lines
6.3 KiB
Go
268 lines
6.3 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 {
|
||
|
c string // the cgo comment
|
||
|
imports []string // a list of imports
|
||
|
support string // supporting functions
|
||
|
body string // the body of the main function
|
||
|
fail bool // whether the test should fail
|
||
|
}
|
||
|
|
||
|
var ptrTests = []ptrTest{
|
||
|
{
|
||
|
// Passing a pointer to a struct that contains a Go pointer.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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.
|
||
|
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 static variable with no
|
||
|
// pointers doesn't matter.
|
||
|
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.
|
||
|
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,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
os.Exit(doTests())
|
||
|
}
|
||
|
|
||
|
func doTests() int {
|
||
|
dir, err := ioutil.TempDir("", "cgoerrors")
|
||
|
if err != nil {
|
||
|
fmt.Fprintln(os.Stderr, err)
|
||
|
return 2
|
||
|
}
|
||
|
defer os.RemoveAll(dir)
|
||
|
|
||
|
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(dir, 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(dir string, c, errs chan int) {
|
||
|
e := 0
|
||
|
for i := range c {
|
||
|
if !doOne(dir, i) {
|
||
|
e++
|
||
|
}
|
||
|
}
|
||
|
if e > 0 {
|
||
|
errs <- e
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func doOne(dir string, i int) bool {
|
||
|
t := &ptrTests[i]
|
||
|
|
||
|
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.Fprintln(os.Stderr, "closing %s: %v\n", name, err)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
cmd := exec.Command("go", "run", name)
|
||
|
cmd.Dir = dir
|
||
|
buf, err := cmd.CombinedOutput()
|
||
|
|
||
|
ok := true
|
||
|
if t.fail {
|
||
|
if err == nil {
|
||
|
var errbuf bytes.Buffer
|
||
|
fmt.Fprintf(&errbuf, "test %d did not fail as expected\n", i)
|
||
|
reportTestOutput(&errbuf, i, buf)
|
||
|
os.Stderr.Write(errbuf.Bytes())
|
||
|
ok = false
|
||
|
} else if !bytes.Contains(buf, []byte("Go pointer")) {
|
||
|
var errbuf bytes.Buffer
|
||
|
fmt.Fprintf(&errbuf, "test %d output does not contain expected error\n", i)
|
||
|
reportTestOutput(&errbuf, i, buf)
|
||
|
os.Stderr.Write(errbuf.Bytes())
|
||
|
ok = false
|
||
|
}
|
||
|
} else {
|
||
|
if err != nil {
|
||
|
var errbuf bytes.Buffer
|
||
|
fmt.Fprintf(&errbuf, "test %d failed unexpectedly: %v\n", i, err)
|
||
|
reportTestOutput(&errbuf, i, buf)
|
||
|
os.Stderr.Write(errbuf.Bytes())
|
||
|
ok = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if t.fail && ok {
|
||
|
cmd = exec.Command("go", "run", name)
|
||
|
cmd.Dir = dir
|
||
|
env := []string{"GODEBUG=cgocheck=0"}
|
||
|
for _, e := range os.Environ() {
|
||
|
if !strings.HasPrefix(e, "GODEBUG=") {
|
||
|
env = append(env, e)
|
||
|
}
|
||
|
}
|
||
|
cmd.Env = env
|
||
|
buf, err := cmd.CombinedOutput()
|
||
|
if err != nil {
|
||
|
var errbuf bytes.Buffer
|
||
|
fmt.Fprintf(&errbuf, "test %d failed unexpectedly with GODEBUG=cgocheck=0: %v\n", i, err)
|
||
|
reportTestOutput(&errbuf, i, buf)
|
||
|
os.Stderr.Write(errbuf.Bytes())
|
||
|
ok = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
func reportTestOutput(w io.Writer, i int, buf []byte) {
|
||
|
fmt.Fprintf(w, "=== test %d output ===\n", i)
|
||
|
fmt.Fprintf(w, "%s", buf)
|
||
|
fmt.Fprintf(w, "=== end of test %d output ===\n", i)
|
||
|
}
|