summaryrefslogtreecommitdiffstats
path: root/misc/cgo/testsanitizers
diff options
context:
space:
mode:
Diffstat (limited to 'misc/cgo/testsanitizers')
-rw-r--r--misc/cgo/testsanitizers/asan_test.go81
-rw-r--r--misc/cgo/testsanitizers/cc_test.go485
-rw-r--r--misc/cgo/testsanitizers/cshared_test.go85
-rw-r--r--misc/cgo/testsanitizers/msan_test.go71
-rw-r--r--misc/cgo/testsanitizers/testdata/asan1_fail.go28
-rw-r--r--misc/cgo/testsanitizers/testdata/asan2_fail.go34
-rw-r--r--misc/cgo/testsanitizers/testdata/asan3_fail.go23
-rw-r--r--misc/cgo/testsanitizers/testdata/asan4_fail.go22
-rw-r--r--misc/cgo/testsanitizers/testdata/asan5_fail.go21
-rw-r--r--misc/cgo/testsanitizers/testdata/asan_useAfterReturn.go26
-rw-r--r--misc/cgo/testsanitizers/testdata/msan.go35
-rw-r--r--misc/cgo/testsanitizers/testdata/msan2.go35
-rw-r--r--misc/cgo/testsanitizers/testdata/msan2_cmsan.go38
-rw-r--r--misc/cgo/testsanitizers/testdata/msan3.go33
-rw-r--r--misc/cgo/testsanitizers/testdata/msan4.go50
-rw-r--r--misc/cgo/testsanitizers/testdata/msan5.go57
-rw-r--r--misc/cgo/testsanitizers/testdata/msan6.go72
-rw-r--r--misc/cgo/testsanitizers/testdata/msan7.go38
-rw-r--r--misc/cgo/testsanitizers/testdata/msan8.go109
-rw-r--r--misc/cgo/testsanitizers/testdata/msan_fail.go36
-rw-r--r--misc/cgo/testsanitizers/testdata/msan_shared.go12
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan.go44
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan10.go31
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan11.go55
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan12.go35
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan2.go55
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan3.go40
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan4.go34
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan5.go51
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan6.go49
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan7.go40
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan8.go60
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan9.go67
-rw-r--r--misc/cgo/testsanitizers/testdata/tsan_shared.go63
-rw-r--r--misc/cgo/testsanitizers/tsan_test.go56
35 files changed, 2071 insertions, 0 deletions
diff --git a/misc/cgo/testsanitizers/asan_test.go b/misc/cgo/testsanitizers/asan_test.go
new file mode 100644
index 0000000..22dcf23
--- /dev/null
+++ b/misc/cgo/testsanitizers/asan_test.go
@@ -0,0 +1,81 @@
+// Copyright 2021 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 TestASAN(t *testing.T) {
+ goos, err := goEnv("GOOS")
+ if err != nil {
+ t.Fatal(err)
+ }
+ goarch, err := goEnv("GOARCH")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // The asan tests require support for the -asan option.
+ if !aSanSupported(goos, goarch) {
+ t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch)
+ }
+
+ t.Parallel()
+ requireOvercommit(t)
+ config := configure("address")
+ config.skipIfCSanitizerBroken(t)
+
+ mustRun(t, config.goCmd("build", "std"))
+
+ cases := []struct {
+ src string
+ memoryAccessError string
+ errorLocation string
+ }{
+ {src: "asan1_fail.go", memoryAccessError: "heap-use-after-free", errorLocation: "asan1_fail.go:25"},
+ {src: "asan2_fail.go", memoryAccessError: "heap-buffer-overflow", errorLocation: "asan2_fail.go:31"},
+ {src: "asan3_fail.go", memoryAccessError: "use-after-poison", errorLocation: "asan3_fail.go:13"},
+ {src: "asan4_fail.go", memoryAccessError: "use-after-poison", errorLocation: "asan4_fail.go:13"},
+ {src: "asan5_fail.go", memoryAccessError: "use-after-poison", errorLocation: "asan5_fail.go:18"},
+ {src: "asan_useAfterReturn.go"},
+ }
+ 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.memoryAccessError != "" {
+ outb, err := cmd.CombinedOutput()
+ out := string(outb)
+ if err != nil && strings.Contains(out, tc.memoryAccessError) {
+ // This string is output if the
+ // sanitizer library needs a
+ // symbolizer program and can't find it.
+ const noSymbolizer = "external symbolizer"
+ // Check if -asan option can correctly print where the error occurred.
+ if tc.errorLocation != "" &&
+ !strings.Contains(out, tc.errorLocation) &&
+ !strings.Contains(out, noSymbolizer) &&
+ compilerSupportsLocation() {
+
+ t.Errorf("%#q exited without expected location of the error\n%s; got failure\n%s", strings.Join(cmd.Args, " "), tc.errorLocation, out)
+ }
+ return
+ }
+ t.Fatalf("%#q exited without expected memory access error\n%s; got failure\n%s", strings.Join(cmd.Args, " "), tc.memoryAccessError, out)
+ }
+ mustRun(t, cmd)
+ })
+ }
+}
diff --git a/misc/cgo/testsanitizers/cc_test.go b/misc/cgo/testsanitizers/cc_test.go
new file mode 100644
index 0000000..05b7793
--- /dev/null
+++ b/misc/cgo/testsanitizers/cc_test.go
@@ -0,0 +1,485 @@
+// 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"
+ "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 = os.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
+}
+
+// compilerSupportsLocation reports whether the compiler should be
+// able to provide file/line information in backtraces.
+func compilerSupportsLocation() bool {
+ compiler, err := compilerVersion()
+ if err != nil {
+ return false
+ }
+ switch compiler.name {
+ case "gcc":
+ return compiler.major >= 10
+ case "clang":
+ return true
+ default:
+ return false
+ }
+}
+
+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")
+ }
+
+ case "address":
+ c.goFlags = append(c.goFlags, "-asan")
+ // Set the debug mode to print the C stack trace.
+ c.cFlags = append(c.cFlags, "-g")
+
+ 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 := os.MkdirTemp("", 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 := os.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.Cut(out, []byte("\n"))
+ 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 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")
+ cmdStr := strings.Join(cmd.Args, " ")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return false, fmt.Errorf("%#q exited with %v\n%s", cmdStr, err, out)
+ }
+ if !bytes.Contains(out, []byte("#define CGO_TSAN")) {
+ return true, fmt.Errorf("%#q did not define CGO_TSAN", cmdStr)
+ }
+ 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("testdata", 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.Fatalf("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 := os.MkdirTemp("", 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
+}
+
+// mSanSupported is a copy of the function cmd/internal/sys.MSanSupported,
+// because the internal pacakage can't be used here.
+func mSanSupported(goos, goarch string) bool {
+ switch goos {
+ case "linux":
+ return goarch == "amd64" || goarch == "arm64"
+ default:
+ return false
+ }
+}
+
+// aSanSupported is a copy of the function cmd/internal/sys.ASanSupported,
+// because the internal pacakage can't be used here.
+func aSanSupported(goos, goarch string) bool {
+ switch goos {
+ case "linux":
+ return goarch == "amd64" || goarch == "arm64"
+ default:
+ return false
+ }
+}
diff --git a/misc/cgo/testsanitizers/cshared_test.go b/misc/cgo/testsanitizers/cshared_test.go
new file mode 100644
index 0000000..8fd0371
--- /dev/null
+++ b/misc/cgo/testsanitizers/cshared_test.go
@@ -0,0 +1,85 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestShared(t *testing.T) {
+ t.Parallel()
+ requireOvercommit(t)
+
+ GOOS, err := goEnv("GOOS")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ GOARCH, err := goEnv("GOARCH")
+ 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")
+ //The memory sanitizer tests require support for the -msan option.
+ if tc.sanitizer == "memory" && !mSanSupported(GOOS, GOARCH) {
+ t.Logf("skipping %s test on %s/%s; -msan option is not supported.", name, GOOS, GOARCH)
+ continue
+ }
+ 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 := os.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 0000000..5ee9947
--- /dev/null
+++ b/misc/cgo/testsanitizers/msan_test.go
@@ -0,0 +1,71 @@
+// 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) {
+ goos, err := goEnv("GOOS")
+ if err != nil {
+ t.Fatal(err)
+ }
+ goarch, err := goEnv("GOARCH")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // The msan tests require support for the -msan option.
+ if !mSanSupported(goos, goarch) {
+ t.Skipf("skipping on %s/%s; -msan option is not supported.", goos, goarch)
+ }
+
+ 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: "msan6.go"},
+ {src: "msan7.go"},
+ {src: "msan8.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/testdata/asan1_fail.go b/misc/cgo/testsanitizers/testdata/asan1_fail.go
new file mode 100644
index 0000000..80289e5
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan1_fail.go
@@ -0,0 +1,28 @@
+// Copyright 2021 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
+
+/*
+#include <stdlib.h>
+#include <stdio.h>
+
+int *p;
+int* test() {
+ p = (int *)malloc(2 * sizeof(int));
+ free(p);
+ return p;
+}
+*/
+import "C"
+import "fmt"
+
+func main() {
+ // C passes Go an invalid pointer.
+ a := C.test()
+ // Use after free
+ *a = 2 // BOOM
+ // We shouldn't get here; asan should stop us first.
+ fmt.Println(*a)
+}
diff --git a/misc/cgo/testsanitizers/testdata/asan2_fail.go b/misc/cgo/testsanitizers/testdata/asan2_fail.go
new file mode 100644
index 0000000..3ab0608
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan2_fail.go
@@ -0,0 +1,34 @@
+// Copyright 2021 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
+
+/*
+#include <stdlib.h>
+#include <stdio.h>
+
+int *p;
+int* f() {
+ int i;
+ p = (int *)malloc(5*sizeof(int));
+ for (i = 0; i < 5; i++) {
+ p[i] = i+10;
+ }
+ return p;
+}
+*/
+import "C"
+import (
+ "fmt"
+ "unsafe"
+)
+
+func main() {
+ a := C.f()
+ q5 := (*C.int)(unsafe.Add(unsafe.Pointer(a), 4*5))
+ // Access to C pointer out of bounds.
+ *q5 = 100 // BOOM
+ // We shouldn't get here; asan should stop us first.
+ fmt.Printf("q5: %d, %x\n", *q5, q5)
+}
diff --git a/misc/cgo/testsanitizers/testdata/asan3_fail.go b/misc/cgo/testsanitizers/testdata/asan3_fail.go
new file mode 100644
index 0000000..9f6d26d
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan3_fail.go
@@ -0,0 +1,23 @@
+// Copyright 2021 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
+
+/*
+#include <stdlib.h>
+#include <stdio.h>
+
+void test(int *a) {
+ // Access Go pointer out of bounds.
+ int c = a[5]; // BOOM
+ // We shouldn't get here; asan should stop us first.
+ printf("a[5]=%d\n", c);
+}
+*/
+import "C"
+
+func main() {
+ cIntSlice := []C.int{200, 201, 203, 203, 204}
+ C.test(&cIntSlice[0])
+}
diff --git a/misc/cgo/testsanitizers/testdata/asan4_fail.go b/misc/cgo/testsanitizers/testdata/asan4_fail.go
new file mode 100644
index 0000000..1209845
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan4_fail.go
@@ -0,0 +1,22 @@
+// Copyright 2021 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
+
+/*
+#include <stdlib.h>
+#include <stdio.h>
+
+void test(int* a) {
+ // Access Go pointer out of bounds.
+ a[3] = 300; // BOOM
+ // We shouldn't get here; asan should stop us first.
+ printf("a[3]=%d\n", a[3]);
+}*/
+import "C"
+
+func main() {
+ var cIntArray [2]C.int
+ C.test(&cIntArray[0]) // cIntArray is moved to heap.
+}
diff --git a/misc/cgo/testsanitizers/testdata/asan5_fail.go b/misc/cgo/testsanitizers/testdata/asan5_fail.go
new file mode 100644
index 0000000..d6853ea
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan5_fail.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "unsafe"
+)
+
+func main() {
+ p := new([1024 * 1000]int)
+ p[0] = 10
+ r := bar(&p[1024*1000-1])
+ fmt.Printf("r value is %d", r)
+}
+
+func bar(a *int) int {
+ p := unsafe.Add(unsafe.Pointer(a), 2*unsafe.Sizeof(int(1)))
+ runtime.ASanWrite(p, 8) // BOOM
+ *((*int)(p)) = 10
+ return *((*int)(p))
+}
diff --git a/misc/cgo/testsanitizers/testdata/asan_useAfterReturn.go b/misc/cgo/testsanitizers/testdata/asan_useAfterReturn.go
new file mode 100644
index 0000000..3d3d5a6
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/asan_useAfterReturn.go
@@ -0,0 +1,26 @@
+// Copyright 2021 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
+
+// The -fsanitize=address option of C compier can detect stack-use-after-return bugs.
+// In the following program, the local variable 'local' was moved to heap by the Go
+// compiler because foo() is returning the reference to 'local', and return stack of
+// foo() will be invalid. Thus for main() to use the reference to 'local', the 'local'
+// must be available even after foo() has finished. Therefore, Go has no such issue.
+
+import "fmt"
+
+var ptr *int
+
+func main() {
+ foo()
+ fmt.Printf("ptr=%x, %v", *ptr, ptr)
+}
+
+func foo() {
+ var local int
+ local = 1
+ ptr = &local // local is moved to heap.
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan.go b/misc/cgo/testsanitizers/testdata/msan.go
new file mode 100644
index 0000000..7915fa8
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan.go
@@ -0,0 +1,35 @@
+// 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
+
+/*
+#include <stdint.h>
+
+void f(int32_t *p, int n) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ p[i] = (int32_t)i;
+ }
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "unsafe"
+)
+
+func main() {
+ a := make([]int32, 10)
+ C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a)))
+ for i, v := range a {
+ if i != int(v) {
+ fmt.Println("bad %d: %v\n", i, a)
+ os.Exit(1)
+ }
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan2.go b/misc/cgo/testsanitizers/testdata/msan2.go
new file mode 100644
index 0000000..6690cb0
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan2.go
@@ -0,0 +1,35 @@
+// 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
+
+/*
+#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/testdata/msan2_cmsan.go b/misc/cgo/testsanitizers/testdata/msan2_cmsan.go
new file mode 100644
index 0000000..8fdaea9
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/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/testdata/msan3.go b/misc/cgo/testsanitizers/testdata/msan3.go
new file mode 100644
index 0000000..61a9c29
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan3.go
@@ -0,0 +1,33 @@
+// 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
+
+/*
+extern int *GoFn(int *);
+
+// Yes, you can have definitions if you use //export, as long as they are weak.
+int f(void) __attribute__ ((weak));
+
+int f() {
+ int i;
+ int *p = GoFn(&i);
+ if (*p != 12345)
+ return 0;
+ return 1;
+}
+*/
+import "C"
+
+//export GoFn
+func GoFn(p *C.int) *C.int {
+ *p = C.int(12345)
+ return p
+}
+
+func main() {
+ if r := C.f(); r != 1 {
+ panic(r)
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan4.go b/misc/cgo/testsanitizers/testdata/msan4.go
new file mode 100644
index 0000000..6c91ff5
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan4.go
@@ -0,0 +1,50 @@
+// 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
+
+// The memory profiler can call copy from a slice on the system stack,
+// which msan used to think meant a reference to uninitialized memory.
+
+/*
+#include <time.h>
+#include <unistd.h>
+
+extern void Nop(char*);
+
+// Use weak as a hack to permit defining a function even though we use export.
+void poison() __attribute__ ((weak));
+
+// Poison the stack.
+void poison() {
+ char a[1024];
+ Nop(&a[0]);
+}
+
+*/
+import "C"
+
+import (
+ "runtime"
+)
+
+func main() {
+ runtime.MemProfileRate = 1
+ start(100)
+}
+
+func start(i int) {
+ if i == 0 {
+ return
+ }
+ C.poison()
+ // Tie up a thread.
+ // We won't actually wait for this sleep to complete.
+ go func() { C.sleep(1) }()
+ start(i - 1)
+}
+
+//export Nop
+func Nop(*C.char) {
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan5.go b/misc/cgo/testsanitizers/testdata/msan5.go
new file mode 100644
index 0000000..f1479eb
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan5.go
@@ -0,0 +1,57 @@
+// Copyright 2016 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
+
+// Using reflect to set a value was not seen by msan.
+
+/*
+#include <stdlib.h>
+
+extern void Go1(int*);
+extern void Go2(char*);
+
+// Use weak as a hack to permit defining a function even though we use export.
+void C1() __attribute__ ((weak));
+void C2() __attribute__ ((weak));
+
+void C1() {
+ int i;
+ Go1(&i);
+ if (i != 42) {
+ abort();
+ }
+}
+
+void C2() {
+ char a[2];
+ a[1] = 42;
+ Go2(a);
+ if (a[0] != 42) {
+ abort();
+ }
+}
+*/
+import "C"
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+//export Go1
+func Go1(p *C.int) {
+ reflect.ValueOf(p).Elem().Set(reflect.ValueOf(C.int(42)))
+}
+
+//export Go2
+func Go2(p *C.char) {
+ a := (*[2]byte)(unsafe.Pointer(p))
+ reflect.Copy(reflect.ValueOf(a[:1]), reflect.ValueOf(a[1:]))
+}
+
+func main() {
+ C.C1()
+ C.C2()
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan6.go b/misc/cgo/testsanitizers/testdata/msan6.go
new file mode 100644
index 0000000..003989c
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan6.go
@@ -0,0 +1,72 @@
+// Copyright 2018 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
+
+// A C function returning a value on the Go stack could leave the Go
+// stack marked as uninitialized, potentially causing a later error
+// when the stack is used for something else. Issue 26209.
+
+/*
+#cgo LDFLAGS: -fsanitize=memory
+#cgo CPPFLAGS: -fsanitize=memory
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ uintptr_t a[20];
+} S;
+
+S f() {
+ S *p;
+
+ p = (S *)(malloc(sizeof(S)));
+ p->a[0] = 0;
+ return *p;
+}
+*/
+import "C"
+
+// allocateStack extends the stack so that stack copying doesn't
+// confuse the msan data structures.
+//go:noinline
+func allocateStack(i int) int {
+ if i == 0 {
+ return i
+ }
+ return allocateStack(i - 1)
+}
+
+// F1 marks a chunk of stack as uninitialized.
+// C.f returns an uninitialized struct on the stack, so msan will mark
+// the stack as uninitialized.
+//go:noinline
+func F1() uintptr {
+ s := C.f()
+ return uintptr(s.a[0])
+}
+
+// F2 allocates a struct on the stack and converts it to an empty interface,
+// which will call msanread and see that the data appears uninitialized.
+//go:noinline
+func F2() interface{} {
+ return C.S{}
+}
+
+func poisonStack(i int) int {
+ if i == 0 {
+ return int(F1())
+ }
+ F1()
+ r := poisonStack(i - 1)
+ F2()
+ return r
+}
+
+func main() {
+ allocateStack(16384)
+ poisonStack(128)
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan7.go b/misc/cgo/testsanitizers/testdata/msan7.go
new file mode 100644
index 0000000..2f29fd2
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan7.go
@@ -0,0 +1,38 @@
+// Copyright 2020 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
+
+// Test passing C struct to exported Go function.
+
+/*
+#include <stdint.h>
+#include <stdlib.h>
+
+// T is a C struct with alignment padding after b.
+// The padding bytes are not considered initialized by MSAN.
+// It is big enough to be passed on stack in C ABI (and least
+// on AMD64).
+typedef struct { char b; uintptr_t x, y; } T;
+
+extern void F(T);
+
+// Use weak as a hack to permit defining a function even though we use export.
+void CF(int x) __attribute__ ((weak));
+void CF(int x) {
+ T *t = malloc(sizeof(T));
+ t->b = (char)x;
+ t->x = x;
+ t->y = x;
+ F(*t);
+}
+*/
+import "C"
+
+//export F
+func F(t C.T) { println(t.b, t.x, t.y) }
+
+func main() {
+ C.CF(C.int(0))
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan8.go b/misc/cgo/testsanitizers/testdata/msan8.go
new file mode 100644
index 0000000..1cb5c56
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan8.go
@@ -0,0 +1,109 @@
+// Copyright 2021 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
+
+/*
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+
+#include <sanitizer/msan_interface.h>
+
+// cgoTracebackArg is the type of the argument passed to msanGoTraceback.
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+// msanGoTraceback is registered as the cgo traceback function.
+// This will be called when a signal occurs.
+void msanGoTraceback(void* parg) {
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ arg->buf[0] = 0;
+}
+
+// msanGoWait will be called with all registers undefined as far as
+// msan is concerned. It just waits for a signal.
+// Because the registers are msan-undefined, the signal handler will
+// be invoked with all registers msan-undefined.
+__attribute__((noinline))
+void msanGoWait(unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6) {
+ sigset_t mask;
+
+ sigemptyset(&mask);
+ sigsuspend(&mask);
+}
+
+// msanGoSignalThread is the thread ID of the msanGoLoop thread.
+static pthread_t msanGoSignalThread;
+
+// msanGoSignalThreadSet is used to record that msanGoSignalThread
+// has been initialized. This is accessed atomically.
+static int32_t msanGoSignalThreadSet;
+
+// uninit is explicitly poisoned, so that we can make all registers
+// undefined by calling msanGoWait.
+static unsigned long uninit;
+
+// msanGoLoop loops calling msanGoWait, with the arguments passed
+// such that msan thinks that they are undefined. msan permits
+// undefined values to be used as long as they are not used to
+// for conditionals or for memory access.
+void msanGoLoop() {
+ int i;
+
+ msanGoSignalThread = pthread_self();
+ __atomic_store_n(&msanGoSignalThreadSet, 1, __ATOMIC_SEQ_CST);
+
+ // Force uninit to be undefined for msan.
+ __msan_poison(&uninit, sizeof uninit);
+ for (i = 0; i < 100; i++) {
+ msanGoWait(uninit, uninit, uninit, uninit, uninit, uninit);
+ }
+}
+
+// msanGoReady returns whether msanGoSignalThread is set.
+int msanGoReady() {
+ return __atomic_load_n(&msanGoSignalThreadSet, __ATOMIC_SEQ_CST) != 0;
+}
+
+// msanGoSendSignal sends a signal to the msanGoLoop thread.
+void msanGoSendSignal() {
+ pthread_kill(msanGoSignalThread, SIGWINCH);
+}
+*/
+import "C"
+
+import (
+ "runtime"
+ "time"
+)
+
+func main() {
+ runtime.SetCgoTraceback(0, C.msanGoTraceback, nil, nil)
+
+ c := make(chan bool)
+ go func() {
+ defer func() { c <- true }()
+ C.msanGoLoop()
+ }()
+
+ for C.msanGoReady() == 0 {
+ time.Sleep(time.Microsecond)
+ }
+
+loop:
+ for {
+ select {
+ case <-c:
+ break loop
+ default:
+ C.msanGoSendSignal()
+ time.Sleep(time.Microsecond)
+ }
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan_fail.go b/misc/cgo/testsanitizers/testdata/msan_fail.go
new file mode 100644
index 0000000..4c8dab3
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan_fail.go
@@ -0,0 +1,36 @@
+// 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
+
+/*
+#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) {
+ // We shouldn't get here; msan should stop us first.
+ exit(0);
+ }
+}
+*/
+import "C"
+
+import (
+ "unsafe"
+)
+
+func main() {
+ a := make([]int32, 10)
+ C.f((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a)))
+ a[3] = 1
+ C.g((*C.int32_t)(unsafe.Pointer(&a[0])), C.int(len(a)))
+}
diff --git a/misc/cgo/testsanitizers/testdata/msan_shared.go b/misc/cgo/testsanitizers/testdata/msan_shared.go
new file mode 100644
index 0000000..966947c
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/msan_shared.go
@@ -0,0 +1,12 @@
+// 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.
+
+// This program segfaulted during libpreinit when built with -msan:
+// http://golang.org/issue/18707
+
+package main
+
+import "C"
+
+func main() {}
diff --git a/misc/cgo/testsanitizers/testdata/tsan.go b/misc/cgo/testsanitizers/testdata/tsan.go
new file mode 100644
index 0000000..6c377a7
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan.go
@@ -0,0 +1,44 @@
+// 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
+
+// This program produced false race reports when run under the C/C++
+// ThreadSanitizer, as it did not understand the synchronization in
+// the Go code.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+int val;
+
+int getVal() {
+ return val;
+}
+
+void setVal(int i) {
+ val = i;
+}
+*/
+import "C"
+
+import (
+ "runtime"
+)
+
+func main() {
+ runtime.LockOSThread()
+ C.setVal(1)
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+ C.setVal(2)
+ c <- true
+ }()
+ <-c
+ if v := C.getVal(); v != 2 {
+ panic(v)
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan10.go b/misc/cgo/testsanitizers/testdata/tsan10.go
new file mode 100644
index 0000000..a40f245
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan10.go
@@ -0,0 +1,31 @@
+// 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 main
+
+// This program hung when run under the C/C++ ThreadSanitizer.
+// TSAN defers asynchronous signals until the signaled thread calls into libc.
+// Since the Go runtime makes direct futex syscalls, Go runtime threads could
+// run for an arbitrarily long time without triggering the libc interceptors.
+// See https://golang.org/issue/18717.
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+/*
+#cgo CFLAGS: -g -fsanitize=thread
+#cgo LDFLAGS: -g -fsanitize=thread
+*/
+import "C"
+
+func main() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGUSR1)
+ defer signal.Stop(c)
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ <-c
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan11.go b/misc/cgo/testsanitizers/testdata/tsan11.go
new file mode 100644
index 0000000..189e10f
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan11.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 main
+
+// This program hung when run under the C/C++ ThreadSanitizer. TSAN defers
+// asynchronous signals until the signaled thread calls into libc. The runtime's
+// sysmon goroutine idles itself using direct usleep syscalls, so it could
+// run for an arbitrarily long time without triggering the libc interceptors.
+// See https://golang.org/issue/18717.
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+/*
+#cgo CFLAGS: -g -fsanitize=thread
+#cgo LDFLAGS: -g -fsanitize=thread
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void raise_usr2(int signo) {
+ raise(SIGUSR2);
+}
+
+static void register_handler(int signo) {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_ONSTACK;
+ sa.sa_handler = raise_usr2;
+
+ if (sigaction(SIGUSR1, &sa, NULL) != 0) {
+ perror("failed to register SIGUSR1 handler");
+ exit(EXIT_FAILURE);
+ }
+}
+*/
+import "C"
+
+func main() {
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, syscall.SIGUSR2)
+
+ C.register_handler(C.int(syscall.SIGUSR1))
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+
+ <-ch
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan12.go b/misc/cgo/testsanitizers/testdata/tsan12.go
new file mode 100644
index 0000000..0ef545d
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan12.go
@@ -0,0 +1,35 @@
+// 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 main
+
+// This program hung when run under the C/C++ ThreadSanitizer. TSAN installs a
+// libc interceptor that writes signal handlers to a global variable within the
+// TSAN runtime instead of making a sigaction system call. A bug in
+// syscall.runtime_AfterForkInChild corrupted TSAN's signal forwarding table
+// during calls to (*os/exec.Cmd).Run, causing the parent process to fail to
+// invoke signal handlers.
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "os/signal"
+ "syscall"
+)
+
+import "C"
+
+func main() {
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, syscall.SIGUSR1)
+
+ if err := exec.Command("true").Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Unexpected error from `true`: %v", err)
+ os.Exit(1)
+ }
+
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ <-ch
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan2.go b/misc/cgo/testsanitizers/testdata/tsan2.go
new file mode 100644
index 0000000..5018a19
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan2.go
@@ -0,0 +1,55 @@
+// 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
+
+// This program produced false race reports when run under the C/C++
+// ThreadSanitizer, as it did not understand the synchronization in
+// the Go code.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+extern void GoRun(void);
+
+// Yes, you can have definitions if you use //export, as long as they are weak.
+
+int val __attribute__ ((weak));
+
+int run(void) __attribute__ ((weak));
+
+int run() {
+ val = 1;
+ GoRun();
+ return val;
+}
+
+void setVal(int) __attribute__ ((weak));
+
+void setVal(int i) {
+ val = i;
+}
+*/
+import "C"
+
+import "runtime"
+
+//export GoRun
+func GoRun() {
+ runtime.LockOSThread()
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+ C.setVal(2)
+ c <- true
+ }()
+ <-c
+}
+
+func main() {
+ if v := C.run(); v != 2 {
+ panic(v)
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan3.go b/misc/cgo/testsanitizers/testdata/tsan3.go
new file mode 100644
index 0000000..87f6c80
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan3.go
@@ -0,0 +1,40 @@
+// Copyright 2016 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
+
+// The stubs for the C functions read and write the same slot on the
+// g0 stack when copying arguments in and out.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+int Func1() {
+ return 0;
+}
+
+void Func2(int x) {
+ (void)x;
+}
+*/
+import "C"
+
+func main() {
+ const N = 10000
+ done := make(chan bool, N)
+ for i := 0; i < N; i++ {
+ go func() {
+ C.Func1()
+ done <- true
+ }()
+ go func() {
+ C.Func2(0)
+ done <- true
+ }()
+ }
+ for i := 0; i < 2*N; i++ {
+ <-done
+ }
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan4.go b/misc/cgo/testsanitizers/testdata/tsan4.go
new file mode 100644
index 0000000..f0c76d8
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan4.go
@@ -0,0 +1,34 @@
+// Copyright 2016 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
+
+// Check that calls to C.malloc/C.free do not trigger TSAN false
+// positive reports.
+
+// #cgo CFLAGS: -fsanitize=thread
+// #cgo LDFLAGS: -fsanitize=thread
+// #include <stdlib.h>
+import "C"
+
+import (
+ "runtime"
+ "sync"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 100; i++ {
+ p := C.malloc(C.size_t(i * 10))
+ runtime.Gosched()
+ C.free(p)
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan5.go b/misc/cgo/testsanitizers/testdata/tsan5.go
new file mode 100644
index 0000000..1214a77
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan5.go
@@ -0,0 +1,51 @@
+// Copyright 2016 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
+
+// Check that calls to C.malloc/C.free do not collide with the calls
+// made by the os/user package.
+
+// #cgo CFLAGS: -fsanitize=thread
+// #cgo LDFLAGS: -fsanitize=thread
+// #include <stdlib.h>
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "runtime"
+ "sync"
+)
+
+func main() {
+ u, err := user.Current()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ // Let the test pass.
+ os.Exit(0)
+ }
+
+ var wg sync.WaitGroup
+ for i := 0; i < 20; i++ {
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 1000; i++ {
+ user.Lookup(u.Username)
+ runtime.Gosched()
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 1000; i++ {
+ p := C.malloc(C.size_t(len(u.Username) + 1))
+ runtime.Gosched()
+ C.free(p)
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan6.go b/misc/cgo/testsanitizers/testdata/tsan6.go
new file mode 100644
index 0000000..c96f08d
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan6.go
@@ -0,0 +1,49 @@
+// Copyright 2016 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
+
+// Check that writes to Go allocated memory, with Go synchronization,
+// do not look like a race.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+void f(char *p) {
+ *p = 1;
+}
+*/
+import "C"
+
+import (
+ "runtime"
+ "sync"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ c := make(chan []C.char, 100)
+ for i := 0; i < 10; i++ {
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 100; i++ {
+ c <- make([]C.char, 4096)
+ runtime.Gosched()
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 100; i++ {
+ p := &(<-c)[0]
+ mu.Lock()
+ C.f(p)
+ mu.Unlock()
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan7.go b/misc/cgo/testsanitizers/testdata/tsan7.go
new file mode 100644
index 0000000..2fb9e45
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan7.go
@@ -0,0 +1,40 @@
+// Copyright 2016 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
+
+// Setting an environment variable in a cgo program changes the C
+// environment. Test that this does not confuse the race detector.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "time"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ f := func() {
+ defer wg.Done()
+ for i := 0; i < 100; i++ {
+ time.Sleep(time.Microsecond)
+ mu.Lock()
+ s := fmt.Sprint(i)
+ os.Setenv("TSAN_TEST"+s, s)
+ mu.Unlock()
+ }
+ }
+ wg.Add(2)
+ go f()
+ go f()
+ wg.Wait()
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan8.go b/misc/cgo/testsanitizers/testdata/tsan8.go
new file mode 100644
index 0000000..88d82a6
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan8.go
@@ -0,0 +1,60 @@
+// Copyright 2016 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
+
+// This program failed when run under the C/C++ ThreadSanitizer. The TSAN
+// sigaction function interceptor returned SIG_DFL instead of the Go runtime's
+// handler in registerSegvForwarder.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct sigaction prev_sa;
+
+void forwardSignal(int signo, siginfo_t *info, void *context) {
+ // One of sa_sigaction and/or sa_handler
+ if ((prev_sa.sa_flags&SA_SIGINFO) != 0) {
+ prev_sa.sa_sigaction(signo, info, context);
+ return;
+ }
+ if (prev_sa.sa_handler != SIG_IGN && prev_sa.sa_handler != SIG_DFL) {
+ prev_sa.sa_handler(signo);
+ return;
+ }
+
+ fprintf(stderr, "No Go handler to forward to!\n");
+ abort();
+}
+
+void registerSegvFowarder() {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ sa.sa_sigaction = forwardSignal;
+
+ if (sigaction(SIGSEGV, &sa, &prev_sa) != 0) {
+ perror("failed to register SEGV forwarder");
+ exit(EXIT_FAILURE);
+ }
+}
+*/
+import "C"
+
+func main() {
+ C.registerSegvFowarder()
+
+ defer func() {
+ recover()
+ }()
+ var nilp *int
+ *nilp = 42
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan9.go b/misc/cgo/testsanitizers/testdata/tsan9.go
new file mode 100644
index 0000000..06304be
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan9.go
@@ -0,0 +1,67 @@
+// Copyright 2016 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
+
+// This program failed when run under the C/C++ ThreadSanitizer. The
+// TSAN library was not keeping track of whether signals should be
+// delivered on the alternate signal stack, and the Go signal handler
+// was not preserving callee-saved registers from C callers.
+
+/*
+#cgo CFLAGS: -g -fsanitize=thread
+#cgo LDFLAGS: -g -fsanitize=thread
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+void spin() {
+ size_t n;
+ struct timeval tvstart, tvnow;
+ int diff;
+ void *prev = NULL, *cur;
+
+ gettimeofday(&tvstart, NULL);
+ for (n = 0; n < 1<<20; n++) {
+ cur = malloc(n);
+ free(prev);
+ prev = cur;
+
+ gettimeofday(&tvnow, NULL);
+ diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec);
+
+ // Profile frequency is 100Hz so we should definitely
+ // get a signal in 50 milliseconds.
+ if (diff > 50 * 1000) {
+ break;
+ }
+ }
+
+ free(prev);
+}
+*/
+import "C"
+
+import (
+ "io"
+ "runtime/pprof"
+ "time"
+)
+
+func goSpin() {
+ start := time.Now()
+ for n := 0; n < 1<<20; n++ {
+ _ = make([]byte, n)
+ if time.Since(start) > 50*time.Millisecond {
+ break
+ }
+ }
+}
+
+func main() {
+ pprof.StartCPUProfile(io.Discard)
+ go C.spin()
+ goSpin()
+ pprof.StopCPUProfile()
+}
diff --git a/misc/cgo/testsanitizers/testdata/tsan_shared.go b/misc/cgo/testsanitizers/testdata/tsan_shared.go
new file mode 100644
index 0000000..55ff67e
--- /dev/null
+++ b/misc/cgo/testsanitizers/testdata/tsan_shared.go
@@ -0,0 +1,63 @@
+// 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 main
+
+// This program failed with SIGSEGV when run under the C/C++ ThreadSanitizer.
+// The Go runtime had re-registered the C handler with the wrong flags due to a
+// typo, resulting in null pointers being passed for the info and context
+// parameters to the handler.
+
+/*
+#cgo CFLAGS: -fsanitize=thread
+#cgo LDFLAGS: -fsanitize=thread
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucontext.h>
+
+void check_params(int signo, siginfo_t *info, void *context) {
+ ucontext_t* uc = (ucontext_t*)(context);
+
+ if (info->si_signo != signo) {
+ fprintf(stderr, "info->si_signo does not match signo.\n");
+ abort();
+ }
+
+ if (uc->uc_stack.ss_size == 0) {
+ fprintf(stderr, "uc_stack has size 0.\n");
+ abort();
+ }
+}
+
+
+// Set up the signal handler in a high priority constructor, so
+// that it is installed before the Go code starts.
+
+static void register_handler(void) __attribute__ ((constructor (200)));
+
+static void register_handler() {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = check_params;
+
+ if (sigaction(SIGUSR1, &sa, NULL) != 0) {
+ perror("failed to register SIGUSR1 handler");
+ exit(EXIT_FAILURE);
+ }
+}
+*/
+import "C"
+
+import "syscall"
+
+func init() {
+ C.raise(C.int(syscall.SIGUSR1))
+}
+
+func main() {}
diff --git a/misc/cgo/testsanitizers/tsan_test.go b/misc/cgo/testsanitizers/tsan_test.go
new file mode 100644
index 0000000..ec4e003
--- /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)
+ })
+ }
+}