mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
cmd/cgo/internal/testsanitizers: bound ASAN C support probe
The C sanitizer support check builds and runs a tiny C program before the Go ASAN tests run. Some libasan versions run a slow LeakSanitizer check at process exit even for this no-op program, which can cause TestASAN to time out before it starts testing Go binaries. For ASAN support probes, set leak_check_at_exit=0. This avoids the implicit exit-time leak scan for the probe without disabling explicit LSAN tests. Also run the compile and execute probes with a fixed local timeout so broken C sanitizer configurations are skipped instead of hitting the package-level test timeout. Fixes #72996 Change-Id: I53a0269324f8f0eb214df81aeeecc72dfc432fe1 Reviewed-on: https://go-review.googlesource.com/c/go/+/774660 Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
834214f787
commit
e30b75a910
3 changed files with 65 additions and 10 deletions
|
|
@ -13,6 +13,7 @@ package sanitizers_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -110,6 +111,24 @@ func appendExperimentEnv(cmd *exec.Cmd, experiments []string) {
|
|||
cmd.Env = append(cmd.Env, "GOEXPERIMENT="+exps)
|
||||
}
|
||||
|
||||
func appendASANOptions(cmd *exec.Cmd, opts ...string) {
|
||||
if cmd.Env == nil {
|
||||
cmd.Env = cmd.Environ()
|
||||
}
|
||||
var asanOptions string
|
||||
for _, evar := range cmd.Env {
|
||||
name, value, ok := strings.Cut(evar, "=")
|
||||
if ok && name == "ASAN_OPTIONS" {
|
||||
asanOptions = value
|
||||
}
|
||||
}
|
||||
if asanOptions != "" {
|
||||
asanOptions += ":"
|
||||
}
|
||||
asanOptions += strings.Join(opts, ":")
|
||||
cmd.Env = append(cmd.Env, "ASAN_OPTIONS="+asanOptions)
|
||||
}
|
||||
|
||||
// mustRun executes t and fails cmd with a well-formatted message if it fails.
|
||||
func mustRun(t *testing.T, cmd *exec.Cmd) {
|
||||
t.Helper()
|
||||
|
|
@ -137,7 +156,7 @@ func mustRun(t *testing.T, cmd *exec.Cmd) {
|
|||
}
|
||||
|
||||
// cc returns a cmd that executes `$(go env CC) $(go env GOGCCFLAGS) $args`.
|
||||
func cc(args ...string) (*exec.Cmd, error) {
|
||||
func cc(ctx context.Context, args ...string) (*exec.Cmd, error) {
|
||||
CC, err := goEnv("CC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -186,7 +205,7 @@ func cc(args ...string) (*exec.Cmd, error) {
|
|||
flags = append(flags, GOGCCFLAGS[start:])
|
||||
}
|
||||
|
||||
cmd := exec.Command(CC, flags...)
|
||||
cmd := exec.CommandContext(ctx, CC, flags...)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
return cmd, nil
|
||||
}
|
||||
|
|
@ -211,7 +230,7 @@ func compilerVersion() (version, error) {
|
|||
compiler.err = func() error {
|
||||
compiler.name = "unknown"
|
||||
|
||||
cmd, err := cc("--version")
|
||||
cmd, err := cc(context.Background(), "--version")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -224,7 +243,7 @@ func compilerVersion() (version, error) {
|
|||
var match [][]byte
|
||||
if bytes.HasPrefix(out, []byte("gcc")) {
|
||||
compiler.name = "gcc"
|
||||
cmd, err := cc("-dumpfullversion", "-dumpversion")
|
||||
cmd, err := cc(context.Background(), "-dumpfullversion", "-dumpversion")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -464,6 +483,11 @@ int LLVMFuzzerTestOneInput(char *data, size_t size) {
|
|||
`)
|
||||
|
||||
func (c *config) checkCSanitizer() (skip bool, err error) {
|
||||
// The sanitizer probes compile and run tiny C programs. If either step
|
||||
// takes longer than this, treat the C sanitizer configuration as broken
|
||||
// instead of letting the package-level test timeout fire.
|
||||
probeTimeout := 20 * time.Second
|
||||
|
||||
dir, err := os.MkdirTemp("", c.sanitizer)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create temp directory: %v", err)
|
||||
|
|
@ -481,14 +505,26 @@ func (c *config) checkCSanitizer() (skip bool, err error) {
|
|||
}
|
||||
|
||||
dst := filepath.Join(dir, "return0")
|
||||
cmd, err := cc(c.cFlags...)
|
||||
compileCtx, cancelCompile := context.WithTimeout(context.Background(), probeTimeout)
|
||||
defer cancelCompile()
|
||||
cmd, err := cc(compileCtx, c.cFlags...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cmd.Args = append(cmd.Args, c.ldFlags...)
|
||||
cmd.Args = append(cmd.Args, "-o", dst, src)
|
||||
if c.sanitizer == "address" {
|
||||
// This is only a compiler support probe for ASAN. Some libasan versions
|
||||
// run a slow LeakSanitizer check at exit even for this no-op C program,
|
||||
// which can hang TestASAN before it starts testing Go binaries.
|
||||
appendASANOptions(cmd, "leak_check_at_exit=0")
|
||||
}
|
||||
makeHangProne(cmd)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if errors.Is(compileCtx.Err(), context.DeadlineExceeded) {
|
||||
return true, fmt.Errorf("%#q timed out after %v", cmd, probeTimeout)
|
||||
}
|
||||
if bytes.Contains(out, []byte("-fsanitize")) &&
|
||||
(bytes.Contains(out, []byte("unrecognized")) ||
|
||||
bytes.Contains(out, []byte("unsupported"))) {
|
||||
|
|
@ -502,7 +538,20 @@ func (c *config) checkCSanitizer() (skip bool, err error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if out, err := exec.Command(dst).CombinedOutput(); err != nil {
|
||||
runCtx, cancelRun := context.WithTimeout(context.Background(), probeTimeout)
|
||||
defer cancelRun()
|
||||
cmd = exec.CommandContext(runCtx, dst)
|
||||
makeHangProne(cmd)
|
||||
if c.sanitizer == "address" {
|
||||
// Match the compile-time probe above: avoid libasan's implicit LSan exit
|
||||
// scan for this standalone C binary. The explicit LSAN tests still run
|
||||
// with leak checking enabled.
|
||||
appendASANOptions(cmd, "leak_check_at_exit=0")
|
||||
}
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
if errors.Is(runCtx.Err(), context.DeadlineExceeded) {
|
||||
return true, fmt.Errorf("%#q timed out after %v", cmd, probeTimeout)
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return true, fmt.Errorf("%#q failed to produce executable: %v", cmd, err)
|
||||
}
|
||||
|
|
@ -537,7 +586,7 @@ func (c *config) checkRuntime() (skip bool, err error) {
|
|||
// 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...)
|
||||
cmd, err := cc(context.Background(), c.cFlags...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -594,8 +643,14 @@ func newTempDir(t *testing.T) *tempDir {
|
|||
// leak.
|
||||
func hangProneCmd(name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
makeHangProne(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// makeHangProne configures cmd to receive SIGKILL when the parent process receives SIGINT.
|
||||
// See [hangProneCmd] for details.
|
||||
func makeHangProne(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func TestShared(t *testing.T) {
|
|||
}
|
||||
|
||||
dstBin := dir.Join(name)
|
||||
cmd, err := cc(config.cFlags...)
|
||||
cmd, err := cc(t.Context(), config.cFlags...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func TestLibFuzzer(t *testing.T) {
|
|||
mustRun(t, config.goCmd("build", "-buildmode=c-archive", "-o", archivePath, srcPath(tc.goSrc)))
|
||||
|
||||
// build C code (if any) and link with Go code
|
||||
cmd, err := cc(config.cFlags...)
|
||||
cmd, err := cc(t.Context(), config.cFlags...)
|
||||
if err != nil {
|
||||
t.Fatalf("error running cc: %v", err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue