github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/misc/cgo/testsanitizers/cc_test.go (about) 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. 4 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 8 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" 25 ) 26 27 var overcommit struct { 28 sync.Once 29 value int 30 err error 31 } 32 33 // requireOvercommit skips t if the kernel does not allow overcommit. 34 func requireOvercommit(t *testing.T) { 35 t.Helper() 36 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 42 } 43 overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out))) 44 }) 45 46 if overcommit.err != nil { 47 t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err) 48 } 49 if overcommit.value == 2 { 50 t.Skip("vm.overcommit_memory=2") 51 } 52 } 53 54 var env struct { 55 sync.Once 56 m map[string]string 57 err error 58 } 59 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 67 } 68 69 env.m = make(map[string]string) 70 env.err = json.Unmarshal(out, &env.m) 71 }) 72 if env.err != nil { 73 return "", env.err 74 } 75 76 v, ok := env.m[key] 77 if !ok { 78 return "", fmt.Errorf("`go env`: no entry for %v", key) 79 } 80 return v, nil 81 } 82 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() 87 } 88 cmd.Env = append(cmd.Env, key+"="+value) 89 } 90 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) 97 } 98 } 99 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 105 } 106 107 GOGCCFLAGS, err := goEnv("GOGCCFLAGS") 108 if err != nil { 109 return nil, err 110 } 111 112 // Split GOGCCFLAGS, respecting quoting. 113 // 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 128 } 129 } else { 130 if lastSpace { 131 start = i 132 lastSpace = false 133 } 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 143 } 144 } 145 } 146 if !lastSpace { 147 flags = append(flags, GOGCCFLAGS[start:]) 148 } 149 150 cmd := exec.Command(CC, flags...) 151 cmd.Args = append(cmd.Args, args...) 152 return cmd, nil 153 } 154 155 type version struct { 156 name string 157 major, minor int 158 } 159 160 var compiler struct { 161 sync.Once 162 version 163 err error 164 } 165 166 // compilerVersion detects the version of $(go env CC). 167 // 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" 174 175 cmd, err := cc("--version") 176 if err != nil { 177 return err 178 } 179 out, err := cmd.Output() 180 if err != nil { 181 // Compiler does not support "--version" flag: not Clang or GCC. 182 return nil 183 } 184 185 var match [][]byte 186 if bytes.HasPrefix(out, []byte("gcc")) { 187 compiler.name = "gcc" 188 189 cmd, err := cc("-dumpversion") 190 if err != nil { 191 return err 192 } 193 out, err := cmd.Output() 194 if err != nil { 195 // gcc, but does not support gcc's "-dumpversion" flag?! 196 return err 197 } 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" 204 } 205 } 206 207 if len(match) < 3 { 208 return nil // "unknown" 209 } 210 if compiler.major, err = strconv.Atoi(string(match[1])); err != nil { 211 return err 212 } 213 if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil { 214 return err 215 } 216 return nil 217 }() 218 }) 219 return compiler.version, compiler.err 220 } 221 222 type compilerCheck struct { 223 once sync.Once 224 err error 225 skip bool // If true, skip with err instead of failing with it. 226 } 227 228 type config struct { 229 sanitizer string 230 231 cFlags, ldFlags, goFlags []string 232 233 sanitizerCheck, runtimeCheck compilerCheck 234 } 235 236 var configs struct { 237 sync.Mutex 238 m map[string]*config 239 } 240 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 247 } 248 249 c := &config{ 250 sanitizer: sanitizer, 251 cFlags: []string{"-fsanitize=" + sanitizer}, 252 ldFlags: []string{"-fsanitize=" + sanitizer}, 253 } 254 255 if testing.Verbose() { 256 c.goFlags = append(c.goFlags, "-x") 257 } 258 259 switch sanitizer { 260 case "memory": 261 c.goFlags = append(c.goFlags, "-msan") 262 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") 269 } 270 271 default: 272 panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer)) 273 } 274 275 if configs.m == nil { 276 configs.m = make(map[string]*config) 277 } 278 configs.m[sanitizer] = c 279 return c 280 } 281 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 291 } 292 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() 299 }) 300 if check.err != nil { 301 t.Helper() 302 if check.skip { 303 t.Skip(check.err) 304 } 305 t.Fatal(check.err) 306 } 307 } 308 309 var cMain = []byte(` 310 int main() { 311 return 0; 312 } 313 `) 314 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) 319 } 320 defer os.RemoveAll(dir) 321 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) 325 } 326 327 dst := filepath.Join(dir, "return0") 328 cmd, err := cc(c.cFlags...) 329 if err != nil { 330 return false, err 331 } 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)) 340 } 341 return true, fmt.Errorf("%#q failed: %v\n%s", strings.Join(cmd.Args, " "), err, out) 342 } 343 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) 347 } 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) 350 } 351 352 return false, nil 353 } 354 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() 361 }) 362 if check.err != nil { 363 t.Helper() 364 if check.skip { 365 t.Skip(check.err) 366 } 367 t.Fatal(check.err) 368 } 369 } 370 371 func (c *config) checkRuntime() (skip bool, err error) { 372 if c.sanitizer != "thread" { 373 return false, nil 374 } 375 376 // libcgo.h sets CGO_TSAN if it detects TSAN support in the C compiler. 377 // Dump the preprocessor defines to check 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 382 } 383 cmd.Args = append(cmd.Args, "-dM", "-E", "../../../src/runtime/cgo/libcgo.h") 384 cmdStr := strings.Join(cmd.Args, " ") 385 out, err := cmd.CombinedOutput() 386 if err != nil { 387 return false, fmt.Errorf("%#q exited with %v\n%s", cmdStr, err, out) 388 } 389 if !bytes.Contains(out, []byte("#define CGO_TSAN")) { 390 return true, fmt.Errorf("%#q did not define CGO_TSAN", cmdStr) 391 } 392 return false, nil 393 } 394 395 // srcPath returns the path to the given file relative to this test's source tree. 396 func srcPath(path string) string { 397 return filepath.Join("testdata", path) 398 } 399 400 // A tempDir manages a temporary directory within a test. 401 type tempDir struct { 402 base string 403 } 404 405 func (d *tempDir) RemoveAll(t *testing.T) { 406 t.Helper() 407 if d.base == "" { 408 return 409 } 410 if err := os.RemoveAll(d.base); err != nil { 411 t.Fatalf("Failed to remove temp dir: %v", err) 412 } 413 } 414 415 func (d *tempDir) Join(name string) string { 416 return filepath.Join(d.base, name) 417 } 418 419 func newTempDir(t *testing.T) *tempDir { 420 t.Helper() 421 dir, err := ioutil.TempDir("", filepath.Dir(t.Name())) 422 if err != nil { 423 t.Fatalf("Failed to create temp dir: %v", err) 424 } 425 return &tempDir{base: dir} 426 } 427 428 // hangProneCmd returns an exec.Cmd for a command that is likely to hang. 429 // 430 // If one of these tests hangs, the caller is likely to kill the test process 431 // using SIGINT, which will be sent to all of the processes in the test's group. 432 // Unfortunately, TSAN in particular is prone to dropping signals, so the SIGINT 433 // may terminate the test binary but leave the subprocess running. hangProneCmd 434 // configures subprocess to receive SIGKILL instead to ensure that it won't 435 // leak. 436 func hangProneCmd(name string, arg ...string) *exec.Cmd { 437 cmd := exec.Command(name, arg...) 438 cmd.SysProcAttr = &syscall.SysProcAttr{ 439 Pdeathsig: syscall.SIGKILL, 440 } 441 return cmd 442 }