mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			517 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			517 lines
		
	
	
		
			15 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 errorstest
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| )
 | |
| 
 | |
| // ptrTest is the tests without the boilerplate.
 | |
| type ptrTest struct {
 | |
| 	name      string   // for reporting
 | |
| 	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{
 | |
| 	{
 | |
| 		// Passing a pointer to a struct that contains a Go pointer.
 | |
| 		name: "ptr1",
 | |
| 		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.
 | |
| 		name: "ptr2",
 | |
| 		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.
 | |
| 		name: "ok1",
 | |
| 		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.
 | |
| 		name: "ptr-field",
 | |
| 		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.
 | |
| 		name: "ptr-field-ok",
 | |
| 		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.
 | |
| 		name:    "slice-ok-1",
 | |
| 		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.
 | |
| 		name:    "slice-ptr-1",
 | |
| 		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.
 | |
| 		name:    "slice-ptr-2",
 | |
| 		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.
 | |
| 		name:    "slice-ok-2",
 | |
| 		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 slice of an array that is
 | |
| 		// an element in a struct, with a type conversion.
 | |
| 		name:    "slice-ok-3",
 | |
| 		c:       `void f(void* p) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		support: `type S struct { p *int; a [4]byte }`,
 | |
| 		body:    `i := 0; p := &S{p:&i}; s := p.a[:]; C.f(unsafe.Pointer(&s[0]))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Passing the address of a slice of an array that is
 | |
| 		// an element in a struct, with a type conversion.
 | |
| 		name:    "slice-ok-4",
 | |
| 		c:       `typedef void* PV; void f(PV p) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		support: `type S struct { p *int; a [4]byte }`,
 | |
| 		body:    `i := 0; p := &S{p:&i}; C.f(C.PV(unsafe.Pointer(&p.a[0])))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Passing the address of a static variable with no
 | |
| 		// pointers doesn't matter.
 | |
| 		name:    "varok",
 | |
| 		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.
 | |
| 		name:    "var",
 | |
| 		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,
 | |
| 	},
 | |
| 	{
 | |
| 		// Storing a Go pointer into C memory should fail.
 | |
| 		name: "barrier",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     char **f1() { return malloc(sizeof(char*)); }
 | |
|                     void f2(char **p) {}`,
 | |
| 		body:      `p := C.f1(); *p = new(C.char); C.f2(p)`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Storing a Go pointer into C memory by assigning a
 | |
| 		// large value should fail.
 | |
| 		name: "barrier-struct",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[10]; };
 | |
|                     struct s *f1() { return malloc(sizeof(struct s)); }
 | |
|                     void f2(struct s *p) {}`,
 | |
| 		body:      `p := C.f1(); p.a = [10]*C.char{new(C.char)}; C.f2(p)`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Storing a Go pointer into C memory using a slice
 | |
| 		// copy should fail.
 | |
| 		name: "barrier-slice",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[10]; };
 | |
|                     struct s *f1() { return malloc(sizeof(struct s)); }
 | |
|                     void f2(struct s *p) {}`,
 | |
| 		body:      `p := C.f1(); copy(p.a[:], []*C.char{new(C.char)}); C.f2(p)`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// A very large value uses a GC program, which is a
 | |
| 		// different code path.
 | |
| 		name: "barrier-gcprog-array",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[32769]; };
 | |
|                     struct s *f1() { return malloc(sizeof(struct s)); }
 | |
|                     void f2(struct s *p) {}`,
 | |
| 		body:      `p := C.f1(); p.a = [32769]*C.char{new(C.char)}; C.f2(p)`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Similar case, with a source on the heap.
 | |
| 		name: "barrier-gcprog-array-heap",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[32769]; };
 | |
|                     struct s *f1() { return malloc(sizeof(struct s)); }
 | |
|                     void f2(struct s *p) {}
 | |
|                     void f3(void *p) {}`,
 | |
| 		imports:   []string{"unsafe"},
 | |
| 		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))`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// A GC program with a struct.
 | |
| 		name: "barrier-gcprog-struct",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[32769]; };
 | |
|                     struct s2 { struct s f; };
 | |
|                     struct s2 *f1() { return malloc(sizeof(struct s2)); }
 | |
|                     void f2(struct s2 *p) {}`,
 | |
| 		body:      `p := C.f1(); p.f = C.struct_s{[32769]*C.char{new(C.char)}}; C.f2(p)`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Similar case, with a source on the heap.
 | |
| 		name: "barrier-gcprog-struct-heap",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     struct s { char *a[32769]; };
 | |
|                     struct s2 { struct s f; };
 | |
|                     struct s2 *f1() { return malloc(sizeof(struct s2)); }
 | |
|                     void f2(struct s2 *p) {}
 | |
|                     void f3(void *p) {}`,
 | |
