cc_test.go 12 KB

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