Source file src/cmd/cgo/internal/testsanitizers/lsan_test.go

     1  // Copyright 2025 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  //go:build linux || (freebsd && amd64)
     6  
     7  package sanitizers_test
     8  
     9  import (
    10  	"internal/platform"
    11  	"internal/testenv"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  func TestLSAN(t *testing.T) {
    17  	config := mustHaveLSAN(t)
    18  
    19  	t.Parallel()
    20  	mustRun(t, config.goCmd("build", "std"))
    21  
    22  	cases := []struct {
    23  		src           string
    24  		leakError     string
    25  		errorLocation string
    26  	}{
    27  		{src: "lsan1.go", leakError: "detected memory leaks", errorLocation: "lsan1.go:11"},
    28  		{src: "lsan2.go"},
    29  		{src: "lsan3.go"},
    30  	}
    31  	for _, tc := range cases {
    32  		name := strings.TrimSuffix(tc.src, ".go")
    33  		t.Run(name, func(t *testing.T) {
    34  			t.Parallel()
    35  
    36  			dir := newTempDir(t)
    37  			defer dir.RemoveAll(t)
    38  
    39  			outPath := dir.Join(name)
    40  			mustRun(t, config.goCmd("build", "-o", outPath, srcPath(tc.src)))
    41  
    42  			cmd := hangProneCmd(outPath)
    43  			if tc.leakError == "" {
    44  				mustRun(t, cmd)
    45  			} else {
    46  				outb, err := cmd.CombinedOutput()
    47  				out := string(outb)
    48  				if err != nil || len(out) > 0 {
    49  					t.Logf("%s\n%v\n%s", cmd, err, out)
    50  				}
    51  				if err != nil && strings.Contains(out, tc.leakError) {
    52  					// This string is output if the
    53  					// sanitizer library needs a
    54  					// symbolizer program and can't find it.
    55  					const noSymbolizer = "external symbolizer"
    56  					if tc.errorLocation != "" &&
    57  						!strings.Contains(out, tc.errorLocation) &&
    58  						!strings.Contains(out, noSymbolizer) &&
    59  						compilerSupportsLocation() {
    60  
    61  						t.Errorf("output does not contain expected location of the error %q", tc.errorLocation)
    62  					}
    63  				} else {
    64  					t.Errorf("output does not contain expected leak error %q", tc.leakError)
    65  				}
    66  
    67  				// Make sure we can disable the leak check.
    68  				cmd = hangProneCmd(outPath)
    69  				replaceEnv(cmd, "ASAN_OPTIONS", "detect_leaks=0")
    70  				mustRun(t, cmd)
    71  			}
    72  		})
    73  	}
    74  }
    75  
    76  func mustHaveLSAN(t *testing.T) *config {
    77  	testenv.MustHaveGoBuild(t)
    78  	testenv.MustHaveCGO(t)
    79  	goos, err := goEnv("GOOS")
    80  	if err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	goarch, err := goEnv("GOARCH")
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	// LSAN is a subset of ASAN, so just check for ASAN support.
    88  	if !platform.ASanSupported(goos, goarch) {
    89  		t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch)
    90  	}
    91  
    92  	if !compilerRequiredLsanVersion(goos, goarch) {
    93  		t.Skipf("skipping on %s/%s: too old version of compiler", goos, goarch)
    94  	}
    95  
    96  	requireOvercommit(t)
    97  
    98  	config := configure("leak")
    99  	config.skipIfCSanitizerBroken(t)
   100  
   101  	return config
   102  }
   103  

View as plain text