| 		imports:   []string{"unsafe"},
 | |
| 		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))`,
 | |
| 		fail:      true,
 | |
| 		expensive: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Exported functions may not return Go pointers.
 | |
| 		name: "export1",
 | |
| 		c:    `extern unsigned char *GoFn();`,
 | |
| 		support: `//export GoFn
 | |
|                           func GoFn() *byte { return new(byte) }`,
 | |
| 		body: `C.GoFn()`,
 | |
| 		fail: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Returning a C pointer is fine.
 | |
| 		name: "exportok",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     extern unsigned char *GoFn();`,
 | |
| 		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 <stddef.h>
 | |
|                     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 <stddef.h>
 | |
|                                  typedef struct { const char *p; ptrdiff_t n; } gostring;
 | |
|                                  extern gostring GoStr();
 | |
|                                  void f() { GoStr(); }`,
 | |
| 			},
 | |
| 		},
 | |
| 		fail: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Don't check non-pointer data.
 | |
| 		// Uses unsafe code to get a pointer we shouldn't check.
 | |
| 		// Although we use unsafe, the uintptr represents an integer
 | |
| 		// that happens to have the same representation as a pointer;
 | |
| 		// that is, we are testing something that is not unsafe.
 | |
| 		name: "ptrdata1",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     void f(void* p) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		support: `type S struct { p *int; a [8*8]byte; u uintptr }`,
 | |
| 		body:    `i := 0; p := &S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(*p)))); *q = *p; C.f(unsafe.Pointer(q))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Like ptrdata1, but with a type that uses a GC program.
 | |
| 		name: "ptrdata2",
 | |
| 		c: `#include <stdlib.h>
 | |
|                     void f(void* p) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		support: `type S struct { p *int; a [32769*8]byte; q *int; u uintptr }`,
 | |
| 		body:    `i := 0; p := S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(p)))); *q = p; C.f(unsafe.Pointer(q))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Check deferred pointers when they are used, not
 | |
| 		// when the defer statement is run.
 | |
| 		name: "defer",
 | |
| 		c:    `typedef struct s { int *p; } s; void f(s *ps) {}`,
 | |
| 		body: `p := &C.s{}; defer C.f(p); p.p = new(C.int)`,
 | |
| 		fail: true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Check a pointer to a union if the union has any
 | |
| 		// pointer fields.
 | |
| 		name:    "union1",
 | |
| 		c:       `typedef union { char **p; unsigned long i; } u; void f(u *pu) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		body:    `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`,
 | |
| 		fail:    true,
 | |
| 	},
 | |
| 	{
 | |
| 		// Don't check a pointer to a union if the union does
 | |
| 		// not have any pointer fields.
 | |
| 		// Like ptrdata1 above, the uintptr represents an
 | |
| 		// integer that happens to have the same
 | |
| 		// representation as a pointer.
 | |
| 		name:    "union2",
 | |
| 		c:       `typedef union { unsigned long i; } u; void f(u *pu) {}`,
 | |
| 		imports: []string{"unsafe"},
 | |
| 		body:    `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Test preemption while entering a cgo call. Issue #21306.
 | |
| 		name:    "preempt-during-call",
 | |
| 		c:       `void f() {}`,
 | |
| 		imports: []string{"runtime", "sync"},
 | |
| 		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.f(); runtime.GOMAXPROCS(i) }; wg.Done() }(i) }; wg.Wait()`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| 	{
 | |
| 		// Test poller deadline with cgocheck=2.  Issue #23435.
 | |
| 		name:    "deadline",
 | |
| 		c:       `#define US 10`,
 | |
| 		imports: []string{"os", "time"},
 | |
| 		body:    `r, _, _ := os.Pipe(); r.SetDeadline(time.Now().Add(C.US * time.Microsecond))`,
 | |
