Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 63 additions & 8 deletions src/cmd/cgo/internal/testsanitizers/cc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package sanitizers_test

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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"))) {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion src/cmd/cgo/internal/testsanitizers/cshared_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/cgo/internal/testsanitizers/libfuzzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Loading