aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2017-08-01 19:30:57 -0400
committerBryan Mills <bcmills@google.com>2017-08-17 15:05:08 +0000
commit39d4693bac5ed85765a05f25ac68b2d4771ee470 (patch)
treedf66f6b61e57a94837d72623a63a51101def93bb
parentd46953c9f61ee9fe9852be86bf7bae02e1b82e36 (diff)
downloadgo-39d4693bac5ed85765a05f25ac68b2d4771ee470.tar.gz
go-39d4693bac5ed85765a05f25ac68b2d4771ee470.zip
misc/cgo/testsanitizers: convert test.bash to Go
This makes it much easier to run individual failing subtests. Use $(go env CC) instead of always defaulting to clang; this makes it easier to test with other compilers. Run C binaries to detect incompatible compiler/kernel pairings instead of sniffing versions. updates #21196 Change-Id: I0debb3cc4a4244df44b825157ffdc97b5c09338d Reviewed-on: https://go-review.googlesource.com/52910 Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
-rw-r--r--misc/cgo/testsanitizers/cc_test.go441
-rw-r--r--misc/cgo/testsanitizers/cshared_test.go74
-rw-r--r--misc/cgo/testsanitizers/msan_test.go55
-rw-r--r--misc/cgo/testsanitizers/src/msan.go (renamed from misc/cgo/testsanitizers/msan.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan2.go (renamed from misc/cgo/testsanitizers/msan2.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan2_cmsan.go38
-rw-r--r--misc/cgo/testsanitizers/src/msan3.go (renamed from misc/cgo/testsanitizers/msan3.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan4.go (renamed from misc/cgo/testsanitizers/msan4.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan5.go (renamed from misc/cgo/testsanitizers/msan5.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan_fail.go (renamed from misc/cgo/testsanitizers/msan_fail.go)0
-rw-r--r--misc/cgo/testsanitizers/src/msan_shared.go (renamed from misc/cgo/testsanitizers/msan_shared.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan.go (renamed from misc/cgo/testsanitizers/tsan.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan10.go (renamed from misc/cgo/testsanitizers/tsan10.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan11.go (renamed from misc/cgo/testsanitizers/tsan11.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan12.go (renamed from misc/cgo/testsanitizers/tsan12.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan2.go (renamed from misc/cgo/testsanitizers/tsan2.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan3.go (renamed from misc/cgo/testsanitizers/tsan3.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan4.go (renamed from misc/cgo/testsanitizers/tsan4.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan5.go (renamed from misc/cgo/testsanitizers/tsan5.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan6.go (renamed from misc/cgo/testsanitizers/tsan6.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan7.go (renamed from misc/cgo/testsanitizers/tsan7.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan8.go (renamed from misc/cgo/testsanitizers/tsan8.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan9.go (renamed from misc/cgo/testsanitizers/tsan9.go)0
-rw-r--r--misc/cgo/testsanitizers/src/tsan_shared.go (renamed from misc/cgo/testsanitizers/tsan_shared.go)0
-rwxr-xr-xmisc/cgo/testsanitizers/test.bash233
-rw-r--r--misc/cgo/testsanitizers/tsan_test.go56
-rw-r--r--src/cmd/dist/test.go2
27 files changed, 665 insertions, 234 deletions
diff --git a/misc/cgo/testsanitizers/cc_test.go b/misc/cgo/testsanitizers/cc_test.go
new file mode 100644
index 0000000000..cacb0d93df
--- /dev/null
+++ b/misc/cgo/testsanitizers/cc_test.go
@@ -0,0 +1,441 @@
+// Copyright 2017 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.
+
+// sanitizers_test checks the use of Go with sanitizers like msan, asan, etc.
+// See https://github.com/google/sanitizers.
+package sanitizers_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "unicode"
+)
+
+var overcommit struct {
+ sync.Once
+ value int
+ err error
+}
+
+// requireOvercommit skips t if the kernel does not allow overcommit.
+func requireOvercommit(t *testing.T) {
+ t.Helper()
+
+ overcommit.Once.Do(func() {
+ var out []byte
+ out, overcommit.err = ioutil.ReadFile("/proc/sys/vm/overcommit_memory")
+ if overcommit.err != nil {
+ return
+ }
+ overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out)))
+ })
+
+ if overcommit.err != nil {
+ t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err)
+ }
+ if overcommit.value == 2 {
+ t.Skip("vm.overcommit_memory=2")
+ }
+}
+
+var env struct {
+ sync.Once
+ m map[string]string
+ err error
+}
+
+// goEnv returns the output of $(go env) as a map.
+func goEnv(key string) (string, error) {
+ env.Once.Do(func() {
+ var out []byte
+ out, env.err = exec.Command("go", "env", "-json").Output()
+ if env.err != nil {
+ return
+ }
+
+ env.m = make(map[string]string)
+ env.err = json.Unmarshal(out, &env.m)
+ })
+ if env.err != nil {
+ return "", env.err
+ }
+
+ v, ok := env.m[key]
+ if !ok {
+ return "", fmt.Errorf("`go env`: no entry for %v", key)
+ }
+ return v, nil
+}
+
+// replaceEnv sets the key environment variable to value in cmd.
+func replaceEnv(cmd *exec.Cmd, key, value string) {
+ if cmd.Env == nil {
+ cmd.Env = os.Environ()
+ }
+ cmd.Env = append(cmd.Env, key+"="+value)
+}
+
+// mustRun executes t and fails cmd with a well-formatted message if it fails.
+func mustRun(t *testing.T, cmd *exec.Cmd) {
+ t.Helper()
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+}
+
+// cc returns a cmd that executes `$(go env CC) $(go env GOGCCFLAGS) $args`.
+func cc(args ...string) (*exec.Cmd, error) {
+ CC, err := goEnv("CC")
+ if err != nil {
+ return nil, err
+ }
+
+ GOGCCFLAGS, err := goEnv("GOGCCFLAGS")
+ if err != nil {
+ return nil, err
+ }
+
+ // Split GOGCCFLAGS, respecting quoting.
+ //
+ // TODO(bcmills): This code also appears in
+ // misc/cgo/testcarchive/carchive_test.go, and perhaps ought to go in
+ // src/cmd/dist/test.go as well. Figure out where to put it so that it can be
+ // shared.
+ var flags []string
+ quote := '\000'
+ start := 0
+ lastSpace := true
+ backslash := false
+ for i, c := range GOGCCFLAGS {
+ if quote == '\000' && unicode.IsSpace(c) {
+ if !lastSpace {
+ flags = append(flags, GOGCCFLAGS[start:i])
+ lastSpace = true
+ }
+ } else {
+ if lastSpace {
+ start = i
+ lastSpace = false
+ }
+ if quote == '\000' && !backslash && (c == '"' || c == '\'') {
+ quote = c
+ backslash = false
+ } else if !backslash && quote == c {
+ quote = '\000'
+ } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
+ backslash = true
+ } else {
+ backslash = false
+ }
+ }
+ }
+ if !lastSpace {
+ flags = append(flags, GOGCCFLAGS[start:])
+ }
+
+ cmd := exec.Command(CC, flags...)
+ cmd.Args = append(cmd.Args, args...)
+ return cmd, nil
+}
+
+type version struct {
+ name string
+ major, minor int
+}
+
+var compiler struct {
+ sync.Once
+ version
+ err error
+}
+
+// compilerVersion detects the version of $(go env CC).
+//
+// It returns a non-nil error if the compiler matches a known version schema but
+// the version could not be parsed, or if $(go env CC) could not be determined.
+func compilerVersion() (version, error) {
+ compiler.Once.Do(func() {
+ compiler.err = func() error {
+ compiler.name = "unknown"
+
+ cmd, err := cc("--version")
+ if err != nil {
+ return err
+ }
+ out, err := cmd.Output()
+ if err != nil {
+ // Compiler does not support "--version" flag: not Clang or GCC.
+ return nil
+ }
+
+ var match [][]byte
+ if bytes.HasPrefix(out, []byte("gcc")) {
+ compiler.name = "gcc"
+
+ cmd, err := cc("-dumpversion")
+ if err != nil {
+ return err
+ }
+ out, err := cmd.Output()
+ if err != nil {
+ // gcc, but does not support gcc's "-dumpversion" flag?!
+ return err
+ }
+ gccRE := regexp.MustCompile(`(\d+)\.(\d+)`)
+ match = gccRE.FindSubmatch(out)
+ } else {
+ clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
+ if match = clangRE.FindSubmatch(out); len(match) > 0 {
+ compiler.name = "clang"
+ }
+ }
+
+ if len(match) < 3 {
+ return nil // "unknown"
+ }
+ if compiler.major, err = strconv.Atoi(string(match[1])); err != nil {
+ return err
+ }
+ if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil {
+ return err
+ }
+ return nil
+ }()
+ })
+ return compiler.version, compiler.err
+}
+
+type compilerCheck struct {
+ once sync.Once
+ err error
+ skip bool // If true, skip with err instead of failing with it.
+}
+
+type config struct {
+ sanitizer string
+
+ cFlags, ldFlags, goFlags []string
+
+ sanitizerCheck, runtimeCheck compilerCheck
+}
+
+var configs struct {
+ sync.Mutex
+ m map[string]*config
+}
+
+// configure returns the configuration for the given sanitizer.
+func configure(sanitizer string) *config {
+ configs.Lock()
+ defer configs.Unlock()
+ if c, ok := configs.m[sanitizer]; ok {
+ return c
+ }
+
+ c := &config{
+ sanitizer: sanitizer,
+ cFlags: []string{"-fsanitize=" + sanitizer},
+ ldFlags: []string{"-fsanitize=" + sanitizer},
+ }
+
+ if testing.Verbose() {
+ c.goFlags = append(c.goFlags, "-x")
+ }
+
+ switch sanitizer {
+ case "memory":
+ c.goFlags = append(c.goFlags, "-msan")
+
+ case "thread":
+ c.goFlags = append(c.goFlags, "--installsuffix=tsan")
+ compiler, _ := compilerVersion()
+ if compiler.name == "gcc" {
+ c.cFlags = append(c.cFlags, "-fPIC")
+ c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan")
+ }
+
+ default:
+ panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer))
+ }
+
+ if configs.m == nil {
+ configs.m = make(map[string]*config)
+ }
+ configs.m[sanitizer] = c
+ return c
+}
+
+// goCmd returns a Cmd that executes "go $subcommand $args" with appropriate
+// additional flags and environment.
+func (c *config) goCmd(subcommand string, args ...string) *exec.Cmd {
+ cmd := exec.Command("go", subcommand)
+ cmd.Args = append(cmd.Args, c.goFlags...)
+ cmd.Args = append(cmd.Args, args...)
+ replaceEnv(cmd, "CGO_CFLAGS", strings.Join(c.cFlags, " "))
+ replaceEnv(cmd, "CGO_LDFLAGS", strings.Join(c.ldFlags, " "))
+ return cmd
+}
+
+// skipIfCSanitizerBroken skips t if the C compiler does not produce working
+// binaries as configured.
+func (c *config) skipIfCSanitizerBroken(t *testing.T) {
+ check := &c.sanitizerCheck
+ check.once.Do(func() {
+ check.skip, check.err = c.checkCSanitizer()
+ })
+ if check.err != nil {
+ t.Helper()
+ if check.skip {
+ t.Skip(check.err)
+ }
+ t.Fatal(check.err)
+ }
+}
+
+var cMain = []byte(`
+int main() {
+ return 0;
+}
+`)
+
+func (c *config) checkCSanitizer() (skip bool, err error) {
+ dir, err := ioutil.TempDir("", c.sanitizer)
+ if err != nil {
+ return false, fmt.Errorf("failed to create temp directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ src := filepath.Join(dir, "return0.c")
+ if err := ioutil.WriteFile(src, cMain, 0600); err != nil {
+ return false, fmt.Errorf("failed to write C source file: %v", err)
+ }
+
+ dst := filepath.Join(dir, "return0")
+ cmd, err := cc(c.cFlags...)
+ if err != nil {
+ return false, err
+ }
+ cmd.Args = append(cmd.Args, c.ldFlags...)
+ cmd.Args = append(cmd.Args, "-o", dst, src)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ if bytes.Contains(out, []byte("-fsanitize")) &&
+ (bytes.Contains(out, []byte("unrecognized")) ||
+ bytes.Contains(out, []byte("unsupported"))) {
+ return true, errors.New(string(out))
+ }
+ return true, fmt.Errorf("%#q failed: %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+
+ if out, err := exec.Command(dst).CombinedOutput(); err != nil {
+ if os.IsNotExist(err) {
+ return true, fmt.Errorf("%#q failed to produce executable: %v", strings.Join(cmd.Args, " "), err)
+ }
+ snippet := bytes.SplitN(out, []byte{'\n'}, 2)[0]
+ return true, fmt.Errorf("%#q generated broken executable: %v\n%s", strings.Join(cmd.Args, " "), err, snippet)
+ }
+
+ return false, nil
+}
+
+// skipIfRuntimeIncompatible skips t if the Go runtime is suspected not to work
+// with cgo as configured.
+func (c *config) skipIfRuntimeIncompatible(t *testing.T) {
+ check := &c.runtimeCheck
+ check.once.Do(func() {
+ check.skip, check.err = c.checkRuntime()
+ })
+ if check.err != nil {
+ t.Helper()
+ if check.skip {
+ t.Skip(check.err)
+ }
+ t.Fatal(check.err)
+ }
+}
+
+func (c *config) checkRuntime() (skip bool, err error) {
+ if c.sanitizer != "thread" {
+ return false, nil
+ }
+
+ // libcgo.h sets CGO_TSAN if it detects TSAN support in the C compiler.
+ // Dump the preprocessor defines to check that that works.
+ // (Sometimes it doesn't: see https://golang.org/issue/15983.)
+ cmd, err := cc(c.cFlags...)
+ if err != nil {
+ return false, err
+ }
+ cmd.Args = append(cmd.Args, "-dM", "-E", "../../../src/runtime/cgo/libcgo.h")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return false, fmt.Errorf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+ if !bytes.Contains(out, []byte("#define CGO_TSAN")) {
+ return true, fmt.Errorf("%#q did not define CGO_TSAN")
+ }
+ return false, nil
+}
+
+// srcPath returns the path to the given file relative to this test's source tree.
+func srcPath(path string) string {
+ return filepath.Join("src", path)
+}
+
+// A tempDir manages a temporary directory within a test.
+type tempDir struct {
+ base string
+}
+
+func (d *tempDir) RemoveAll(t *testing.T) {
+ t.Helper()
+ if d.base == "" {
+ return
+ }
+ if err := os.RemoveAll(d.base); err != nil {
+ t.Fatal("Failed to remove temp dir: %v", err)
+ }
+}
+
+func (d *tempDir) Join(name string) string {
+ return filepath.Join(d.base, name)
+}
+
+func newTempDir(t *testing.T) *tempDir {
+ t.Helper()
+ dir, err := ioutil.TempDir("", filepath.Dir(t.Name()))
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ return &tempDir{base: dir}
+}
+
+// hangProneCmd returns an exec.Cmd for a command that is likely to hang.
+//
+// If one of these tests hangs, the caller is likely to kill the test process
+// using SIGINT, which will be sent to all of the processes in the test's group.
+// Unfortunately, TSAN in particular is prone to dropping signals, so the SIGINT
+// may terminate the test binary but leave the subprocess running. hangProneCmd
+// configures subprocess to receive SIGKILL instead to ensure that it won't
+// leak.
+func hangProneCmd(name string, arg ...string) *exec.Cmd {
+ cmd := exec.Command(name, arg...)
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Pdeathsig: syscall.SIGKILL,
+ }
+ return cmd
+}
diff --git a/misc/cgo/testsanitizers/cshared_test.go b/misc/cgo/testsanitizers/cshared_test.go
new file mode 100644
index 0000000000..56063ea620
--- /dev/null
+++ b/misc/cgo/testsanitizers/cshared_test.go
@@ -0,0 +1,74 @@
+// Copyright 2017 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.
+
+package sanitizers_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+func TestShared(t *testing.T) {
+ t.Parallel()
+ requireOvercommit(t)
+
+ GOOS, err := goEnv("GOOS")
+ if err != nil {
+ t.Fatal(err)
+ }
+ libExt := "so"
+ if GOOS == "darwin" {
+ libExt = "dylib"
+ }
+
+ cases := []struct {
+ src string
+ sanitizer string
+ }{
+ {
+ src: "msan_shared.go",
+ sanitizer: "memory",
+ },
+ {
+ src: "tsan_shared.go",
+ sanitizer: "thread",
+ },
+ }
+
+ for _, tc := range cases {
+ tc := tc
+ name := strings.TrimSuffix(tc.src, ".go")
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ config := configure(tc.sanitizer)
+ config.skipIfCSanitizerBroken(t)
+
+ dir := newTempDir(t)
+ defer dir.RemoveAll(t)
+
+ lib := dir.Join(fmt.Sprintf("lib%s.%s", name, libExt))
+ mustRun(t, config.goCmd("build", "-buildmode=c-shared", "-o", lib, srcPath(tc.src)))
+
+ cSrc := dir.Join("main.c")
+ if err := ioutil.WriteFile(cSrc, cMain, 0600); err != nil {
+ t.Fatalf("failed to write C source file: %v", err)
+ }
+
+ dstBin := dir.Join(name)
+ cmd, err := cc(config.cFlags...)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cmd.Args = append(cmd.Args, config.ldFlags...)
+ cmd.Args = append(cmd.Args, "-o", dstBin, cSrc, lib)
+ mustRun(t, cmd)
+
+ cmd = hangProneCmd(dstBin)
+ replaceEnv(cmd, "LD_LIBRARY_PATH", ".")
+ mustRun(t, cmd)
+ })
+ }
+}
diff --git a/misc/cgo/testsanitizers/msan_test.go b/misc/cgo/testsanitizers/msan_test.go
new file mode 100644
index 0000000000..af5afa9ee4
--- /dev/null
+++ b/misc/cgo/testsanitizers/msan_test.go
@@ -0,0 +1,55 @@
+// Copyright 2017 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.
+
+package sanitizers_test
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestMSAN(t *testing.T) {
+ t.Parallel()
+ requireOvercommit(t)
+ config := configure("memory")
+ config.skipIfCSanitizerBroken(t)
+
+ mustRun(t, config.goCmd("build", "std"))
+
+ cases := []struct {
+ src string
+ wantErr bool
+ }{
+ {src: "msan.go"},
+ {src: "msan2.go"},
+ {src: "msan2_cmsan.go"},
+ {src: "msan3.go"},
+ {src: "msan4.go"},
+ {src: "msan5.go"},
+ {src: "msan_fail.go", wantErr: true},
+ }
+ for _, tc := range cases {
+ tc := tc
+ name := strings.TrimSuffix(tc.src, ".go")
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ dir := newTempDir(t)
+ defer dir.RemoveAll(t)
+
+ outPath := dir.Join(name)
+ mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src)))
+
+ cmd := hangProneCmd(outPath)
+ if tc.wantErr {
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return
+ }
+ t.Fatalf("%#q exited without error; want MSAN failure\n%s", strings.Join(cmd.Args, " "), out)
+ }
+ mustRun(t, cmd)
+ })
+ }
+}
diff --git a/misc/cgo/testsanitizers/msan.go b/misc/cgo/testsanitizers/src/msan.go
index 7915fa84f6..7915fa84f6 100644
--- a/misc/cgo/testsanitizers/msan.go
+++ b/misc/cgo/testsanitizers/src/msan.go
diff --git a/misc/cgo/testsanitizers/msan2.go b/misc/cgo/testsanitizers/src/msan2.go
index 6690cb034f..6690cb034f 100644
--- a/misc/cgo/testsanitizers/msan2.go
+++ b/misc/cgo/testsanitizers/src/msan2.go
diff --git a/misc/cgo/testsanitizers/src/msan2_cmsan.go b/misc/cgo/testsanitizers/src/msan2_cmsan.go
new file mode 100644
index 0000000000..8fdaea90c9
--- /dev/null
+++ b/misc/cgo/testsanitizers/src/msan2_cmsan.go
@@ -0,0 +1,38 @@
+// 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.
+
+package main
+
+/*
+#cgo LDFLAGS: -fsanitize=memory
+#cgo CPPFLAGS: -fsanitize=memory
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+void f(int32_t *p, int n) {
+ int32_t * volatile q = (int32_t *)malloc(sizeof(int32_t) * n);
+ memcpy(p, q, n * sizeof(*p));
+ free(q);
+}
+
+void g(int32_t *p, int n) {
+ if (p[4] != 1) {
+ abort();
+ }
+}
+*/
+import "C"
+
+import (
+ "unsafe"
+)
+
+func main() {
+ a := make([]int32, 10)
+ C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a)))
+ a[4] = 1
+ C.g((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a)))
+}
diff --git a/misc/cgo/testsanitizers/msan3.go b/misc/cgo/testsanitizers/src/msan3.go
index 61a9c29e1a..61a9c29e1a 100644
--- a/misc/cgo/testsanitizers/msan3.go
+++ b/misc/cgo/testsanitizers/src/msan3.go
diff --git a/misc/cgo/testsanitizers/msan4.go b/misc/cgo/testsanitizers/src/msan4.go
index 6c91ff5f09..6c91ff5f09 100644
--- a/misc/cgo/testsanitizers/msan4.go
+++ b/misc/cgo/testsanitizers/src/msan4.go
diff --git a/misc/cgo/testsanitizers/msan5.go b/misc/cgo/testsanitizers/src/msan5.go
index f1479eb8a0..f1479eb8a0 100644
--- a/misc/cgo/testsanitizers/msan5.go
+++ b/misc/cgo/testsanitizers/src/msan5.go
diff --git a/misc/cgo/testsanitizers/msan_fail.go b/misc/cgo/testsanitizers/src/msan_fail.go
index 4c8dab34f6..4c8dab34f6 100644
--- a/misc/cgo/testsanitizers/msan_fail.go
+++ b/misc/cgo/testsanitizers/src/msan_fail.go
diff --git a/misc/cgo/testsanitizers/msan_shared.go b/misc/cgo/testsanitizers/src/msan_shared.go
index 966947cac3..966947cac3 100644
--- a/misc/cgo/testsanitizers/msan_shared.go
+++ b/misc/cgo/testsanitizers/src/msan_shared.go
diff --git a/misc/cgo/testsanitizers/tsan.go b/misc/cgo/testsanitizers/src/tsan.go
index 6c377a701f..6c377a701f 100644
--- a/misc/cgo/testsanitizers/tsan.go
+++ b/misc/cgo/testsanitizers/src/tsan.go
diff --git a/misc/cgo/testsanitizers/tsan10.go b/misc/cgo/testsanitizers/src/tsan10.go
index a40f245553..a40f245553 100644
--- a/misc/cgo/testsanitizers/tsan10.go
+++ b/misc/cgo/testsanitizers/src/tsan10.go
diff --git a/misc/cgo/testsanitizers/tsan11.go b/misc/cgo/testsanitizers/src/tsan11.go
index 70ac9c8ae2..70ac9c8ae2 100644
--- a/misc/cgo/testsanitizers/tsan11.go
+++ b/misc/cgo/testsanitizers/src/tsan11.go
diff --git a/misc/cgo/testsanitizers/tsan12.go b/misc/cgo/testsanitizers/src/tsan12.go
index 3e767eee1f..3e767eee1f 100644
--- a/misc/cgo/testsanitizers/tsan12.go
+++ b/misc/cgo/testsanitizers/src/tsan12.go
diff --git a/misc/cgo/testsanitizers/tsan2.go b/misc/cgo/testsanitizers/src/tsan2.go
index 5018a1987c..5018a1987c 100644
--- a/misc/cgo/testsanitizers/tsan2.go
+++ b/misc/cgo/testsanitizers/src/tsan2.go
diff --git a/misc/cgo/testsanitizers/tsan3.go b/misc/cgo/testsanitizers/src/tsan3.go
index 87f6c80f1b..87f6c80f1b 100644
--- a/misc/cgo/testsanitizers/tsan3.go
+++ b/misc/cgo/testsanitizers/src/tsan3.go
diff --git a/misc/cgo/testsanitizers/tsan4.go b/misc/cgo/testsanitizers/src/tsan4.go
index f0c76d8411..f0c76d8411 100644
--- a/misc/cgo/testsanitizers/tsan4.go
+++ b/misc/cgo/testsanitizers/src/tsan4.go
diff --git a/misc/cgo/testsanitizers/tsan5.go b/misc/cgo/testsanitizers/src/tsan5.go
index 1214a7743b..1214a7743b 100644
--- a/misc/cgo/testsanitizers/tsan5.go
+++ b/misc/cgo/testsanitizers/src/tsan5.go
diff --git a/misc/cgo/testsanitizers/tsan6.go b/misc/cgo/testsanitizers/src/tsan6.go
index c96f08d2f3..c96f08d2f3 100644
--- a/misc/cgo/testsanitizers/tsan6.go
+++ b/misc/cgo/testsanitizers/src/tsan6.go
diff --git a/misc/cgo/testsanitizers/tsan7.go b/misc/cgo/testsanitizers/src/tsan7.go
index 2fb9e45ee2..2fb9e45ee2 100644
--- a/misc/cgo/testsanitizers/tsan7.go
+++ b/misc/cgo/testsanitizers/src/tsan7.go
diff --git a/misc/cgo/testsanitizers/tsan8.go b/misc/cgo/testsanitizers/src/tsan8.go
index 88d82a6078..88d82a6078 100644
--- a/misc/cgo/testsanitizers/tsan8.go
+++ b/misc/cgo/testsanitizers/src/tsan8.go
diff --git a/misc/cgo/testsanitizers/tsan9.go b/misc/cgo/testsanitizers/src/tsan9.go
index f166d8b495..f166d8b495 100644
--- a/misc/cgo/testsanitizers/tsan9.go
+++ b/misc/cgo/testsanitizers/src/tsan9.go
diff --git a/misc/cgo/testsanitizers/tsan_shared.go b/misc/cgo/testsanitizers/src/tsan_shared.go
index 55ff67ecba..55ff67ecba 100644
--- a/misc/cgo/testsanitizers/tsan_shared.go
+++ b/misc/cgo/testsanitizers/src/tsan_shared.go
diff --git a/misc/cgo/testsanitizers/test.bash b/misc/cgo/testsanitizers/test.bash
deleted file mode 100755
index 9f80af6c50..0000000000
--- a/misc/cgo/testsanitizers/test.bash
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/env bash
-# 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.
-
-# This directory is intended to test the use of Go with sanitizers
-# like msan, asan, etc. See https://github.com/google/sanitizers .
-
-set -e
-
-# The sanitizers were originally developed with clang, so prefer it.
-CC=cc
-if test -x "$(type -p clang)"; then
- CC=clang
-fi
-export CC
-
-if [ "$(sysctl -n vm.overcommit_memory)" = 2 ]; then
- echo "skipping msan/tsan tests: vm.overcommit_memory=2" >&2
- exit 0
-fi
-
-msan=yes
-
-TMPDIR=${TMPDIR:-/tmp}
-echo 'int main() { return 0; }' > ${TMPDIR}/testsanitizers$$.c
-if $CC -fsanitize=memory -o ${TMPDIR}/testsanitizers$$ ${TMPDIR}/testsanitizers$$.c 2>&1 | grep "unrecognized" >& /dev/null; then
- echo "skipping msan tests: $CC -fsanitize=memory not supported"
- msan=no
-elif ! test -x ${TMPDIR}/testsanitizers$$; then
- echo "skipping msan tests: $CC -fsanitize-memory did not generate an executable"
- msan=no
-elif ! ${TMPDIR}/testsanitizers$$ >/dev/null 2>&1; then
- echo "skipping msan tests: $CC -fsanitize-memory generates broken executable"
- msan=no
-fi
-rm -f ${TMPDIR}/testsanitizers$$.*
-
-tsan=yes
-
-# The memory and thread sanitizers in versions of clang before 3.6
-# don't work with Go.
-if test "$msan" = "yes" && $CC --version | grep clang >& /dev/null; then
- ver=$($CC --version | sed -e 's/.* version \([0-9.-]*\).*/\1/')
- major=$(echo $ver | sed -e 's/\([0-9]*\).*/\1/')
- minor=$(echo $ver | sed -e 's/[0-9]*\.\([0-9]*\).*/\1/')
- if test "$major" -lt 3 || test "$major" -eq 3 -a "$minor" -lt 6; then
- echo "skipping msan/tsan tests: clang version $major.$minor (older than 3.6)"
- msan=no
- tsan=no
- fi
-
- # Clang before 3.8 does not work with Linux at or after 4.1.
- # golang.org/issue/12898.
- if test "$msan" = "yes" -a "$major" -lt 3 || test "$major" -eq 3 -a "$minor" -lt 8; then
- if test "$(uname)" = Linux; then
- linuxver=$(uname -r)
- linuxmajor=$(echo $linuxver | sed -e 's/\([0-9]*\).*/\1/')
- linuxminor=$(echo $linuxver | sed -e 's/[0-9]*\.\([0-9]*\).*/\1/')
- if test "$linuxmajor" -gt 4 || test "$linuxmajor" -eq 4 -a "$linuxminor" -ge 1; then
- echo "skipping msan/tsan tests: clang version $major.$minor (older than 3.8) incompatible with linux version $linuxmajor.$linuxminor (4.1 or newer)"
- msan=no
- tsan=no
- fi
- fi
- fi
-fi
-
-status=0
-
-testmsanshared() {
- goos=$(go env GOOS)
- suffix="-installsuffix testsanitizers"
- libext="so"
- if [ "$goos" = "darwin" ]; then
- libext="dylib"
- fi
- go build -msan -buildmode=c-shared $suffix -o ${TMPDIR}/libmsanshared.$libext msan_shared.go
-
- echo 'int main() { return 0; }' > ${TMPDIR}/testmsanshared.c
- $CC $(go env GOGCCFLAGS) -fsanitize=memory -o ${TMPDIR}/testmsanshared ${TMPDIR}/testmsanshared.c ${TMPDIR}/libmsanshared.$libext
-
- if ! LD_LIBRARY_PATH=. ${TMPDIR}/testmsanshared; then
- echo "FAIL: msan_shared"
- status=1
- fi
- rm -f ${TMPDIR}/{testmsanshared,testmsanshared.c,libmsanshared.$libext}
-}
-
-if test "$msan" = "yes"; then
- if ! go build -msan std; then
- echo "FAIL: build -msan std"
- status=1
- fi
-
- if ! go run -msan msan.go; then
- echo "FAIL: msan"
- status=1
- fi
-
- if ! CGO_LDFLAGS="-fsanitize=memory" CGO_CPPFLAGS="-fsanitize=memory" go run -msan -a msan2.go; then
- echo "FAIL: msan2 with -fsanitize=memory"
- status=1
- fi
-
- if ! go run -msan -a msan2.go; then
- echo "FAIL: msan2"
- status=1
- fi
-
- if ! go run -msan msan3.go; then
- echo "FAIL: msan3"
- status=1
- fi
-
- if ! go run -msan msan4.go; then
- echo "FAIL: msan4"
- status=1
- fi
-
- if ! go run -msan msan5.go; then
- echo "FAIL: msan5"
- status=1
- fi
-
- if go run -msan msan_fail.go 2>/dev/null; then
- echo "FAIL: msan_fail"
- status=1
- fi
-
- testmsanshared
-fi
-
-testtsanshared() {
- goos=$(go env GOOS)
- suffix="-installsuffix tsan"
- libext="so"
- if [ "$goos" = "darwin" ]; then
- libext="dylib"
- fi
- go build -buildmode=c-shared $suffix -o ${TMPDIR}/libtsanshared.$libext tsan_shared.go
-
- echo 'int main() { return 0; }' > ${TMPDIR}/testtsanshared.c
- $CC $(go env GOGCCFLAGS) -fsanitize=thread -o ${TMPDIR}/testtsanshared ${TMPDIR}/testtsanshared.c ${TMPDIR}/libtsanshared.$libext
-
- if ! LD_LIBRARY_PATH=. ${TMPDIR}/testtsanshared; then
- echo "FAIL: tsan_shared"
- status=1
- fi
- rm -f ${TMPDIR}/{testtsanshared,testtsanshared.c,libtsanshared.$libext}
-}
-
-if test "$tsan" = "yes"; then
- echo 'int main() { return 0; }' > ${TMPDIR}/testsanitizers$$.c
- ok=yes
- if ! $CC -fsanitize=thread ${TMPDIR}/testsanitizers$$.c -o ${TMPDIR}/testsanitizers$$ &> ${TMPDIR}/testsanitizers$$.err; then
- ok=no
- fi
- if grep "unrecognized" ${TMPDIR}/testsanitizers$$.err >& /dev/null; then
- echo "skipping tsan tests: -fsanitize=thread not supported"
- tsan=no
- elif test "$ok" != "yes"; then
- cat ${TMPDIR}/testsanitizers$$.err
- echo "skipping tsan tests: -fsanitizer=thread build failed"
- tsan=no
- elif ! ${TMPDIR}/testsanitizers$$ 2>&1; then
- echo "skipping tsan tests: running tsan program failed"
- tsan=no
- fi
- rm -f ${TMPDIR}/testsanitizers$$*
-fi
-
-# Run a TSAN test.
-# $1 test name
-# $2 environment variables
-# $3 go run args
-testtsan() {
- err=${TMPDIR}/tsanerr$$.out
- if ! env $2 go run $3 $1 2>$err; then
- cat $err
- echo "FAIL: $1"
- status=1
- elif grep -i warning $err >/dev/null 2>&1; then
- cat $err
- echo "FAIL: $1"
- status=1
- fi
- rm -f $err
-}
-
-if test "$tsan" = "yes"; then
- testtsan tsan.go
- testtsan tsan2.go
- testtsan tsan3.go
- testtsan tsan4.go
- testtsan tsan8.go
- testtsan tsan9.go
-
- # These tests are only reliable using clang or GCC version 7 or later.
- # Otherwise runtime/cgo/libcgo.h can't tell whether TSAN is in use.
- ok=false
- clang=false
- if ${CC} --version | grep clang >/dev/null 2>&1; then
- ok=true
- clang=true
- else
- ver=$($CC -dumpversion)
- major=$(echo $ver | sed -e 's/\([0-9]*\).*/\1/')
- if test "$major" -lt 7; then
- echo "skipping remaining TSAN tests: GCC version $major (older than 7)"
- else
- ok=true
- fi
- fi
-
- if test "$ok" = "true"; then
- # These tests require rebuilding os/user with -fsanitize=thread.
- testtsan tsan5.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
- testtsan tsan6.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
- testtsan tsan7.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
-
- # The remaining tests reportedly hang when built with GCC; issue #21196.
- if test "$clang" = "true"; then
- testtsan tsan10.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
- testtsan tsan11.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
- testtsan tsan12.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
- fi
-
- testtsanshared
- fi
-fi
-
-exit $status
diff --git a/misc/cgo/testsanitizers/tsan_test.go b/misc/cgo/testsanitizers/tsan_test.go
new file mode 100644
index 0000000000..ec4e0033fb
--- /dev/null
+++ b/misc/cgo/testsanitizers/tsan_test.go
@@ -0,0 +1,56 @@
+// Copyright 2017 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.
+
+package sanitizers_test
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestTSAN(t *testing.T) {
+ t.Parallel()
+ requireOvercommit(t)
+ config := configure("thread")
+ config.skipIfCSanitizerBroken(t)
+
+ mustRun(t, config.goCmd("build", "std"))
+
+ cases := []struct {
+ src string
+ needsRuntime bool
+ }{
+ {src: "tsan.go"},
+ {src: "tsan2.go"},
+ {src: "tsan3.go"},
+ {src: "tsan4.go"},
+ {src: "tsan5.go", needsRuntime: true},
+ {src: "tsan6.go", needsRuntime: true},
+ {src: "tsan7.go", needsRuntime: true},
+ {src: "tsan8.go"},
+ {src: "tsan9.go"},
+ {src: "tsan10.go", needsRuntime: true},
+ {src: "tsan11.go", needsRuntime: true},
+ {src: "tsan12.go", needsRuntime: true},
+ }
+ for _, tc := range cases {
+ tc := tc
+ name := strings.TrimSuffix(tc.src, ".go")
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ dir := newTempDir(t)
+ defer dir.RemoveAll(t)
+
+ outPath := dir.Join(name)
+ mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src)))
+
+ cmd := hangProneCmd(outPath)
+ if tc.needsRuntime {
+ config.skipIfRuntimeIncompatible(t)
+ }
+ mustRun(t, cmd)
+ })
+ }
+}
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index 6052904cbf..79338b3721 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -620,7 +620,7 @@ func (t *tester) registerTests() {
t.registerTest("testasan", "../misc/cgo/testasan", "go", "run", "main.go")
}
if t.goos == "linux" && t.goarch == "amd64" {
- t.registerTest("testsanitizers", "../misc/cgo/testsanitizers", "./test.bash")
+ t.registerHostTest("testsanitizers/msan", "../misc/cgo/testsanitizers", "misc/cgo/testsanitizers", ".")
}
if t.hasBash() && t.goos != "android" && !t.iOS() && t.gohostos != "windows" {
t.registerTest("cgo_errors", "../misc/cgo/errors", "./test.bash")