| 		fail:    false,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func TestPointerChecks(t *testing.T) {
 | |
| 	for _, pt := range ptrTests {
 | |
| 		pt := pt
 | |
| 		t.Run(pt.name, func(t *testing.T) {
 | |
| 			testOne(t, pt)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testOne(t *testing.T, pt ptrTest) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	gopath, err := ioutil.TempDir("", filepath.Base(t.Name()))
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(gopath)
 | |
| 
 | |
| 	src := filepath.Join(gopath, "src")
 | |
| 	if err := os.Mkdir(src, 0777); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	name := filepath.Join(src, fmt.Sprintf("%s.go", filepath.Base(t.Name())))
 | |
| 	f, err := os.Create(name)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	b := bufio.NewWriter(f)
 | |
| 	fmt.Fprintln(b, `package main`)
 | |
| 	fmt.Fprintln(b)
 | |
| 	fmt.Fprintln(b, `/*`)
 | |
| 	fmt.Fprintln(b, pt.c)
 | |
| 	fmt.Fprintln(b, `*/`)
 | |
| 	fmt.Fprintln(b, `import "C"`)
 | |
| 	fmt.Fprintln(b)
 | |
| 	for _, imp := range pt.imports {
 | |
| 		fmt.Fprintln(b, `import "`+imp+`"`)
 | |
| 	}
 | |
| 	if len(pt.imports) > 0 {
 | |
| 		fmt.Fprintln(b)
 | |
| 	}
 | |
| 	if len(pt.support) > 0 {
 | |
| 		fmt.Fprintln(b, pt.support)
 | |
| 		fmt.Fprintln(b)
 | |
| 	}
 | |
| 	fmt.Fprintln(b, `func main() {`)
 | |
| 	fmt.Fprintln(b, pt.body)
 | |
| 	fmt.Fprintln(b, `}`)
 | |
| 
 | |
| 	if err := b.Flush(); err != nil {
 | |
| 		t.Fatalf("flushing %s: %v", name, err)
 | |
| 	}
 | |
| 	if err := f.Close(); err != nil {
 | |
| 		t.Fatalf("closing %s: %v", name, err)
 | |
| 	}
 | |
| 
 | |
| 	for _, e := range pt.extra {
 | |
| 		if err := ioutil.WriteFile(filepath.Join(src, e.name), []byte(e.contents), 0644); err != nil {
 | |
| 			t.Fatalf("writing %s: %v", e.name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	args := func(cmd *exec.Cmd) string {
 | |
| 		return strings.Join(cmd.Args, " ")
 | |
| 	}
 | |
| 
 | |
| 	cmd := exec.Command("go", "build")
 | |
| 	cmd.Dir = src
 | |
| 	cmd.Env = addEnv("GOPATH", gopath)
 | |
| 	buf, err := cmd.CombinedOutput()
 | |
| 	if err != nil {
 | |
| 		t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 		t.Fatalf("failed to build: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	exe := filepath.Join(src, filepath.Base(src))
 | |
| 	cmd = exec.Command(exe)
 | |
| 	cmd.Dir = src
 | |
| 
 | |
| 	if pt.expensive {
 | |
| 		cmd.Env = cgocheckEnv("1")
 | |
| 		buf, err := cmd.CombinedOutput()
 | |
| 		if err != nil {
 | |
| 			t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 			if pt.fail {
 | |
| 				t.Fatalf("test marked expensive, but failed when not expensive: %v", err)
 | |
| 			} else {
 | |
| 				t.Errorf("failed unexpectedly with GODEBUG=cgocheck=1: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		cmd = exec.Command(exe)
 | |
| 		cmd.Dir = src
 | |
| 	}
 | |
| 
 | |
| 	if pt.expensive {
 | |
| 		cmd.Env = cgocheckEnv("2")
 | |
| 	}
 | |
| 
 | |
| 	buf, err = cmd.CombinedOutput()
 | |
| 	if pt.fail {
 | |
| 		if err == nil {
 | |
| 			t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 			t.Fatalf("did not fail as expected")
 | |
| 		} else if !bytes.Contains(buf, []byte("Go pointer")) {
 | |
| 			t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 			t.Fatalf("did not print expected error (failed with %v)", err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		if err != nil {
 | |
| 			t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 			t.Fatalf("failed unexpectedly: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		if !pt.expensive {
 | |
| 			// Make sure it passes with the expensive checks.
 | |
| 			cmd := exec.Command(exe)
 | |
| 			cmd.Dir = src
 | |
| 			cmd.Env = cgocheckEnv("2")
 | |
| 			buf, err := cmd.CombinedOutput()
 | |
| 			if err != nil {
 | |
| 				t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 				t.Fatalf("failed unexpectedly with expensive checks: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if pt.fail {
 | |
| 		cmd = exec.Command(exe)
 | |
| 		cmd.Dir = src
 | |
| 		cmd.Env = cgocheckEnv("0")
 | |
| 		buf, err := cmd.CombinedOutput()
 | |
| 		if err != nil {
 | |
| 			t.Logf("%#q:\n%s", args(cmd), buf)
 | |
| 			t.Fatalf("failed unexpectedly with GODEBUG=cgocheck=0: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func cgocheckEnv(val string) []string {
 | |
| 	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, look) {
 | |
| 			env = append(env, e)
 | |
| 		}
 | |
| 	}
 | |
| 	return env
 | |
| }
 |