1
2
3
4
5
6
7 package errorstest
8
9 import (
10 "bytes"
11 "flag"
12 "fmt"
13 "internal/testenv"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "slices"
18 "strings"
19 "sync/atomic"
20 "testing"
21 )
22
23 var tmp = flag.String("tmp", "", "use `dir` for temporary files and do not clean up")
24
25
26 type ptrTest struct {
27 name string
28 c string
29 c1 string
30 imports []string
31 support string
32 body string
33 extra []extra
34 fail bool
35 expensive bool
36 }
37
38 type extra struct {
39 name string
40 contents string
41 }
42
43 var ptrTests = []ptrTest{
44 {
45
46 name: "ptr1",
47 c: `typedef struct s1 { int *p; } s1; void f1(s1 *ps) {}`,
48 body: `C.f1(&C.s1{new(C.int)})`,
49 fail: true,
50 },
51 {
52
53 name: "ptr2",
54 c: `typedef struct s2 { int *p; } s2; void f2(s2 *ps) {}`,
55 body: `p := &C.s2{new(C.int)}; C.f2(p)`,
56 fail: true,
57 },
58 {
59
60
61 name: "ok1",
62 c: `struct s3 { int i; int *p; }; void f3(int *p) {}`,
63 body: `p := &C.struct_s3{i: 0, p: new(C.int)}; C.f3(&p.i)`,
64 fail: false,
65 },
66 {
67
68 name: "ptrfield",
69 c: `struct s4 { int i; int *p; }; void f4(int **p) {}`,
70 body: `p := &C.struct_s4{i: 0, p: new(C.int)}; C.f4(&p.p)`,
71 fail: true,
72 },
73 {
74
75
76
77 name: "ptrfieldok",
78 c: `struct s5 { int *p1; int *p2; }; void f5(int **p) {}`,
79 body: `p := &C.struct_s5{p1: nil, p2: new(C.int)}; C.f5(&p.p1)`,
80 fail: false,
81 },
82 {
83
84 name: "sliceok1",
85 c: `void f6(void **p) {}`,
86 imports: []string{"unsafe"},
87 body: `s := []unsafe.Pointer{nil}; C.f6(&s[0])`,
88 fail: false,
89 },
90 {
91
92 name: "sliceptr1",
93 c: `void f7(void **p) {}`,
94 imports: []string{"unsafe"},
95 body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f7(&s[0])`,
96 fail: true,
97 },
98 {
99
100
101
102 name: "sliceptr2",
103 c: `void f8(void **p) {}`,
104 imports: []string{"unsafe"},
105 body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f8(&s[0])`,
106 fail: true,
107 },
108 {
109
110
111 name: "sliceok2",
112 c: `void f9(void **p) {}`,
113 imports: []string{"unsafe"},
114 support: `type S9 struct { p *int; s []unsafe.Pointer }`,
115 body: `i := 0; p := &S9{p:&i, s:[]unsafe.Pointer{nil}}; C.f9(&p.s[0])`,
116 fail: false,
117 },
118 {
119
120
121 name: "sliceok3",
122 c: `void f10(void* p) {}`,
123 imports: []string{"unsafe"},
124 support: `type S10 struct { p *int; a [4]byte }`,
125 body: `i := 0; p := &S10{p:&i}; s := p.a[:]; C.f10(unsafe.Pointer(&s[0]))`,
126 fail: false,
127 },
128 {
129
130
131 name: "sliceok4",
132 c: `typedef void* PV11; void f11(PV11 p) {}`,
133 imports: []string{"unsafe"},
134 support: `type S11 struct { p *int; a [4]byte }`,
135 body: `i := 0; p := &S11{p:&i}; C.f11(C.PV11(unsafe.Pointer(&p.a[0])))`,
136 fail: false,
137 },
138 {
139
140
141 name: "varok",
142 c: `void f12(char** parg) {}`,
143 support: `var hello12 = [...]C.char{'h', 'e', 'l', 'l', 'o'}`,
144 body: `parg := [1]*C.char{&hello12[0]}; C.f12(&parg[0])`,
145 fail: false,
146 },
147 {
148
149
150 name: "var1",
151 c: `void f13(char*** parg) {}`,
152 support: `var hello13 = [...]*C.char{new(C.char)}`,
153 body: `parg := [1]**C.char{&hello13[0]}; C.f13(&parg[0])`,
154 fail: true,
155 },
156 {
157
158 name: "barrier",
159 c: `#include <stdlib.h>
160 char **f14a() { return malloc(sizeof(char*)); }
161 void f14b(char **p) {}`,
162 body: `p := C.f14a(); *p = new(C.char); C.f14b(p)`,
163 fail: true,
164 expensive: true,
165 },
166 {
167
168 name: "barrierpinnedok",
169 c: `#include <stdlib.h>
170 char **f14a2() { return malloc(sizeof(char*)); }
171 void f14b2(char **p) {}`,
172 imports: []string{"runtime"},
173 body: `var pinr runtime.Pinner; p := C.f14a2(); x := new(C.char); pinr.Pin(x); *p = x; C.f14b2(p); pinr.Unpin()`,
174 fail: false,
175 expensive: true,
176 },
177 {
178
179
180 name: "barrierstruct",
181 c: `#include <stdlib.h>
182 struct s15 { char *a[10]; };
183 struct s15 *f15() { return malloc(sizeof(struct s15)); }
184 void f15b(struct s15 *p) {}`,
185 body: `p := C.f15(); p.a = [10]*C.char{new(C.char)}; C.f15b(p)`,
186 fail: true,
187 expensive: true,
188 },
189 {
190
191
192 name: "barrierslice",
193 c: `#include <stdlib.h>
194 struct s16 { char *a[10]; };
195 struct s16 *f16() { return malloc(sizeof(struct s16)); }
196 void f16b(struct s16 *p) {}`,
197 body: `p := C.f16(); copy(p.a[:], []*C.char{new(C.char)}); C.f16b(p)`,
198 fail: true,
199 expensive: true,
200 },
201 {
202
203
204 name: "barriergcprogarray",
205 c: `#include <stdlib.h>
206 struct s17 { char *a[32769]; };
207 struct s17 *f17() { return malloc(sizeof(struct s17)); }
208 void f17b(struct s17 *p) {}`,
209 body: `p := C.f17(); p.a = [32769]*C.char{new(C.char)}; C.f17b(p)`,
210 fail: true,
211 expensive: true,
212 },
213 {
214
215 name: "barriergcprogarrayheap",
216 c: `#include <stdlib.h>
217 struct s18 { char *a[32769]; };
218 struct s18 *f18() { return malloc(sizeof(struct s18)); }
219 void f18b(struct s18 *p) {}
220 void f18c(void *p) {}`,
221 imports: []string{"unsafe"},
222 body: `p := C.f18(); n := &[32769]*C.char{new(C.char)}; p.a = *n; C.f18b(p); n[0] = nil; C.f18c(unsafe.Pointer(n))`,
223 fail: true,
224 expensive: true,
225 },
226 {
227
228 name: "barriergcprogstruct",
229 c: `#include <stdlib.h>
230 struct s19a { char *a[32769]; };
231 struct s19b { struct s19a f; };
232 struct s19b *f19() { return malloc(sizeof(struct s19b)); }
233 void f19b(struct s19b *p) {}`,
234 body: `p := C.f19(); p.f = C.struct_s19a{[32769]*C.char{new(C.char)}}; C.f19b(p)`,
235 fail: true,
236 expensive: true,
237 },
238 {
239
240 name: "barriergcprogstructheap",
241 c: `#include <stdlib.h>
242 struct s20a { char *a[32769]; };
243 struct s20b { struct s20a f; };
244 struct s20b *f20() { return malloc(sizeof(struct s20b)); }
245 void f20b(struct s20b *p) {}
246 void f20c(void *p) {}`,
247 imports: []string{"unsafe"},
248 body: `p := C.f20(); n := &C.struct_s20a{[32769]*C.char{new(C.char)}}; p.f = *n; C.f20b(p); n.a[0] = nil; C.f20c(unsafe.Pointer(n))`,
249 fail: true,
250 expensive: true,
251 },
252 {
253
254 name: "export1",
255 c: `#ifdef _WIN32
256 __declspec(dllexport)
257 #endif
258 extern unsigned char *GoFn21();`,
259 support: `//export GoFn21
260 func GoFn21() *byte { return new(byte) }`,
261 body: `C.GoFn21()`,
262 fail: true,
263 },
264 {
265
266 name: "exportok",
267 c: `#include <stdlib.h>
268 #ifdef _WIN32
269 __declspec(dllexport)
270 #endif
271 extern unsigned char *GoFn22();`,
272 support: `//export GoFn22
273 func GoFn22() *byte { return (*byte)(C.malloc(1)) }`,
274 body: `C.GoFn22()`,
275 },
276 {
277
278 name: "passstring",
279 c: `#include <stddef.h>
280 typedef struct { const char *p; ptrdiff_t n; } gostring23;
281 gostring23 f23(gostring23 s) { return s; }`,
282 imports: []string{"unsafe"},
283 body: `s := "a"; r := C.f23(*(*C.gostring23)(unsafe.Pointer(&s))); if *(*string)(unsafe.Pointer(&r)) != s { panic(r) }`,
284 },
285 {
286
287 name: "passstringslice",
288 c: `void f24(void *p) {}`,
289 imports: []string{"strings", "unsafe"},
290 support: `type S24 struct { a [1]string }`,
291 body: `s := S24{a:[1]string{strings.Repeat("a", 2)}}; C.f24(unsafe.Pointer(&s.a[0]))`,
292 fail: true,
293 },
294 {
295
296 name: "retstring",
297 c: `extern void f25();`,
298 imports: []string{"strings"},
299 support: `//export GoStr25
300 func GoStr25() string { return strings.Repeat("a", 2) }`,
301 body: `C.f25()`,
302 c1: `#include <stddef.h>
303 typedef struct { const char *p; ptrdiff_t n; } gostring25;
304 extern gostring25 GoStr25();
305 void f25() { GoStr25(); }`,
306 fail: true,
307 },
308 {
309
310
311
312
313
314 name: "ptrdata1",
315 c: `#include <stdlib.h>
316 void f26(void* p) {}`,
317 imports: []string{"unsafe"},
318 support: `type S26 struct { p *int; a [8*8]byte; u uintptr }`,
319 body: `i := 0; p := &S26{u:uintptr(unsafe.Pointer(&i))}; q := (*S26)(C.malloc(C.size_t(unsafe.Sizeof(*p)))); *q = *p; C.f26(unsafe.Pointer(q))`,
320 fail: false,
321 },
322 {
323
324 name: "ptrdata2",
325 c: `#include <stdlib.h>
326 void f27(void* p) {}`,
327 imports: []string{"unsafe"},
328 support: `type S27 struct { p *int; a [32769*8]byte; q *int; u uintptr }`,
329 body: `i := 0; p := S27{u:uintptr(unsafe.Pointer(&i))}; q := (*S27)(C.malloc(C.size_t(unsafe.Sizeof(p)))); *q = p; C.f27(unsafe.Pointer(q))`,
330 fail: false,
331 },
332 {
333
334
335 name: "defer1",
336 c: `typedef struct s28 { int *p; } s28; void f28(s28 *ps) {}`,
337 body: `p := &C.s28{}; defer C.f28(p); p.p = new(C.int)`,
338 fail: true,
339 },
340 {
341
342
343 name: "union1",
344 c: `typedef union { char **p; unsigned long i; } u29; void f29(u29 *pu) {}`,
345 imports: []string{"unsafe"},
346 body: `var b C.char; p := &b; C.f29((*C.u29)(unsafe.Pointer(&p)))`,
347 fail: true,
348 },
349 {
350
351
352
353
354
355 name: "union2",
356 c: `typedef union { unsigned long i; } u39; void f39(u39 *pu) {}`,
357 imports: []string{"unsafe"},
358 body: `var b C.char; p := &b; C.f39((*C.u39)(unsafe.Pointer(&p)))`,
359 fail: false,
360 },
361 {
362
363 name: "preemptduringcall",
364 c: `void f30() {}`,
365 imports: []string{"runtime", "sync"},
366 body: `var wg sync.WaitGroup; wg.Add(100); for i := 0; i < 100; i++ { go func(i int) { for j := 0; j < 100; j++ { C.f30(); runtime.GOMAXPROCS(i) }; wg.Done() }(i) }; wg.Wait()`,
367 fail: false,
368 },
369 {
370
371 name: "deadline",
372 c: `#define US31 10`,
373 imports: []string{"os", "time"},
374 body: `r, _, _ := os.Pipe(); r.SetDeadline(time.Now().Add(C.US31 * time.Microsecond))`,
375 fail: false,
376 },
377 {
378
379 name: "chanrecv",
380 c: `void f32(char** p) {}`,
381 imports: []string{"time"},
382 body: `c := make(chan []*C.char, 2); c <- make([]*C.char, 1); go func() { time.Sleep(10 * time.Second); panic("received twice from chan") }(); C.f32(&(<-c)[0]);`,
383 fail: false,
384 },
385 {
386
387
388
389 name: "structfield",
390 c: `void f33(void* p) {}`,
391 imports: []string{"unsafe"},
392 support: `type S33 struct { p *int; a [8]byte; u uintptr }`,
393 body: `s := &S33{p: new(int)}; C.f33(unsafe.Pointer(&s.a))`,
394 fail: false,
395 },
396 {
397
398
399
400 name: "structfield2",
401 c: `void f34(void* p, int r, void* s) {}`,
402 imports: []string{"unsafe"},
403 support: `type S34 struct { a [8]byte; p *int; b int64; }`,
404 body: `s := &S34{p: new(int)}; C.f34(unsafe.Pointer(&s.a), 32, unsafe.Pointer(&s.b))`,
405 fail: false,
406 },
407 {
408
409
410
411 name: "defer2",
412 c: `void f35(char **pc) {}`,
413 support: `type S35a struct { s []*C.char }; type S35b struct { ps *S35a }`,
414 body: `p := &S35b{&S35a{[]*C.char{nil}}}; defer C.f35(&p.ps.s[0]); p.ps = nil`,
415 fail: false,
416 },
417 {
418
419
420 name: "buffer",
421 c: `void f36(void *p) {}`,
422 imports: []string{"bytes", "unsafe"},
423 body: `var b bytes.Buffer; b.WriteString("a"); C.f36(unsafe.Pointer(&b.Bytes()[0]))`,
424 fail: false,
425 },
426 {
427
428 name: "finalizer",
429 c: `// Nothing to declare.`,
430 imports: []string{"os"},
431 support: `func open37() { os.Open(os.Args[0]) }; var G37 [][]byte`,
432 body: `for i := 0; i < 10000; i++ { G37 = append(G37, make([]byte, 4096)); if i % 100 == 0 { G37 = nil; open37() } }`,
433 fail: false,
434 },
435 {
436
437 name: "structof",
438 c: `// Nothing to declare.`,
439 imports: []string{"reflect"},
440 support: `type MyInt38 int; func (i MyInt38) Get() int { return int(i) }; type Getter38 interface { Get() int }`,
441 body: `t := reflect.StructOf([]reflect.StructField{{Name: "MyInt38", Type: reflect.TypeOf(MyInt38(0)), Anonymous: true}}); v := reflect.New(t).Elem(); v.Interface().(Getter38).Get()`,
442 fail: false,
443 },
444 {
445
446
447 name: "structfieldcast",
448 c: `struct S40i { int i; int* p; }; void f40(struct S40i* p) {}`,
449 support: `type S40 struct { p *int; a C.struct_S40i }`,
450 body: `s := &S40{p: new(int)}; C.f40((*C.struct_S40i)(&s.a))`,
451 fail: false,
452 },
453 {
454
455 name: "stringdata",
456 c: `void f41(void* p) {}`,
457 imports: []string{"unsafe"},
458 body: `s := struct { a [4]byte; p *int }{p: new(int)}; str := unsafe.String(&s.a[0], 4); C.f41(unsafe.Pointer(unsafe.StringData(str)))`,
459 fail: false,
460 },
461 {
462 name: "slicedata",
463 c: `void f42(void* p) {}`,
464 imports: []string{"unsafe"},
465 body: `s := []*byte{nil, new(byte)}; C.f42(unsafe.Pointer(unsafe.SliceData(s)))`,
466 fail: true,
467 },
468 {
469 name: "slicedata2",
470 c: `void f43(void* p) {}`,
471 imports: []string{"unsafe"},
472 body: `s := struct { a [4]byte; p *int }{p: new(int)}; C.f43(unsafe.Pointer(unsafe.SliceData(s.a[:])))`,
473 fail: false,
474 },
475 }
476
477 func TestPointerChecks(t *testing.T) {
478 testenv.MustHaveGoBuild(t)
479 testenv.MustHaveCGO(t)
480
481 var gopath string
482 var dir string
483 if *tmp != "" {
484 gopath = *tmp
485 dir = ""
486 } else {
487 d, err := os.MkdirTemp("", filepath.Base(t.Name()))
488 if err != nil {
489 t.Fatal(err)
490 }
491 dir = d
492 gopath = d
493 }
494
495 exe := buildPtrTests(t, gopath, false)
496 exe2 := buildPtrTests(t, gopath, true)
497
498
499
500
501
502
503 var pending int32
504 for _, pt := range ptrTests {
505 pt := pt
506 t.Run(pt.name, func(t *testing.T) {
507 atomic.AddInt32(&pending, +1)
508 defer func() {
509 if atomic.AddInt32(&pending, -1) == 0 {
510 os.RemoveAll(dir)
511 }
512 }()
513 testOne(t, pt, exe, exe2)
514 })
515 }
516 }
517
518 func buildPtrTests(t *testing.T, gopath string, cgocheck2 bool) (exe string) {
519
520 src := filepath.Join(gopath, "src", "ptrtest")
521 if err := os.MkdirAll(src, 0777); err != nil {
522 t.Fatal(err)
523 }
524 if err := os.WriteFile(filepath.Join(src, "go.mod"), []byte("module ptrtest\ngo 1.20"), 0666); err != nil {
525 t.Fatal(err)
526 }
527
528
529
530 var cgo1, cgo2 bytes.Buffer
531 fmt.Fprintf(&cgo1, "package main\n\n/*\n")
532 fmt.Fprintf(&cgo2, "package main\n\n/*\n")
533
534
535 for _, pt := range ptrTests {
536 cgo := &cgo1
537 if strings.Contains(pt.support, "//export") {
538 cgo = &cgo2
539 }
540 fmt.Fprintf(cgo, "%s\n", pt.c)
541 fmt.Fprintf(&cgo1, "%s\n", pt.c1)
542 }
543 fmt.Fprintf(&cgo1, "*/\nimport \"C\"\n\n")
544 fmt.Fprintf(&cgo2, "*/\nimport \"C\"\n\n")
545
546
547 did1 := make(map[string]bool)
548 did2 := make(map[string]bool)
549 did1["os"] = true
550 fmt.Fprintf(&cgo1, "import \"os\"\n")
551
552 for _, pt := range ptrTests {
553 did := did1
554 cgo := &cgo1
555 if strings.Contains(pt.support, "//export") {
556 did = did2
557 cgo = &cgo2
558 }
559 for _, imp := range pt.imports {
560 if !did[imp] {
561 did[imp] = true
562 fmt.Fprintf(cgo, "import %q\n", imp)
563 }
564 }
565 }
566
567
568 for _, pt := range ptrTests {
569 cgo := &cgo1
570 if strings.Contains(pt.support, "//export") {
571 cgo = &cgo2
572 }
573 fmt.Fprintf(cgo, "%s\nfunc %s() {\n%s\n}\n", pt.support, pt.name, pt.body)
574 }
575
576
577 fmt.Fprintf(&cgo1, "var funcs = map[string]func() {\n")
578 for _, pt := range ptrTests {
579 fmt.Fprintf(&cgo1, "\t%q: %s,\n", pt.name, pt.name)
580 }
581 fmt.Fprintf(&cgo1, "}\n\n")
582 fmt.Fprintf(&cgo1, "%s\n", ptrTestMain)
583
584 if err := os.WriteFile(filepath.Join(src, "cgo1.go"), cgo1.Bytes(), 0666); err != nil {
585 t.Fatal(err)
586 }
587 if err := os.WriteFile(filepath.Join(src, "cgo2.go"), cgo2.Bytes(), 0666); err != nil {
588 t.Fatal(err)
589 }
590
591 exeName := "ptrtest.exe"
592 if cgocheck2 {
593 exeName = "ptrtest2.exe"
594 }
595 cmd := exec.Command("go", "build", "-o", exeName)
596 cmd.Dir = src
597 cmd.Env = append(os.Environ(), "GOPATH="+gopath)
598
599
600 goexperiment := strings.Split(os.Getenv("GOEXPERIMENT"), ",")
601 if len(goexperiment) == 1 && goexperiment[0] == "" {
602 goexperiment = nil
603 }
604 i := slices.Index(goexperiment, "cgocheck2")
605 changed := false
606 if cgocheck2 && i < 0 {
607 goexperiment = append(goexperiment, "cgocheck2")
608 changed = true
609 } else if !cgocheck2 && i >= 0 {
610 goexperiment = append(goexperiment[:i], goexperiment[i+1:]...)
611 changed = true
612 }
613 if changed {
614 cmd.Env = append(cmd.Env, "GOEXPERIMENT="+strings.Join(goexperiment, ","))
615 }
616
617 out, err := cmd.CombinedOutput()
618 if err != nil {
619 t.Fatalf("go build: %v\n%s", err, out)
620 }
621
622 return filepath.Join(src, exeName)
623 }
624
625 const ptrTestMain = `
626 func main() {
627 for _, arg := range os.Args[1:] {
628 f := funcs[arg]
629 if f == nil {
630 panic("missing func "+arg)
631 }
632 f()
633 }
634 }
635 `
636
637 var csem = make(chan bool, 16)
638
639 func testOne(t *testing.T, pt ptrTest, exe, exe2 string) {
640 t.Parallel()
641
642
643
644 runcmd := func(cgocheck string) ([]byte, error) {
645 csem <- true
646 defer func() { <-csem }()
647 x := exe
648 if cgocheck == "2" {
649 x = exe2
650 cgocheck = "1"
651 }
652 cmd := exec.Command(x, pt.name)
653 cmd.Env = append(os.Environ(), "GODEBUG=cgocheck="+cgocheck)
654 return cmd.CombinedOutput()
655 }
656
657 if pt.expensive {
658 buf, err := runcmd("1")
659 if err != nil {
660 t.Logf("%s", buf)
661 if pt.fail {
662 t.Fatalf("test marked expensive, but failed when not expensive: %v", err)
663 } else {
664 t.Errorf("failed unexpectedly with GODEBUG=cgocheck=1: %v", err)
665 }
666 }
667
668 }
669
670 cgocheck := ""
671 if pt.expensive {
672 cgocheck = "2"
673 }
674
675 buf, err := runcmd(cgocheck)
676 if pt.fail {
677 if err == nil {
678 t.Logf("%s", buf)
679 t.Fatalf("did not fail as expected")
680 } else if !bytes.Contains(buf, []byte("Go pointer")) {
681 t.Logf("%s", buf)
682 t.Fatalf("did not print expected error (failed with %v)", err)
683 }
684 } else {
685 if err != nil {
686 t.Logf("%s", buf)
687 t.Fatalf("failed unexpectedly: %v", err)
688 }
689
690 if !pt.expensive {
691
692 buf, err := runcmd("2")
693 if err != nil {
694 t.Logf("%s", buf)
695 t.Fatalf("failed unexpectedly with expensive checks: %v", err)
696 }
697 }
698 }
699
700 if pt.fail {
701 buf, err := runcmd("0")
702 if err != nil {
703 t.Logf("%s", buf)
704 t.Fatalf("failed unexpectedly with GODEBUG=cgocheck=0: %v", err)
705 }
706 }
707 }
708
View as plain text