PR c++/86342 - -Wdeprecated-copy and system headers.
[official-gcc.git] / libgo / misc / cgo / testsanitizers / cc_test.go
blob306844bdc80a6b56c9792837b7176c78545b9399
1 // Copyright 2017 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // sanitizers_test checks the use of Go with sanitizers like msan, asan, etc.
6 // See https://github.com/google/sanitizers.
7 package sanitizers_test
9 import (
10 "bytes"
11 "encoding/json"
12 "errors"
13 "fmt"
14 "io/ioutil"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "regexp"
19 "strconv"
20 "strings"
21 "sync"
22 "syscall"
23 "testing"
24 "unicode"
27 var overcommit struct {
28 sync.Once
29 value int
30 err error
33 // requireOvercommit skips t if the kernel does not allow overcommit.
34 func requireOvercommit(t *testing.T) {
35 t.Helper()
37 overcommit.Once.Do(func() {
38 var out []byte
39 out, overcommit.err = ioutil.ReadFile("/proc/sys/vm/overcommit_memory")
40 if overcommit.err != nil {
41 return
43 overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out)))
46 if overcommit.err != nil {
47 t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err)
49 if overcommit.value == 2 {
50 t.Skip("vm.overcommit_memory=2")
54 var env struct {
55 sync.Once
56 m map[string]string
57 err error
60 // goEnv returns the output of $(go env) as a map.
61 func goEnv(key string) (string, error) {
62 env.Once.Do(func() {
63 var out []byte
64 out, env.err = exec.Command("go", "env", "-json").Output()
65 if env.err != nil {
66 return
69 env.m = make(map[string]string)
70 env.err = json.Unmarshal(out, &env.m)
72 if env.err != nil {
73 return "", env.err
76 v, ok := env.m[key]
77 if !ok {
78 return "", fmt.Errorf("`go env`: no entry for %v", key)
80 return v, nil
83 // replaceEnv sets the key environment variable to value in cmd.
84 func replaceEnv(cmd *exec.Cmd, key, value string) {
85 if cmd.Env == nil {
86 cmd.Env = os.Environ()
88 cmd.Env = append(cmd.Env, key+"="+value)
91 // mustRun executes t and fails cmd with a well-formatted message if it fails.
92 func mustRun(t *testing.T, cmd *exec.Cmd) {
93 t.Helper()
94 out, err := cmd.CombinedOutput()
95 if err != nil {
96 t.Fatalf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out)
100 // cc returns a cmd that executes `$(go env CC) $(go env GOGCCFLAGS) $args`.
101 func cc(args ...string) (*exec.Cmd, error) {
102 CC, err := goEnv("CC")
103 if err != nil {
104 return nil, err
107 GOGCCFLAGS, err := goEnv("GOGCCFLAGS")
108 if err != nil {
109 return nil, err
112 // Split GOGCCFLAGS, respecting quoting.
114 // TODO(bcmills): This code also appears in
115 // misc/cgo/testcarchive/carchive_test.go, and perhaps ought to go in
116 // src/cmd/dist/test.go as well. Figure out where to put it so that it can be
117 // shared.
118 var flags []string
119 quote := '\000'
120 start := 0
121 lastSpace := true
122 backslash := false
123 for i, c := range GOGCCFLAGS {
124 if quote == '\000' && unicode.IsSpace(c) {
125 if !lastSpace {
126 flags = append(flags, GOGCCFLAGS[start:i])
127 lastSpace = true
129 } else {
130 if lastSpace {
131 start = i
132 lastSpace = false
134 if quote == '\000' && !backslash && (c == '"' || c == '\'') {
135 quote = c
136 backslash = false
137 } else if !backslash && quote == c {
138 quote = '\000'
139 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
140 backslash = true
141 } else {
142 backslash = false
146 if !lastSpace {
147 flags = append(flags, GOGCCFLAGS[start:])
150 cmd := exec.Command(CC, flags...)
151 cmd.Args = append(cmd.Args, args...)
152 return cmd, nil
155 type version struct {
156 name string
157 major, minor int
160 var compiler struct {
161 sync.Once
162 version
163 err error
166 // compilerVersion detects the version of $(go env CC).
168 // It returns a non-nil error if the compiler matches a known version schema but
169 // the version could not be parsed, or if $(go env CC) could not be determined.
170 func compilerVersion() (version, error) {
171 compiler.Once.Do(func() {
172 compiler.err = func() error {
173 compiler.name = "unknown"
175 cmd, err := cc("--version")
176 if err != nil {
177 return err
179 out, err := cmd.Output()
180 if err != nil {
181 // Compiler does not support "--version" flag: not Clang or GCC.
182 return nil
185 var match [][]byte
186 if bytes.HasPrefix(out, []byte("gcc")) {
187 compiler.name = "gcc"
189 cmd, err := cc("-dumpversion")
190 if err != nil {
191 return err
193 out, err := cmd.Output()
194 if err != nil {
195 // gcc, but does not support gcc's "-dumpversion" flag?!
196 return err
198 gccRE := regexp.MustCompile(`(\d+)\.(\d+)`)
199 match = gccRE.FindSubmatch(out)
200 } else {
201 clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
202 if match = clangRE.FindSubmatch(out); len(match) > 0 {
203 compiler.name = "clang"
207 if len(match) < 3 {
208 return nil // "unknown"
210 if compiler.major, err = strconv.Atoi(string(match[1])); err != nil {
211 return err
213 if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil {
214 return err
216 return nil
219 return compiler.version, compiler.err
222 type compilerCheck struct {
223 once sync.Once
224 err error
225 skip bool // If true, skip with err instead of failing with it.
228 type config struct {
229 sanitizer string
231 cFlags, ldFlags, goFlags []string
233 sanitizerCheck, runtimeCheck compilerCheck
236 var configs struct {
237 sync.Mutex
238 m map[string]*config
241 // configure returns the configuration for the given sanitizer.
242 func configure(sanitizer string) *config {
243 configs.Lock()
244 defer configs.Unlock()
245 if c, ok := configs.m[sanitizer]; ok {
246 return c
249 c := &config{
250 sanitizer: sanitizer,
251 cFlags: []string{"-fsanitize=" + sanitizer},
252 ldFlags: []string{"-fsanitize=" + sanitizer},
255 if testing.Verbose() {
256 c.goFlags = append(c.goFlags, "-x")
259 switch sanitizer {
260 case "memory":
261 c.goFlags = append(c.goFlags, "-msan")
263 case "thread":
264 c.goFlags = append(c.goFlags, "--installsuffix=tsan")
265 compiler, _ := compilerVersion()
266 if compiler.name == "gcc" {
267 c.cFlags = append(c.cFlags, "-fPIC")
268 c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan")
271 default:
272 panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer))
275 if configs.m == nil {
276 configs.m = make(map[string]*config)
278 configs.m[sanitizer] = c
279 return c
282 // goCmd returns a Cmd that executes "go $subcommand $args" with appropriate
283 // additional flags and environment.
284 func (c *config) goCmd(subcommand string, args ...string) *exec.Cmd {
285 cmd := exec.Command("go", subcommand)
286 cmd.Args = append(cmd.Args, c.goFlags...)
287 cmd.Args = append(cmd.Args, args...)
288 replaceEnv(cmd, "CGO_CFLAGS", strings.Join(c.cFlags, " "))
289 replaceEnv(cmd, "CGO_LDFLAGS", strings.Join(c.ldFlags, " "))
290 return cmd
293 // skipIfCSanitizerBroken skips t if the C compiler does not produce working
294 // binaries as configured.
295 func (c *config) skipIfCSanitizerBroken(t *testing.T) {
296 check := &c.sanitizerCheck
297 check.once.Do(func() {
298 check.skip, check.err = c.checkCSanitizer()
300 if check.err != nil {
301 t.Helper()
302 if check.skip {
303 t.Skip(check.err)
305 t.Fatal(check.err)
309 var cMain = []byte(`
310 int main() {
311 return 0;
315 func (c *config) checkCSanitizer() (skip bool, err error) {
316 dir, err := ioutil.TempDir("", c.sanitizer)
317 if err != nil {
318 return false, fmt.Errorf("failed to create temp directory: %v", err)
320 defer os.RemoveAll(dir)
322 src := filepath.Join(dir, "return0.c")
323 if err := ioutil.WriteFile(src, cMain, 0600); err != nil {
324 return false, fmt.Errorf("failed to write C source file: %v", err)
327 dst := filepath.Join(dir, "return0")
328 cmd, err := cc(c.cFlags...)
329 if err != nil {
330 return false, err
332 cmd.Args = append(cmd.Args, c.ldFlags...)
333 cmd.Args = append(cmd.Args, "-o", dst, src)
334 out, err := cmd.CombinedOutput()
335 if err != nil {
336 if bytes.Contains(out, []byte("-fsanitize")) &&
337 (bytes.Contains(out, []byte("unrecognized")) ||
338 bytes.Contains(out, []byte("unsupported"))) {
339 return true, errors.New(string(out))
341 return true, fmt.Errorf("%#q failed: %v\n%s", strings.Join(cmd.Args, " "), err, out)
344 if out, err := exec.Command(dst).CombinedOutput(); err != nil {
345 if os.IsNotExist(err) {
346 return true, fmt.Errorf("%#q failed to produce executable: %v", strings.Join(cmd.Args, " "), err)
348 snippet := bytes.SplitN(out, []byte{'\n'}, 2)[0]
349 return true, fmt.Errorf("%#q generated broken executable: %v\n%s", strings.Join(cmd.Args, " "), err, snippet)
352 return false, nil
355 // skipIfRuntimeIncompatible skips t if the Go runtime is suspected not to work
356 // with cgo as configured.
357 func (c *config) skipIfRuntimeIncompatible(t *testing.T) {
358 check := &c.runtimeCheck
359 check.once.Do(func() {
360 check.skip, check.err = c.checkRuntime()
362 if check.err != nil {
363 t.Helper()
364 if check.skip {
365 t.Skip(check.err)
367 t.Fatal(check.err)
371 func (c *config) checkRuntime() (skip bool, err error) {
372 if c.sanitizer != "thread" {
373 return false, nil
376 // libcgo.h sets CGO_TSAN if it detects TSAN support in the C compiler.
377 // Dump the preprocessor defines to check that that works.
378 // (Sometimes it doesn't: see https://golang.org/issue/15983.)
379 cmd, err := cc(c.cFlags...)
380 if err != nil {
381 return false, err
383 cmd.Args = append(cmd.Args, "-dM", "-E", "../../../src/runtime/cgo/libcgo.h")
384 out, err := cmd.CombinedOutput()
385 if err != nil {
386 return false, fmt.Errorf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out)
388 if !bytes.Contains(out, []byte("#define CGO_TSAN")) {
389 return true, fmt.Errorf("%#q did not define CGO_TSAN")
391 return false, nil
394 // srcPath returns the path to the given file relative to this test's source tree.
395 func srcPath(path string) string {
396 return filepath.Join("src", path)
399 // A tempDir manages a temporary directory within a test.
400 type tempDir struct {
401 base string
404 func (d *tempDir) RemoveAll(t *testing.T) {
405 t.Helper()
406 if d.base == "" {
407 return
409 if err := os.RemoveAll(d.base); err != nil {
410 t.Fatalf("Failed to remove temp dir: %v", err)
414 func (d *tempDir) Join(name string) string {
415 return filepath.Join(d.base, name)
418 func newTempDir(t *testing.T) *tempDir {
419 t.Helper()
420 dir, err := ioutil.TempDir("", filepath.Dir(t.Name()))
421 if err != nil {
422 t.Fatalf("Failed to create temp dir: %v", err)
424 return &tempDir{base: dir}
427 // hangProneCmd returns an exec.Cmd for a command that is likely to hang.
429 // If one of these tests hangs, the caller is likely to kill the test process
430 // using SIGINT, which will be sent to all of the processes in the test's group.
431 // Unfortunately, TSAN in particular is prone to dropping signals, so the SIGINT
432 // may terminate the test binary but leave the subprocess running. hangProneCmd
433 // configures subprocess to receive SIGKILL instead to ensure that it won't
434 // leak.
435 func hangProneCmd(name string, arg ...string) *exec.Cmd {
436 cmd := exec.Command(name, arg...)
437 cmd.SysProcAttr = &syscall.SysProcAttr{
438 Pdeathsig: syscall.SIGKILL,
440 return cmd