From 94d1e3f8b1838e8a04074464a957e979a5c5e36b Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 17 Nov 2025 11:03:15 +0100 Subject: pkg/clangtool: allow final verification of output Let tools verify that all source file names, line numbers, etc are valid/present. If there are any bogus entries, it's better to detect them early, than to crash/error much later when the info is used. --- pkg/clangtool/clangtool.go | 59 ++++++++++++++++++++++++++++++++++++-- pkg/clangtool/tooltest/tooltest.go | 4 ++- pkg/declextract/entity.go | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) (limited to 'pkg') diff --git a/pkg/clangtool/clangtool.go b/pkg/clangtool/clangtool.go index 9b9f9387d..4e3914cf8 100644 --- a/pkg/clangtool/clangtool.go +++ b/pkg/clangtool/clangtool.go @@ -34,7 +34,7 @@ type OutputDataPtr[T any] interface { *T Merge(*T) SetSourceFile(string, func(filename string) string) - SortAndDedup() + Finalize(*Verifier) } // Run runs the clang tool on all files in the compilation database @@ -81,7 +81,15 @@ func Run[Output any, OutputPtr OutputDataPtr[Output]](cfg *Config) (OutputPtr, e } out.Merge(res.out) } - out.SortAndDedup() + // Finalize the output (sort, dedup, etc), and let the output verify + // that all source file names, line numbers, etc are valid/present. + // If there are any bogus entries, it's better to detect them early, + // than to crash/error much later when the info is used. + // Some of the source files (generated) may be in the obj dir. + srcDirs := []string{cfg.KernelSrc, cfg.KernelObj} + if err := Finalize(out, srcDirs); err != nil { + return nil, err + } if cfg.CacheFile != "" { osutil.MkdirAll(filepath.Dir(cfg.CacheFile)) data, err := json.MarshalIndent(out, "", "\t") @@ -95,6 +103,53 @@ func Run[Output any, OutputPtr OutputDataPtr[Output]](cfg *Config) (OutputPtr, e return out, nil } +func Finalize[Output any, OutputPtr OutputDataPtr[Output]](out OutputPtr, srcDirs []string) error { + v := &Verifier{ + srcDirs: srcDirs, + fileCache: make(map[string]int), + } + out.Finalize(v) + if v.err.Len() == 0 { + return nil + } + return errors.New(v.err.String()) +} + +type Verifier struct { + srcDirs []string + fileCache map[string]int // file->line count (-1 is cached for missing files) + err strings.Builder +} + +func (v *Verifier) Filename(file string) { + if _, ok := v.fileCache[file]; ok { + return + } + for _, srcDir := range v.srcDirs { + data, err := os.ReadFile(filepath.Join(srcDir, file)) + if err != nil { + continue + } + v.fileCache[file] = len(bytes.Split(data, []byte{'\n'})) + return + } + v.fileCache[file] = -1 + fmt.Fprintf(&v.err, "missing file: %v\n", file) +} + +func (v *Verifier) LineRange(file string, start, end int) { + v.Filename(file) + lines, ok := v.fileCache[file] + if !ok || lines < 0 { + return + } + // Line numbers produced by clang are 1-based. + if start <= 0 || end < start || end > lines { + fmt.Fprintf(&v.err, "bad line range [%v-%v] for file %v with %v lines\n", + start, end, file, lines) + } +} + func runTool[Output any, OutputPtr OutputDataPtr[Output]](cfg *Config, dbFile, file string) (OutputPtr, error) { relFile := strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(filepath.Clean(file), cfg.KernelSrc), cfg.KernelObj), "/") diff --git a/pkg/clangtool/tooltest/tooltest.go b/pkg/clangtool/tooltest/tooltest.go index 14681946b..b9457e32d 100644 --- a/pkg/clangtool/tooltest/tooltest.go +++ b/pkg/clangtool/tooltest/tooltest.go @@ -48,7 +48,9 @@ func LoadOutput[Output any, OutputPtr clangtool.OutputDataPtr[Output]](t *testin } out.Merge(tmp) }) - out.SortAndDedup() + if err := clangtool.Finalize(out, []string{"testdata"}); err != nil { + t.Fatal(err) + } return out } diff --git a/pkg/declextract/entity.go b/pkg/declextract/entity.go index bd8f143d1..3b5e13a6d 100644 --- a/pkg/declextract/entity.go +++ b/pkg/declextract/entity.go @@ -241,7 +241,7 @@ func (out *Output) Merge(other *Output) { out.NetlinkPolicies = append(out.NetlinkPolicies, other.NetlinkPolicies...) } -func (out *Output) SortAndDedup() { +func (out *Output) Finalize(v *clangtool.Verifier) { out.Functions = clangtool.SortAndDedupSlice(out.Functions) out.Consts = clangtool.SortAndDedupSlice(out.Consts) out.Enums = clangtool.SortAndDedupSlice(out.Enums) -- cgit mrf-deployment