]> Cypherpunks.ru repositories - gostls13.git/blob - misc/cgo/errors/ptr.go
bbcaaabecb7bd4fb4ebed2eb48a70fe89564047e
[gostls13.git] / misc / cgo / errors / ptr.go
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 }