// Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux || (freebsd && amd64) package sanitizers_test import ( "internal/platform" "internal/testenv" "strings" "testing" ) func TestLSAN(t *testing.T) { config := mustHaveLSAN(t) t.Parallel() mustRun(t, config.goCmd("build", "std")) cases := []struct { src string leakError string errorLocation string }{ {src: "lsan1.go", leakError: "detected memory leaks", errorLocation: "lsan1.go:11"}, {src: "lsan2.go"}, {src: "lsan3.go"}, } for _, tc := range cases { name := strings.TrimSuffix(tc.src, ".go") t.Run(name, func(t *testing.T) { t.Parallel() dir := newTempDir(t) defer dir.RemoveAll(t) outPath := dir.Join(name) mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src))) cmd := hangProneCmd(outPath) if tc.leakError == "" { mustRun(t, cmd) } else { outb, err := cmd.CombinedOutput() out := string(outb) if err != nil || len(out) > 0 { t.Logf("%s\n%v\n%s", cmd, err, out) } if err != nil && strings.Contains(out, tc.leakError) { // This string is output if the // sanitizer library needs a // symbolizer program and can't find it. const noSymbolizer = "external symbolizer" if tc.errorLocation != "" && !strings.Contains(out, tc.errorLocation) && !strings.Contains(out, noSymbolizer) && compilerSupportsLocation() { t.Errorf("output does not contain expected location of the error %q", tc.errorLocation) } } else { t.Errorf("output does not contain expected leak error %q", tc.leakError) } // Make sure we can disable the leak check. cmd = hangProneCmd(outPath) replaceEnv(cmd, "ASAN_OPTIONS", "detect_leaks=0") mustRun(t, cmd) } }) } } func mustHaveLSAN(t *testing.T) *config { testenv.MustHaveGoBuild(t) testenv.MustHaveCGO(t) goos, err := goEnv("GOOS") if err != nil { t.Fatal(err) } goarch, err := goEnv("GOARCH") if err != nil { t.Fatal(err) } // LSAN is a subset of ASAN, so just check for ASAN support. if !platform.ASanSupported(goos, goarch) { t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch) } if !compilerRequiredLsanVersion(goos, goarch) { t.Skipf("skipping on %s/%s: too old version of compiler", goos, goarch) } requireOvercommit(t) config := configure("leak") config.skipIfCSanitizerBroken(t) return config }