github.com/kidsbmilk/gofrontend_all@v0.0.0-20220701224323-6479d5976c5d/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 "os" 15 "os/exec" 16 "path/filepath" 17 "regexp" 18 "strconv" 19 "strings" 20 "sync" 21 "syscall" 22 "testing" 23 "unicode" 24 ) 25 26 var overcommit struct { 27 sync.Once 28 value int 29 err error 30 } 31 32 // requireOvercommit skips t if the kernel does not allow overcommit. 33 func requireOvercommit(t *testing.T) { 34 t.Helper() 35 36 overcommit.Once.Do(func() { 37 var out []byte 38 out, overcommit.err = os.ReadFile("/proc/sys/vm/overcommit_memory") 39 if overcommit.err != nil { 40 return 41 } 42 overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out))) 43 }) 44 45 if overcommit.err != nil { 46 t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err) 47 } 48 if overcommit.value == 2 { 49 t.Skip("vm.overcommit_memory=2") 50 } 51 } 52 53 var env struct { 54 sync.Once 55 m map[string]string 56 err error 57 } 58 59 // goEnv returns the output of $(go env) as a map. 60 func goEnv(key string) (string, error) { 61 env.Once.Do(func() { 62 var out []byte 63 out, env.err = exec.Command("go", "env", "-json").Output() 64 if env.err != nil { 65 return 66 } 67 68 env.m = make(map[string]string) 69 env.err = json.Unmarshal(out, &env.m) 70 }) 71 if env.err != nil { 72 return "", env.err 73 } 74 75 v, ok := env.m[key] 76 if !ok { 77 return "", fmt.Errorf("`go env`: no entry for %v", key) 78 } 79 return v, nil 80 } 81 82 // replaceEnv sets the key environment variable to value in cmd. 83 func replaceEnv(cmd *exec.Cmd, key, value string) { 84 if cmd.Env == nil { 85 cmd.Env = os.Environ() 86 } 87 cmd.Env = append(cmd.Env, key+"="+value) 88 } 89 90 // mustRun executes t and fails cmd with a well-formatted message if it fails. 91 func mustRun(t *testing.T, cmd *exec.Cmd) { 92 t.Helper() 93 out, err := cmd.CombinedOutput() 94 if err != nil { 95 t.Fatalf("%#q exited with %v\n%s", strings.Join(cmd.Args, " "), err, out) 96 } 97 } 98 99 // cc returns a cmd that executes `$(go env CC) $(go env GOGCCFLAGS) $args`. 100 func cc(args ...string) (*exec.Cmd, error) { 101 CC, err := goEnv("CC") 102 if err != nil { 103 return nil, err 104 } 105 106 GOGCCFLAGS, err := goEnv("GOGCCFLAGS") 107 if err != nil { 108 return nil, err 109 } 110 111 // Split GOGCCFLAGS, respecting quoting. 112 // 113 // TODO(bcmills): This code also appears in 114 // misc/cgo/testcarchive/carchive_test.go, and perhaps ought to go in 115 // src/cmd/dist/test.go as well. Figure out where to put it so that it can be 116 // shared. 117 var flags []string 118 quote := '\000' 119 start := 0 120 lastSpace := true 121 backslash := false 122 for i, c := range GOGCCFLAGS { 123 if quote == '\000' && unicode.IsSpace(c) { 124 if !lastSpace { 125 flags = append(flags, GOGCCFLAGS[start:i]) 126 lastSpace = true 127 } 128 } else { 129 if lastSpace { 130 start = i 131 lastSpace = false 132 } 133 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 134 quote = c 135 backslash = false 136 } else if !backslash && quote == c { 137 quote = '\000' 138 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 139 backslash = true 140 } else { 141 backslash = false 142 } 143 } 144 } 145 if !lastSpace { 146 flags = append(flags, GOGCCFLAGS[start:]) 147 } 148 149 cmd := exec.Command(CC, flags...) 150 cmd.Args = append(cmd.Args, args...) 151 return cmd, nil 152 } 153 154 type version struct { 155 name string 156 major, minor int 157 } 158 159 var compiler struct { 160 sync.Once 161 version 162 err error 163 } 164 165 // compilerVersion detects the version of $(go env CC). 166 // 167 // It returns a non-nil error if the compiler matches a known version schema but 168 // the version could not be parsed, or if $(go env CC) could not be determined. 169 func compilerVersion() (version, error) { 170 compiler.Once.Do(func() { 171 compiler.err = func() error { 172 compiler.name = "unknown" 173 174 cmd, err := cc("--version") 175 if err != nil { 176 return err 177 } 178 out, err := cmd.Output() 179 if err != nil { 180 // Compiler does not support "--version" flag: not Clang or GCC. 181 return nil 182 } 183 184 var match [][]byte 185 if bytes.HasPrefix(out, []byte("gcc")) { 186 compiler.name = "gcc" 187 188 cmd, err := cc("-dumpversion") 189 if err != nil { 190 return err 191 } 192 out, err := cmd.Output() 193 if err != nil { 194 // gcc, but does not support gcc's "-dumpversion" flag?! 195 return err 196 } 197 gccRE := regexp.MustCompile(`(\d+)\.(\d+)`) 198 match = gccRE.FindSubmatch(out) 199 } else { 200 clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`) 201 if match = clangRE.FindSubmatch(out); len(match) > 0 { 202 compiler.name = "clang" 203 } 204 } 205 206 if len(match) < 3 { 207 return nil // "unknown" 208 } 209 if compiler.major, err = strconv.Atoi(string(match[1])); err != nil { 210 return err 211 } 212 if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil { 213 return err 214 } 215 return nil 216 }() 217 }) 218 return compiler.version, compiler.err 219 } 220 221 // compilerSupportsLocation reports whether the compiler should be 222 // able to provide file/line information in backtraces. 223 func compilerSupportsLocation() bool { 224 compiler, err := compilerVersion() 225 if err != nil { 226 return false 227 } 228 switch compiler.name { 229 case "gcc": 230 return compiler.major >= 10 231 case "clang": 232 return true 233 default: 234 return false 235 } 236 } 237 238 type compilerCheck struct { 239 once sync.Once 240 err error 241 skip bool // If true, skip with err instead of failing with it. 242 } 243 244 type config struct { 245 sanitizer string 246 247 cFlags, ldFlags, goFlags []string 248 249 sanitizerCheck, runtimeCheck compilerCheck 250 } 251 252 var configs struct { 253 sync.Mutex 254 m map[string]*config 255 } 256 257 // configure returns the configuration for the given sanitizer. 258 func configure(sanitizer string) *config { 259 configs.Lock() 260 defer configs.Unlock() 261 if c, ok := configs.m[sanitizer]; ok { 262 return c 263 } 264 265 c := &config{ 266 sanitizer: sanitizer, 267 cFlags: []string{"-fsanitize=" + sanitizer}, 268 ldFlags: []string{"-fsanitize=" + sanitizer}, 269 } 270 271 if testing.Verbose() { 272 c.goFlags = append(c.goFlags, "-x") 273 } 274 275 switch sanitizer { 276 case "memory": 277 c.goFlags = append(c.goFlags, "-msan") 278 279 case "thread": 280 c.goFlags = append(c.goFlags, "--installsuffix=tsan") 281 compiler, _ := compilerVersion() 282 if compiler.name == "gcc" { 283 c.cFlags = append(c.cFlags, "-fPIC") 284 c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan") 285 } 286 287 case "address": 288 c.goFlags = append(c.goFlags, "-asan") 289 // Set the debug mode to print the C stack trace. 290 c.cFlags = append(c.cFlags, "-g") 291 292 default: 293 panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer)) 294 } 295 296 if configs.m == nil { 297 configs.m = make(map[string]*config) 298 } 299 configs.m[sanitizer] = c 300 return c 301 } 302 303 // goCmd returns a Cmd that executes "go $subcommand $args" with appropriate 304 // additional flags and environment. 305 func (c *config) goCmd(subcommand string, args ...string) *exec.Cmd { 306 cmd := exec.Command("go", subcommand) 307 cmd.Args = append(cmd.Args, c.goFlags...) 308 cmd.Args = append(cmd.Args, args...) 309 replaceEnv(cmd, "CGO_CFLAGS", strings.Join(c.cFlags, " ")) 310 replaceEnv(cmd, "CGO_LDFLAGS", strings.Join(c.ldFlags, " ")) 311 return cmd 312 } 313 314 // skipIfCSanitizerBroken skips t if the C compiler does not produce working 315 // binaries as configured. 316 func (c *config) skipIfCSanitizerBroken(t *testing.T) { 317 check := &c.sanitizerCheck 318 check.once.Do(func() { 319 check.skip, check.err = c.checkCSanitizer() 320 }) 321 if check.err != nil { 322 t.Helper() 323 if check.skip { 324 t.Skip(check.err) 325 } 326 t.Fatal(check.err) 327 } 328 } 329 330 var cMain = []byte(` 331 int main() { 332 return 0; 333 } 334 `) 335 336 func (c *config) checkCSanitizer() (skip bool, err error) { 337 dir, err := os.MkdirTemp("", c.sanitizer) 338 if err != nil { 339 return false, fmt.Errorf("failed to create temp directory: %v", err) 340 } 341 defer os.RemoveAll(dir) 342 343 src := filepath.Join(dir, "return0.c") 344 if err := os.WriteFile(src, cMain, 0600); err != nil { 345 return false, fmt.Errorf("failed to write C source file: %v", err) 346 } 347 348 dst := filepath.Join(dir, "return0") 349 cmd, err := cc(c.cFlags...) 350 if err != nil { 351 return false, err 352 } 353 cmd.Args = append(cmd.Args, c.ldFlags...) 354 cmd.Args = append(cmd.Args, "-o", dst, src) 355 out, err := cmd.CombinedOutput() 356 if err != nil { 357 if bytes.Contains(out, []byte("-fsanitize")) && 358 (bytes.Contains(out, []byte("unrecognized")) || 359 bytes.Contains(out, []byte("unsupported"))) { 360 return true, errors.New(string(out)) 361 } 362 return true, fmt.Errorf("%#q failed: %v\n%s", strings.Join(cmd.Args, " "), err, out) 363 } 364 365 if out, err := exec.Command(dst).CombinedOutput(); err != nil { 366 if os.IsNotExist(err) { 367 return true, fmt.Errorf("%#q failed to produce executable: %v", strings.Join(cmd.Args, " "), err) 368 } 369 snippet, _, _ := bytes.Cut(out, []byte("\n")) 370 return true, fmt.Errorf("%#q generated broken executable: %v\n%s", strings.Join(cmd.Args, " "), err, snippet) 371 } 372 373 return false, nil 374 } 375 376 // skipIfRuntimeIncompatible skips t if the Go runtime is suspected not to work 377 // with cgo as configured. 378 func (c *config) skipIfRuntimeIncompatible(t *testing.T) { 379 check := &c.runtimeCheck 380 check.once.Do(func() { 381 check.skip, check.err = c.checkRuntime() 382 }) 383 if check.err != nil { 384 t.Helper() 385 if check.skip { 386 t.Skip(check.err) 387 } 388 t.Fatal(check.err) 389 } 390 } 391 392 func (c *config) checkRuntime() (skip bool, err error) { 393 if c.sanitizer != "thread" { 394 return false, nil 395 } 396 397 // libcgo.h sets CGO_TSAN if it detects TSAN support in the C compiler. 398 // Dump the preprocessor defines to check that works. 399 // (Sometimes it doesn't: see https://golang.org/issue/15983.) 400 cmd, err := cc(c.cFlags...) 401 if err != nil { 402 return false, err 403 } 404 cmd.Args = append(cmd.Args, "-dM", "-E", "../../../src/runtime/cgo/libcgo.h") 405 cmdStr := strings.Join(cmd.Args, " ") 406 out, err := cmd.CombinedOutput() 407 if err != nil { 408 return false, fmt.Errorf("%#q exited with %v\n%s", cmdStr, err, out) 409 } 410 if !bytes.Contains(out, []byte("#define CGO_TSAN")) { 411 return true, fmt.Errorf("%#q did not define CGO_TSAN", cmdStr) 412 } 413 return false, nil 414 } 415 416 // srcPath returns the path to the given file relative to this test's source tree. 417 func srcPath(path string) string { 418 return filepath.Join("testdata", path) 419 } 420 421 // A tempDir manages a temporary directory within a test. 422 type tempDir struct { 423 base string 424 } 425 426 func (d *tempDir) RemoveAll(t *testing.T) { 427 t.Helper() 428 if d.base == "" { 429 return 430 } 431 if err := os.RemoveAll(d.base); err != nil { 432 t.Fatalf("Failed to remove temp dir: %v", err) 433 } 434 } 435 436 func (d *tempDir) Join(name string) string { 437 return filepath.Join(d.base, name) 438 } 439 440 func newTempDir(t *testing.T) *tempDir { 441 t.Helper() 442 dir, err := os.MkdirTemp("", filepath.Dir(t.Name())) 443 if err != nil { 444 t.Fatalf("Failed to create temp dir: %v", err) 445 } 446 return &tempDir{base: dir} 447 } 448 449 // hangProneCmd returns an exec.Cmd for a command that is likely to hang. 450 // 451 // If one of these tests hangs, the caller is likely to kill the test process 452 // using SIGINT, which will be sent to all of the processes in the test's group. 453 // Unfortunately, TSAN in particular is prone to dropping signals, so the SIGINT 454 // may terminate the test binary but leave the subprocess running. hangProneCmd 455 // configures subprocess to receive SIGKILL instead to ensure that it won't 456 // leak. 457 func hangProneCmd(name string, arg ...string) *exec.Cmd { 458 cmd := exec.Command(name, arg...) 459 cmd.SysProcAttr = &syscall.SysProcAttr{ 460 Pdeathsig: syscall.SIGKILL, 461 } 462 return cmd 463 } 464 465 // mSanSupported is a copy of the function cmd/internal/sys.MSanSupported, 466 // because the internal pacakage can't be used here. 467 func mSanSupported(goos, goarch string) bool { 468 switch goos { 469 case "linux": 470 return goarch == "amd64" || goarch == "arm64" 471 default: 472 return false 473 } 474 } 475 476 // aSanSupported is a copy of the function cmd/internal/sys.ASanSupported, 477 // because the internal pacakage can't be used here. 478 func aSanSupported(goos, goarch string) bool { 479 switch goos { 480 case "linux": 481 return goarch == "amd64" || goarch == "arm64" 482 default: 483 return false 484 } 485 }