@@ -524,18 +524,23 @@ func expandShellExprs(s string) string {
524524}
525525
526526// parseFileDirectives scans the leading lines of src for //! directives and
527- // returns linker flags and C source files to compile in.
527+ // returns linker flags, C source files to compile in, and valgrind
528+ // suppression paths to apply when the test runs under --valgrind.
528529//
529530// //!-lm -> linker flag -lm
530531// //!-lm [x86_64] -> linker flag -lm, x86_64 only
531532// //!+helper.c -> compile helper.c alongside the module
532533// //!+src/foo.c -- -DDEBUG -> compile src/foo.c with extra flag -DDEBUG
533534// //!+src/foo.c [arch] -> compile only on matching arch
534535// //!+src/foo.c [arch] -- FLAGS -> arch-specific file with extra flags
536+ // //!-suppressions=PATH -> pass --suppressions=PATH to valgrind
537+ // for this file (no effect outside --valgrind)
535538//
536- // srcDir is the directory of the .tin file; relative C source paths are
537- // resolved against it. Scanning stops at the first non-comment, non-blank line.
538- func parseFileDirectives (src , srcDir , stdlibDir string ) (linkerFlags []string , cSources []cSource ) {
539+ // srcDir is the directory of the .tin file; relative paths are resolved
540+ // against it. $TIN_RUNTIME / $TIN_STDLIB / $ENV variables expand in
541+ // suppression paths the same way they do in //!+file flags. Scanning
542+ // stops at the first non-comment, non-blank line.
543+ func parseFileDirectives (src , srcDir , stdlibDir string ) (linkerFlags []string , cSources []cSource , vgSuppressions []string ) {
539544 for _ , line := range strings .SplitAfter (src , "\n " ) {
540545 trimmed := strings .TrimSpace (line )
541546 if trimmed == "" || strings .HasPrefix (trimmed , "//" ) && ! strings .HasPrefix (trimmed , "//!" ) {
@@ -603,6 +608,27 @@ func parseFileDirectives(src, srcDir, stdlibDir string) (linkerFlags []string, c
603608 }
604609
605610 cSources = append (cSources , cSource {path : cpath , flags : extraFlags })
611+ } else if strings .HasPrefix (rest , "-suppressions=" ) {
612+ // Valgrind-only directive: register a suppressions file
613+ // that applies when the binary runs under --valgrind.
614+ // Honours the same `[arch]` qualifier as the other
615+ // directives so platform-specific suppressions stay
616+ // scoped (e.g. a glibc-only file is skipped on macOS).
617+ specAndQualifier , archQualifier := extractArchQualifier (strings .TrimPrefix (rest , "-suppressions=" ))
618+ if ! archMatches (archQualifier ) {
619+ continue
620+ }
621+
622+ rtDir := tinRuntimeDir ()
623+ expanded := strings .ReplaceAll (specAndQualifier , "$TIN_RUNTIME" , rtDir )
624+ expanded = strings .ReplaceAll (expanded , "$TIN_STDLIB" , stdlibDir )
625+ expanded = os .ExpandEnv (expanded )
626+
627+ if ! filepath .IsAbs (expanded ) {
628+ expanded = filepath .Join (srcDir , expanded )
629+ }
630+
631+ vgSuppressions = append (vgSuppressions , expanded )
606632 } else {
607633 // Linker flag: check for optional arch qualifier.
608634 flagAndQualifier , archQualifier := extractArchQualifier (rest )
@@ -995,7 +1021,10 @@ doneFlags:
9951021 if _ , statErr := os .Stat (runCacheBinPath ); statErr == nil && sbomMatches (runCacheDir ) {
9961022 memcheck , binArgs := parseRunArgs (fileArgIdx )
9971023 validateMemcheck (memcheck )
998- execRunBinary (runCacheBinPath , memcheck , binArgs )
1024+ // Pick up `//!-suppressions=` from the source so cached
1025+ // single-file tests still hand them to valgrind.
1026+ _ , _ , vgSupps := parseFileDirectives (string (src ), filepath .Dir (file ), stdlibDirForDirectives (stdlibOverride ))
1027+ execRunBinary (runCacheBinPath , memcheck , binArgs , vgSupps ... )
9991028
10001029 return
10011030 }
@@ -1026,7 +1055,7 @@ doneFlags:
10261055 }
10271056
10281057 // Collect directives declared in the source file via //! lines
1029- fileLinkerFlags , fileCSources := parseFileDirectives (string (src ), filepath .Dir (file ), stdlibDirForDirectives (stdlibOverride ))
1058+ fileLinkerFlags , fileCSources , fileVgSuppressions := parseFileDirectives (string (src ), filepath .Dir (file ), stdlibDirForDirectives (stdlibOverride ))
10301059
10311060 // Estimate total stages for progress display. Mirrors the actual
10321061 // step shape so the post-codegen setTotal call refines without
@@ -1238,7 +1267,7 @@ doneFlags:
12381267 continue
12391268 }
12401269
1241- pkgLinkFlags , pkgCSources := parseFileDirectives (string (src ), filepath .Dir (pkgSrc ), stdlibDirForDirectives (stdlibOverride ))
1270+ pkgLinkFlags , pkgCSources , _ := parseFileDirectives (string (src ), filepath .Dir (pkgSrc ), stdlibDirForDirectives (stdlibOverride ))
12421271 fileLinkerFlags = append (fileLinkerFlags , pkgLinkFlags ... )
12431272 fileCSources = append (fileCSources , pkgCSources ... )
12441273 }
@@ -1437,7 +1466,7 @@ doneFlags:
14371466 cprog .clear ()
14381467
14391468 validateMemcheck (memcheck )
1440- execRunBinary (runCacheBinPath , memcheck , binArgs )
1469+ execRunBinary (runCacheBinPath , memcheck , binArgs , fileVgSuppressions ... )
14411470
14421471 default :
14431472 _ , _ = fmt .Fprint (os .Stderr , usage )
@@ -2668,16 +2697,30 @@ func validateMemcheck(memcheck string) {
26682697// foreign-arch binaries can be exercised on the host. Modeled on Go's GOEXEC
26692698// and Cargo's CARGO_TARGET_<TRIPLE>_RUNNER.
26702699func memcheckCmd (memcheck , binary string , binArgs ... string ) * exec.Cmd {
2700+ return memcheckCmdWithSuppressions (memcheck , binary , nil , binArgs ... )
2701+ }
2702+
2703+ // memcheckCmdWithSuppressions is memcheckCmd plus an explicit list of
2704+ // valgrind --suppressions=PATH files. The test runner gathers these
2705+ // from `//!-suppressions=FILE` directives declared in the test source
2706+ // so the silence stays scoped to the file that opted in -- a global
2707+ // suppression set would silently hide leaks in unrelated tests.
2708+ func memcheckCmdWithSuppressions (memcheck , binary string , vgSuppressions []string , binArgs ... string ) * exec.Cmd {
26712709 switch memcheck {
26722710 case "valgrind" :
2673- args := append ( []string {
2711+ vgArgs := []string {
26742712 "--error-exitcode=1" ,
26752713 "--leak-check=full" ,
26762714 "--errors-for-leak-kinds=all" ,
2677- binary ,
2678- }, binArgs ... )
2715+ }
2716+ for _ , s := range vgSuppressions {
2717+ vgArgs = append (vgArgs , "--suppressions=" + s )
2718+ }
2719+
2720+ vgArgs = append (vgArgs , binary )
2721+ vgArgs = append (vgArgs , binArgs ... )
26792722
2680- return wrapExec ("valgrind" , args ... )
2723+ return wrapExec ("valgrind" , vgArgs ... )
26812724 case "leaks" :
26822725 args := append ([]string {"--atExit" , "--" , binary }, binArgs ... )
26832726
@@ -2851,6 +2894,12 @@ func runFileTests(fpaths []string, extraFlags []string, extraCFlags []string, me
28512894 continue
28522895 }
28532896
2897+ // Parse //!-suppressions= directives up front so both the
2898+ // cache-hit and the fresh-compile branches below can hand them
2899+ // to memcheckCmdWithSuppressions; valgrind picks them up,
2900+ // non-valgrind runs just ignore the list.
2901+ _ , _ , fileVgSuppressions := parseFileDirectives (string (src ), filepath .Dir (fpath ), stdlibDirForDirectives ("" ))
2902+
28542903 // Cache lookup: if the test binary is already built and every dep
28552904 // recorded in its SBOM still hashes the same, run the cached binary
28562905 // directly and skip lex/parse/codegen for this file.
@@ -2860,7 +2909,7 @@ func runFileTests(fpaths []string, extraFlags []string, extraCFlags []string, me
28602909 if _ , statErr := os .Stat (cachedBin ); statErr == nil && sbomMatches (cacheDir ) {
28612910 fmt .Printf ("%s\n \n " , fname )
28622911
2863- run := memcheckCmd (memcheck , cachedBin )
2912+ run := memcheckCmdWithSuppressions (memcheck , cachedBin , fileVgSuppressions )
28642913
28652914 var outBuf bytes.Buffer
28662915
@@ -2994,21 +3043,24 @@ func runFileTests(fpaths []string, extraFlags []string, extraCFlags []string, me
29943043 continue // no test blocks in this file
29953044 }
29963045
2997- fileLinks , fCSources := parseFileDirectives (string (src ), filepath .Dir (fpath ), stdlibDirForDirectives ("" ))
3046+ fileLinks , fCSources , _ := parseFileDirectives (string (src ), filepath .Dir (fpath ), stdlibDirForDirectives ("" ))
29983047
29993048 srcLinks := append ([]string {}, fileLinks ... )
30003049 for _ , lib := range cg .LinkLibs () {
30013050 srcLinks = append (srcLinks , "-l" + lib )
30023051 }
30033052 // Collect //!+file.c and //!-lNAME directives from imported packages,
3004- // just as the single-file build path does.
3053+ // just as the single-file build path does. --valgrind suppression
3054+ // directives stay scoped to the test file -- pulling them in from
3055+ // every transitive package would silence checks they didn't opt
3056+ // into.
30053057 for _ , pkgSrc := range cg .PackageSrcPaths () {
30063058 pkgBytes , pkgReadErr := os .ReadFile (pkgSrc )
30073059 if pkgReadErr != nil {
30083060 continue
30093061 }
30103062
3011- pkgLinks , pkgCSrcs := parseFileDirectives (string (pkgBytes ), filepath .Dir (pkgSrc ), stdlibDirForDirectives ("" ))
3063+ pkgLinks , pkgCSrcs , _ := parseFileDirectives (string (pkgBytes ), filepath .Dir (pkgSrc ), stdlibDirForDirectives ("" ))
30123064 srcLinks = append (srcLinks , pkgLinks ... )
30133065 fCSources = append (fCSources , pkgCSrcs ... )
30143066 }
@@ -3089,7 +3141,7 @@ func runFileTests(fpaths []string, extraFlags []string, extraCFlags []string, me
30893141 cprog .clear ()
30903142 fmt .Printf ("%s\n \n " , fname )
30913143
3092- run := memcheckCmd (memcheck , cachedBin )
3144+ run := memcheckCmdWithSuppressions (memcheck , cachedBin , fileVgSuppressions )
30933145
30943146 var outBuf bytes.Buffer
30953147
@@ -3623,8 +3675,8 @@ func collectExtraObjs(fileArgIdx int) []string {
36233675}
36243676
36253677// execRunBinary runs `bin` (under memcheck if set) and exits with its status.
3626- func execRunBinary (bin , memcheck string , binArgs []string ) {
3627- run := memcheckCmd (memcheck , bin , binArgs ... )
3678+ func execRunBinary (bin , memcheck string , binArgs []string , vgSuppressions ... string ) {
3679+ run := memcheckCmdWithSuppressions (memcheck , bin , vgSuppressions , binArgs ... )
36283680 run .Stdout = os .Stdout
36293681 run .Stderr = os .Stderr
36303682
0 commit comments