33// `.,'\
44// \ Copyright 2026-present Context contributors.
55// SPDX-License-Identifier: Apache-2.0
6- //
6+
77// ================================================================
88// STOP — Read internal/audit/README.md before editing this file.
99//
1919package audit
2020
2121import (
22+ "go/ast"
23+ "go/parser"
24+ "go/token"
2225 "go/types"
26+ "os"
27+ "path/filepath"
2328 "strings"
2429 "testing"
2530 "unicode"
@@ -38,27 +43,57 @@ import (
3843// internal and may be used via reflection or are
3944// genuinely file-scoped helpers.
4045
41- // DO NOT add entries here to make tests pass. New code must
42- // conform to the check. Widening requires a dedicated PR with
43- // justification for each entry.
44- //
45- // linuxOnlyExports lists exported symbols used only from
46- // _linux.go source files. These appear dead on non-Linux
47- // builds because go/packages loads only the current
46+ // rescuePlatformExports parses ALL .go files under
47+ // internal/ (ignoring build tags) and returns the set of
48+ // selector names (e.g. "CmdSysctl", "ProcMeminfo") found
49+ // in non-test source files. This rescues exports that
50+ // appear dead because go/packages only loads the current
4851// platform's file set.
49- var linuxOnlyExports = map [string ]bool {
50- "github.com/ActiveMemory/ctx/internal/config/sysinfo.ProcLoadavg" : true ,
51- "github.com/ActiveMemory/ctx/internal/config/sysinfo.ProcMeminfo" : true ,
52- "github.com/ActiveMemory/ctx/internal/config/sysinfo.LoadavgFmt" : true ,
53- "github.com/ActiveMemory/ctx/internal/config/sysinfo.MemInfoSuffix" : true ,
54- "github.com/ActiveMemory/ctx/internal/config/sysinfo.BytesPerKB" : true ,
55- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldMemTotal" : true ,
56- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldMemAvailable" : true ,
57- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldMemFree" : true ,
58- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldBuffers" : true ,
59- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldCached" : true ,
60- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldSwapTotal" : true ,
61- "github.com/ActiveMemory/ctx/internal/config/sysinfo.FieldSwapFree" : true ,
52+ //
53+ // No manual allowlist: any symbol referenced from any
54+ // platform file is automatically kept alive.
55+ func rescuePlatformExports (
56+ t * testing.T ,
57+ ) map [string ]bool {
58+ t .Helper ()
59+ selectors := make (map [string ]bool )
60+ root := filepath .Join (".." , ".." )
61+ walkErr := filepath .WalkDir (
62+ filepath .Join (root , "internal" ),
63+ func (
64+ path string , d os.DirEntry , err error ,
65+ ) error {
66+ if err != nil || d .IsDir () {
67+ return err
68+ }
69+ if ! strings .HasSuffix (path , ".go" ) {
70+ return nil
71+ }
72+ if isTestFile (path ) {
73+ return nil
74+ }
75+ fset := token .NewFileSet ()
76+ f , parseErr := parser .ParseFile (
77+ fset , path , nil , 0 ,
78+ )
79+ if parseErr != nil {
80+ return nil
81+ }
82+ ast .Inspect (f , func (n ast.Node ) bool {
83+ sel , ok := n .(* ast.SelectorExpr )
84+ if ! ok {
85+ return true
86+ }
87+ selectors [sel .Sel .Name ] = true
88+ return true
89+ })
90+ return nil
91+ },
92+ )
93+ if walkErr != nil {
94+ t .Fatalf ("walk for platform rescue: %v" , walkErr )
95+ }
96+ return selectors
6297}
6398
6499func TestNoDeadExports (t * testing.T ) {
@@ -163,10 +198,22 @@ func TestNoDeadExports(t *testing.T) {
163198 }
164199 }
165200
166- // Phase 3b: remove Linux-only exports (used from
167- // _linux.go files not loaded on this platform).
168- for key := range linuxOnlyExports {
169- delete (defs , key )
201+ // Phase 3b: rescue exports used in platform-specific
202+ // files (_linux.go, _darwin.go, etc.) that go/packages
203+ // did not load on the current OS. Uses go/parser to
204+ // scan ALL .go files regardless of build tags.
205+ rescued := rescuePlatformExports (t )
206+ for key , info := range defs {
207+ // Extract the symbol name from "pkg.Name".
208+ dot := strings .LastIndex (key , "." )
209+ if dot < 0 {
210+ continue
211+ }
212+ name := key [dot + 1 :]
213+ if rescued [name ] {
214+ delete (defs , key )
215+ _ = info // used for deletion only
216+ }
170217 }
171218
172219 // Phase 4: report survivors as dead exports.
0 commit comments