misc/cgo/errors/ptr.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++-------- src/runtime/cgocall.go | 8 ++++++++ diff --git a/misc/cgo/errors/ptr.go b/misc/cgo/errors/ptr.go index 0dd291f5ed132e17d868208b3c104d24400d14de..834cde9199175de2df55b3223bc4ff4f549c0394 100644 --- a/misc/cgo/errors/ptr.go +++ b/misc/cgo/errors/ptr.go @@ -27,8 +27,14 @@ 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{ @@ -237,6 +243,43 @@ 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 + 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 + typedef struct { const char *p; ptrdiff_t n; } gostring; + extern gostring GoStr(); + void f() { GoStr(); }`, + }, + }, + fail: true, + }, } func main() { @@ -244,12 +287,17 @@ os.Exit(doTests()) } func doTests() int { - dir, err := ioutil.TempDir("", "cgoerrors") + gopath, err := ioutil.TempDir("", "cgoerrors") if err != nil { fmt.Fprintln(os.Stderr, err) return 2 } - defer os.RemoveAll(dir) + 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 @@ -259,7 +307,7 @@ errs := make(chan int) for i := 0; i < workers; i++ { wg.Add(1) go func() { - worker(dir, c, errs) + worker(gopath, c, errs) wg.Done() }() } @@ -281,10 +329,10 @@ } return tot } -func worker(dir string, c, errs chan int) { +func worker(gopath string, c, errs chan int) { e := 0 for i := range c { - if !doOne(dir, i) { + if !doOne(gopath, i) { e++ } } @@ -293,9 +341,15 @@ errs <- e } } -func doOne(dir string, i int) bool { +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 { @@ -330,13 +384,30 @@ 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) + 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", "run", name) + 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 { @@ -354,7 +425,7 @@ os.Stderr.Write(errbuf.Bytes()) ok = false } - cmd = exec.Command("go", "run", name) + cmd = exec.Command(exe) cmd.Dir = dir } @@ -362,7 +433,7 @@ if t.expensive { cmd.Env = cgocheckEnv("2") } - buf, err := cmd.CombinedOutput() + buf, err = cmd.CombinedOutput() if t.fail { if err == nil { @@ -389,7 +460,7 @@ } if !t.expensive && ok { // Make sure it passes with the expensive checks. - cmd := exec.Command("go", "run", name) + cmd := exec.Command(exe) cmd.Dir = dir cmd.Env = cgocheckEnv("2") buf, err := cmd.CombinedOutput() @@ -404,7 +475,7 @@ } } if t.fail && ok { - cmd = exec.Command("go", "run", name) + cmd = exec.Command(exe) cmd.Dir = dir cmd.Env = cgocheckEnv("0") buf, err := cmd.CombinedOutput() @@ -427,9 +498,14 @@ fmt.Fprintf(w, "=== end of test %s output ===\n", name) } func cgocheckEnv(val string) []string { - env := []string{"GODEBUG=cgocheck=" + val} + 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, "GODEBUG=") { + if !strings.HasPrefix(e, look) { env = append(env, e) } } diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index 210d1862f95f8c7548f7ff7f95fbe2bc023e26c9..66115fd8b49bd01ad915a891040427e5a4cc1b95 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -467,6 +467,14 @@ for i := 0; i < s.cap; i++ { cgoCheckArg(st.elem, p, true, false, msg) p = add(p, st.elem.size) } + case kindString: + ss := (*stringStruct)(p) + if !cgoIsGoPointer(ss.str) { + return + } + if !top { + panic(errorString(msg)) + } case kindStruct: st := (*structtype)(unsafe.Pointer(t)) if !indir {