github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/misc/cgo/errors/ptr.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Tests that cgo detects invalid pointer passing at runtime. 6 7 package main 8 9 import ( 10 "bufio" 11 "bytes" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "sync" 21 ) 22 23 // ptrTest is the tests without the boilerplate. 24 type ptrTest struct { 25 name string // for reporting 26 c string // the cgo comment 27 imports []string // a list of imports 28 support string // supporting functions 29 body string // the body of the main function 30 extra []extra // extra files 31 fail bool // whether the test should fail 32 expensive bool // whether the test requires the expensive check 33 } 34 35 type extra struct { 36 name string 37 contents string 38 } 39 40 var ptrTests = []ptrTest{ 41 { 42 // Passing a pointer to a struct that contains a Go pointer. 43 name: "ptr1", 44 c: `typedef struct s { int *p; } s; void f(s *ps) {}`, 45 body: `C.f(&C.s{new(C.int)})`, 46 fail: true, 47 }, 48 { 49 // Passing a pointer to a struct that contains a Go pointer. 50 name: "ptr2", 51 c: `typedef struct s { int *p; } s; void f(s *ps) {}`, 52 body: `p := &C.s{new(C.int)}; C.f(p)`, 53 fail: true, 54 }, 55 { 56 // Passing a pointer to an int field of a Go struct 57 // that (irrelevantly) contains a Go pointer. 58 name: "ok1", 59 c: `struct s { int i; int *p; }; void f(int *p) {}`, 60 body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.i)`, 61 fail: false, 62 }, 63 { 64 // Passing a pointer to a pointer field of a Go struct. 65 name: "ptr-field", 66 c: `struct s { int i; int *p; }; void f(int **p) {}`, 67 body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.p)`, 68 fail: true, 69 }, 70 { 71 // Passing a pointer to a pointer field of a Go 72 // struct, where the field does not contain a Go 73 // pointer, but another field (irrelevantly) does. 74 name: "ptr-field-ok", 75 c: `struct s { int *p1; int *p2; }; void f(int **p) {}`, 76 body: `p := &C.struct_s{p1: nil, p2: new(C.int)}; C.f(&p.p1)`, 77 fail: false, 78 }, 79 { 80 // Passing the address of a slice with no Go pointers. 81 name: "slice-ok-1", 82 c: `void f(void **p) {}`, 83 imports: []string{"unsafe"}, 84 body: `s := []unsafe.Pointer{nil}; C.f(&s[0])`, 85 fail: false, 86 }, 87 { 88 // Passing the address of a slice with a Go pointer. 89 name: "slice-ptr-1", 90 c: `void f(void **p) {}`, 91 imports: []string{"unsafe"}, 92 body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f(&s[0])`, 93 fail: true, 94 }, 95 { 96 // Passing the address of a slice with a Go pointer, 97 // where we are passing the address of an element that 98 // is not a Go pointer. 99 name: "slice-ptr-2", 100 c: `void f(void **p) {}`, 101 imports: []string{"unsafe"}, 102 body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f(&s[0])`, 103 fail: true, 104 }, 105 { 106 // Passing the address of a slice that is an element 107 // in a struct only looks at the slice. 108 name: "slice-ok-2", 109 c: `void f(void **p) {}`, 110 imports: []string{"unsafe"}, 111 support: `type S struct { p *int; s []unsafe.Pointer }`, 112 body: `i := 0; p := &S{p:&i, s:[]unsafe.Pointer{nil}}; C.f(&p.s[0])`, 113 fail: false, 114 }, 115 { 116 // Passing the address of a slice of an array that is 117 // an element in a struct, with a type conversion. 118 name: "slice-ok-3", 119 c: `void f(void* p) {}`, 120 imports: []string{"unsafe"}, 121 support: `type S struct { p *int; a [4]byte }`, 122 body: `i := 0; p := &S{p:&i}; s := p.a[:]; C.f(unsafe.Pointer(&s[0]))`, 123 fail: false, 124 }, 125 { 126 // Passing the address of a slice of an array that is 127 // an element in a struct, with a type conversion. 128 name: "slice-ok-4", 129 c: `typedef void* PV; void f(PV p) {}`, 130 imports: []string{"unsafe"}, 131 support: `type S struct { p *int; a [4]byte }`, 132 body: `i := 0; p := &S{p:&i}; C.f(C.PV(unsafe.Pointer(&p.a[0])))`, 133 fail: false, 134 }, 135 { 136 // Passing the address of a static variable with no 137 // pointers doesn't matter. 138 name: "varok", 139 c: `void f(char** parg) {}`, 140 support: `var hello = [...]C.char{'h', 'e', 'l', 'l', 'o'}`, 141 body: `parg := [1]*C.char{&hello[0]}; C.f(&parg[0])`, 142 fail: false, 143 }, 144 { 145 // Passing the address of a static variable with 146 // pointers does matter. 147 name: "var", 148 c: `void f(char*** parg) {}`, 149 support: `var hello = [...]*C.char{new(C.char)}`, 150 body: `parg := [1]**C.char{&hello[0]}; C.f(&parg[0])`, 151 fail: true, 152 }, 153 { 154 // Storing a Go pointer into C memory should fail. 155 name: "barrier", 156 c: `#include <stdlib.h> 157 char **f1() { return malloc(sizeof(char*)); } 158 void f2(char **p) {}`, 159 body: `p := C.f1(); *p = new(C.char); C.f2(p)`, 160 fail: true, 161 expensive: true, 162 }, 163 { 164 // Storing a Go pointer into C memory by assigning a 165 // large value should fail. 166 name: "barrier-struct", 167 c: `#include <stdlib.h> 168 struct s { char *a[10]; }; 169 struct s *f1() { return malloc(sizeof(struct s)); } 170 void f2(struct s *p) {}`, 171 body: `p := C.f1(); p.a = [10]*C.char{new(C.char)}; C.f2(p)`, 172 fail: true, 173 expensive: true, 174 }, 175 { 176 // Storing a Go pointer into C memory using a slice 177 // copy should fail. 178 name: "barrier-slice", 179 c: `#include <stdlib.h> 180 struct s { char *a[10]; }; 181 struct s *f1() { return malloc(sizeof(struct s)); } 182 void f2(struct s *p) {}`, 183 body: `p := C.f1(); copy(p.a[:], []*C.char{new(C.char)}); C.f2(p)`, 184 fail: true, 185 expensive: true, 186 }, 187 { 188 // A very large value uses a GC program, which is a 189 // different code path. 190 name: "barrier-gcprog-array", 191 c: `#include <stdlib.h> 192 struct s { char *a[32769]; }; 193 struct s *f1() { return malloc(sizeof(struct s)); } 194 void f2(struct s *p) {}`, 195 body: `p := C.f1(); p.a = [32769]*C.char{new(C.char)}; C.f2(p)`, 196 fail: true, 197 expensive: true, 198 }, 199 { 200 // Similar case, with a source on the heap. 201 name: "barrier-gcprog-array-heap", 202 c: `#include <stdlib.h> 203 struct s { char *a[32769]; }; 204 struct s *f1() { return malloc(sizeof(struct s)); } 205 void f2(struct s *p) {} 206 void f3(void *p) {}`, 207 imports: []string{"unsafe"}, 208 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))`, 209 fail: true, 210 expensive: true, 211 }, 212 { 213 // A GC program with a struct. 214 name: "barrier-gcprog-struct", 215 c: `#include <stdlib.h> 216 struct s { char *a[32769]; }; 217 struct s2 { struct s f; }; 218 struct s2 *f1() { return malloc(sizeof(struct s2)); } 219 void f2(struct s2 *p) {}`, 220 body: `p := C.f1(); p.f = C.struct_s{[32769]*C.char{new(C.char)}}; C.f2(p)`, 221 fail: true, 222 expensive: true, 223 }, 224 { 225 // Similar case, with a source on the heap. 226 name: "barrier-gcprog-struct-heap", 227 c: `#include <stdlib.h> 228 struct s { char *a[32769]; }; 229 struct s2 { struct s f; }; 230 struct s2 *f1() { return malloc(sizeof(struct s2)); } 231 void f2(struct s2 *p) {} 232 void f3(void *p) {}`, 233 imports: []string{"unsafe"}, 234 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))`, 235 fail: true, 236 expensive: true, 237 }, 238 { 239 // Exported functions may not return Go pointers. 240 name: "export1", 241 c: `extern unsigned char *GoFn();`, 242 support: `//export GoFn 243 func GoFn() *byte { return new(byte) }`, 244 body: `C.GoFn()`, 245 fail: true, 246 }, 247 { 248 // Returning a C pointer is fine. 249 name: "exportok", 250 c: `#include <stdlib.h> 251 extern unsigned char *GoFn();`, 252 support: `//export GoFn 253 func GoFn() *byte { return (*byte)(C.malloc(1)) }`, 254 body: `C.GoFn()`, 255 }, 256 { 257 // Passing a Go string is fine. 258 name: "pass-string", 259 c: `#include <stddef.h> 260 typedef struct { const char *p; ptrdiff_t n; } gostring; 261 gostring f(gostring s) { return s; }`, 262 imports: []string{"unsafe"}, 263 body: `s := "a"; r := C.f(*(*C.gostring)(unsafe.Pointer(&s))); if *(*string)(unsafe.Pointer(&r)) != s { panic(r) }`, 264 }, 265 { 266 // Passing a slice of Go strings fails. 267 name: "pass-string-slice", 268 c: `void f(void *p) {}`, 269 imports: []string{"strings", "unsafe"}, 270 support: `type S struct { a [1]string }`, 271 body: `s := S{a:[1]string{strings.Repeat("a", 2)}}; C.f(unsafe.Pointer(&s.a[0]))`, 272 fail: true, 273 }, 274 { 275 // Exported functions may not return strings. 276 name: "ret-string", 277 c: `extern void f();`, 278 imports: []string{"strings"}, 279 support: `//export GoStr 280 func GoStr() string { return strings.Repeat("a", 2) }`, 281 body: `C.f()`, 282 extra: []extra{ 283 { 284 "call.c", 285 `#include <stddef.h> 286 typedef struct { const char *p; ptrdiff_t n; } gostring; 287 extern gostring GoStr(); 288 void f() { GoStr(); }`, 289 }, 290 }, 291 fail: true, 292 }, 293 } 294 295 func main() { 296 os.Exit(doTests()) 297 } 298 299 func doTests() int { 300 gopath, err := ioutil.TempDir("", "cgoerrors") 301 if err != nil { 302 fmt.Fprintln(os.Stderr, err) 303 return 2 304 } 305 defer os.RemoveAll(gopath) 306 307 if err := os.MkdirAll(filepath.Join(gopath, "src"), 0777); err != nil { 308 fmt.Fprintln(os.Stderr, err) 309 return 2 310 } 311 312 workers := runtime.NumCPU() + 1 313 314 var wg sync.WaitGroup 315 c := make(chan int) 316 errs := make(chan int) 317 for i := 0; i < workers; i++ { 318 wg.Add(1) 319 go func() { 320 worker(gopath, c, errs) 321 wg.Done() 322 }() 323 } 324 325 for i := range ptrTests { 326 c <- i 327 } 328 close(c) 329 330 go func() { 331 wg.Wait() 332 close(errs) 333 }() 334 335 tot := 0 336 for e := range errs { 337 tot += e 338 } 339 return tot 340 } 341 342 func worker(gopath string, c, errs chan int) { 343 e := 0 344 for i := range c { 345 if !doOne(gopath, i) { 346 e++ 347 } 348 } 349 if e > 0 { 350 errs <- e 351 } 352 } 353 354 func doOne(gopath string, i int) bool { 355 t := &ptrTests[i] 356 357 dir := filepath.Join(gopath, "src", fmt.Sprintf("dir%d", i)) 358 if err := os.Mkdir(dir, 0777); err != nil { 359 fmt.Fprintln(os.Stderr, err) 360 return false 361 } 362 363 name := filepath.Join(dir, fmt.Sprintf("t%d.go", i)) 364 f, err := os.Create(name) 365 if err != nil { 366 fmt.Fprintln(os.Stderr, err) 367 return false 368 } 369 370 b := bufio.NewWriter(f) 371 fmt.Fprintln(b, `package main`) 372 fmt.Fprintln(b) 373 fmt.Fprintln(b, `/*`) 374 fmt.Fprintln(b, t.c) 375 fmt.Fprintln(b, `*/`) 376 fmt.Fprintln(b, `import "C"`) 377 fmt.Fprintln(b) 378 for _, imp := range t.imports { 379 fmt.Fprintln(b, `import "`+imp+`"`) 380 } 381 if len(t.imports) > 0 { 382 fmt.Fprintln(b) 383 } 384 if len(t.support) > 0 { 385 fmt.Fprintln(b, t.support) 386 fmt.Fprintln(b) 387 } 388 fmt.Fprintln(b, `func main() {`) 389 fmt.Fprintln(b, t.body) 390 fmt.Fprintln(b, `}`) 391 392 if err := b.Flush(); err != nil { 393 fmt.Fprintf(os.Stderr, "flushing %s: %v\n", name, err) 394 return false 395 } 396 if err := f.Close(); err != nil { 397 fmt.Fprintf(os.Stderr, "closing %s: %v\n", name, err) 398 return false 399 } 400 401 for _, e := range t.extra { 402 if err := ioutil.WriteFile(filepath.Join(dir, e.name), []byte(e.contents), 0644); err != nil { 403 fmt.Fprintf(os.Stderr, "writing %s: %v\n", e.name, err) 404 return false 405 } 406 } 407 408 ok := true 409 410 cmd := exec.Command("go", "build") 411 cmd.Dir = dir 412 cmd.Env = addEnv("GOPATH", gopath) 413 buf, err := cmd.CombinedOutput() 414 if err != nil { 415 fmt.Fprintf(os.Stderr, "test %s failed to build: %v\n%s", t.name, err, buf) 416 return false 417 } 418 419 exe := filepath.Join(dir, filepath.Base(dir)) 420 cmd = exec.Command(exe) 421 cmd.Dir = dir 422 423 if t.expensive { 424 cmd.Env = cgocheckEnv("1") 425 buf, err := cmd.CombinedOutput() 426 if err != nil { 427 var errbuf bytes.Buffer 428 if t.fail { 429 fmt.Fprintf(&errbuf, "test %s marked expensive but failed when not expensive: %v\n", t.name, err) 430 } else { 431 fmt.Fprintf(&errbuf, "test %s failed unexpectedly with GODEBUG=cgocheck=1: %v\n", t.name, err) 432 } 433 reportTestOutput(&errbuf, t.name, buf) 434 os.Stderr.Write(errbuf.Bytes()) 435 ok = false 436 } 437 438 cmd = exec.Command(exe) 439 cmd.Dir = dir 440 } 441 442 if t.expensive { 443 cmd.Env = cgocheckEnv("2") 444 } 445 446 buf, err = cmd.CombinedOutput() 447 448 if t.fail { 449 if err == nil { 450 var errbuf bytes.Buffer 451 fmt.Fprintf(&errbuf, "test %s did not fail as expected\n", t.name) 452 reportTestOutput(&errbuf, t.name, buf) 453 os.Stderr.Write(errbuf.Bytes()) 454 ok = false 455 } else if !bytes.Contains(buf, []byte("Go pointer")) { 456 var errbuf bytes.Buffer 457 fmt.Fprintf(&errbuf, "test %s output does not contain expected error (failed with %v)\n", t.name, err) 458 reportTestOutput(&errbuf, t.name, buf) 459 os.Stderr.Write(errbuf.Bytes()) 460 ok = false 461 } 462 } else { 463 if err != nil { 464 var errbuf bytes.Buffer 465 fmt.Fprintf(&errbuf, "test %s failed unexpectedly: %v\n", t.name, err) 466 reportTestOutput(&errbuf, t.name, buf) 467 os.Stderr.Write(errbuf.Bytes()) 468 ok = false 469 } 470 471 if !t.expensive && ok { 472 // Make sure it passes with the expensive checks. 473 cmd := exec.Command(exe) 474 cmd.Dir = dir 475 cmd.Env = cgocheckEnv("2") 476 buf, err := cmd.CombinedOutput() 477 if err != nil { 478 var errbuf bytes.Buffer 479 fmt.Fprintf(&errbuf, "test %s failed unexpectedly with expensive checks: %v\n", t.name, err) 480 reportTestOutput(&errbuf, t.name, buf) 481 os.Stderr.Write(errbuf.Bytes()) 482 ok = false 483 } 484 } 485 } 486 487 if t.fail && ok { 488 cmd = exec.Command(exe) 489 cmd.Dir = dir 490 cmd.Env = cgocheckEnv("0") 491 buf, err := cmd.CombinedOutput() 492 if err != nil { 493 var errbuf bytes.Buffer 494 fmt.Fprintf(&errbuf, "test %s failed unexpectedly with GODEBUG=cgocheck=0: %v\n", t.name, err) 495 reportTestOutput(&errbuf, t.name, buf) 496 os.Stderr.Write(errbuf.Bytes()) 497 ok = false 498 } 499 } 500 501 return ok 502 } 503 504 func reportTestOutput(w io.Writer, name string, buf []byte) { 505 fmt.Fprintf(w, "=== test %s output ===\n", name) 506 fmt.Fprintf(w, "%s", buf) 507 fmt.Fprintf(w, "=== end of test %s output ===\n", name) 508 } 509 510 func cgocheckEnv(val string) []string { 511 return addEnv("GODEBUG", "cgocheck="+val) 512 } 513 514 func addEnv(key, val string) []string { 515 env := []string{key + "=" + val} 516 look := key + "=" 517 for _, e := range os.Environ() { 518 if !strings.HasPrefix(e, look) { 519 env = append(env, e) 520 } 521 } 522 return env 523 }