[clang] [clang-tools-extra] [clang-tidy] Add experimental header-filter matching (PR #194370)

via cfe-commits cfe-commits at lists.llvm.org
Wed May 6 07:57:40 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-tools-extra

Author: Vladimir Makaev (VladimirMakaev)

<details>
<summary>Changes</summary>

## Summary

This adds an experimental `--experimental-header-filter-matching` option to `clang-tidy`.

Today, `--header-filter` and `--exclude-header-filter` only decide which diagnostics are printed after checks have already matched over the AST. With this option enabled, clang-tidy also uses those filters to avoid AST matcher traversal for out-of-filter declarations.

The target use case is a large C++ monorepo where developers run clang-tidy over a focused module or folder, but each TU includes a broad shared internal framework. Main-file declarations are still matched; the filter only affects included declarations.

## Performance

The most representative LLVM benchmark uses focused clang-tidy checker-module slices. These TUs include broad clang-tidy, Clang AST, and LLVM support headers, while the header filter is restricted to the focused checker module folder.

Check set:

```text
-*,modernize-use-override,readability-qualified-auto,performance-for-range-copy,readability-redundant-member-init,bugprone-unused-return-value,misc-use-internal-linkage
```

Command shape:

```text
run-clang-tidy.py -j8 -quiet -hide-progress -enable-check-profile \
  -clang-tidy-binary <build>/bin/clang-tidy \
  -p <build> \
  -checks=<check-set> \
  -header-filter=<module-header-filter> \
  [--experimental-header-filter-matching=true] \
  <module-source-regex>
```

Each slice was run three times; medians are reported. Warning lines were identical in all repetitions.

| Slice | TUs | Baseline median real | Matching median real | Real speedup | Baseline median check time | Matching median check time | Check-time speedup | Warnings |
|---|---:|---:|---:|---:|---:|---:|---:|---|
| `clang-tools-extra/clang-tidy/bugprone` | 109 | 66.06s | 51.03s | 1.29x | 70.8065s | 35.2657s | 2.01x | identical, 5/5 |
| `clang-tools-extra/clang-tidy/readability` | 62 | 38.82s | 30.28s | 1.28x | 41.1617s | 20.6990s | 1.99x | identical, 4/4 |
| `clang-tools-extra/clang-tidy/modernize` | 53 | 36.31s | 27.26s | 1.33x | 36.9228s | 18.5518s | 1.99x | identical, 4/4 |

The ~2x improvement is in clang-tidy checker-profile time. End-to-end wall time improves less because parsing, preprocessing, and semantic analysis still run.

Additional one-shot LLVM slices with the same check set showed the same direction: `clangd` including tests improved checker time by 2.00x with identical 770/770 warnings, `clang/lib/StaticAnalyzer/Checkers` by 1.86x with identical 516/516 warnings, and `clang/lib/Sema` by 1.76x with identical 1014/1014 warnings.

A more conservative LLVM-specific check set also improved checker time by 24.9% to 34.7% on `clang/lib/AST`, `clang/lib/Sema`, `llvm/lib/Transforms/Scalar`, `llvm/lib/Target/X86`, and `clang-tools-extra/clang-tidy/readability`, again with identical warning lines in those runs.

## Implementation notes

The clang-facing API is a generic `MatchFinderOptions::ShouldSkipLocation` callback. It does not mention clang-tidy or header filters.

clang-tidy wires that callback to a shared `HeaderFilterLocationFilter`, which evaluates the existing header-filter and exclude-header-filter regexes with a per-`FileID` cache. The diagnostic consumer now uses the same helper.

This deliberately does not use `ASTContext::setTraversalScope`. The skip happens in matcher traversal, so the AST context and parent map remain intact. The matcher still descends through an out-of-filter `DeclContext` when it contains an in-filter lexical child, which preserves cases such as a wrapper namespace header including an in-filter header.

## Semantic note

This is an experimental semantic mode, not only a diagnostic filtering mode. Checks that build cross-file or whole-TU state can produce different results if relevant declarations are outside the active header filter.

Known affected patterns include checks such as `misc-confusable-identifiers`, `misc-unused-using-decls`, and `readability-inconsistent-declaration-parameter-name`, where skipped declarations can affect the check's internal state. Generated headers should also be included in the filter when they are semantically part of the focused module.

## Coverage

This includes CLI, `.clang-tidy` config (`ExperimentalHeaderFilterMatching`), `--dump-config`, option merging/defaults, `run-clang-tidy.py`, documentation, release notes, and regression tests for CLI/config behavior, exclude-header-filter, line-filter interaction, main-file diagnostics, wrapper `DeclContext`s, macro expansion locations, and the generic ASTMatchers callback.

The ASTMatchers tests cover pruning, preserved parent/ancestor matcher behavior, and the wrapper-context case. The clang-tidy tests cover the header-filter integration.

## Prior art

This follows the direction from PR #<!-- -->132725 and PR #<!-- -->151035: keep the full AST context intact and put the skip in matcher traversal instead of using `setTraversalScope`. That addresses the parent/ancestor matcher concern that led to reverting PR #<!-- -->128150.

## Testing

```text
git diff --check
git-clang-format --diff HEAD~1 HEAD --extensions h,cpp -- <changed C++ files>
ninja -C ../llvm-build-header-filter-scope clang-tidy FileCheck AllClangUnitTests
../llvm-build-header-filter-scope/tools/clang/unittests/AllClangUnitTests --gtest_filter='MatchFinder.*ShouldSkipLocation*'
../llvm-build-header-filter-scope/bin/llvm-lit -sv \
  clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching-wrapper.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching-macros.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/run-clang-tidy.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/dump-config-filtering.cpp \
  clang-tools-extra/test/clang-tidy/infrastructure/diagnostic.cpp
```


---

Patch is 52.02 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/194370.diff


29 Files Affected:

- (modified) clang-tools-extra/clang-tidy/ClangTidy.cpp (+11) 
- (modified) clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp (+14-19) 
- (modified) clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h (+5-10) 
- (modified) clang-tools-extra/clang-tidy/ClangTidyOptions.cpp (+5) 
- (modified) clang-tools-extra/clang-tidy/ClangTidyOptions.h (+7) 
- (added) clang-tools-extra/clang-tidy/HeaderFilterHelpers.h (+64) 
- (modified) clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp (+28) 
- (modified) clang-tools-extra/clang-tidy/tool/run-clang-tidy.py (+19) 
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+9) 
- (modified) clang-tools-extra/docs/clang-tidy/index.rst (+26-3) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/.clang-tidy (+1) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/1/.clang-tidy (+1) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/config-files/3/.clang-tidy (+1) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/experimental-header-filter-matching/confusable_header.h (+1) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/experimental-header-filter-matching/macro_def_header.h (+6) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/experimental-header-filter-matching/macro_expansion_header.h (+3) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/experimental-header-filter-matching/wrapper_header.h (+3) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/experimental-header-filter-matching/wrapper_kept_header.h (+1) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/config-files.cpp (+6-1) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/diagnostic.cpp (+2) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/dump-config-filtering.cpp (+2) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching-macros.cpp (+18) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching-wrapper.cpp (+10) 
- (added) clang-tools-extra/test/clang-tidy/infrastructure/experimental-header-filter-matching.cpp (+22) 
- (modified) clang-tools-extra/test/clang-tidy/infrastructure/run-clang-tidy.cpp (+15) 
- (modified) clang/docs/ReleaseNotes.rst (+2) 
- (modified) clang/include/clang/ASTMatchers/ASTMatchFinder.h (+18) 
- (modified) clang/lib/ASTMatchers/ASTMatchFinder.cpp (+62-5) 
- (modified) clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp (+182) 


``````````diff
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 05c8fd02fe86a..7d81bcd2f7e55 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -20,6 +20,7 @@
 #include "ClangTidyModule.h"
 #include "ClangTidyProfiling.h"
 #include "ExpandModularHeadersPPCallbacks.h"
+#include "HeaderFilterHelpers.h"
 #include "clang-tidy-config.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -448,6 +449,16 @@ ClangTidyASTConsumerFactory::createASTConsumer(CompilerInstance &Compiler,
   if (!Context.getOptions().SystemHeaders.value_or(false))
     FinderOptions.IgnoreSystemHeaders = true;
 
+  if (Context.getOptions().ExperimentalHeaderFilterMatching.value_or(false)) {
+    auto LocationFilter = std::make_shared<HeaderFilterLocationFilter>(
+        Context.getOptions().HeaderFilterRegex.value_or(""),
+        Context.getOptions().ExcludeHeaderFilterRegex.value_or(""));
+    FinderOptions.ShouldSkipLocation = [LocationFilter,
+                                        SM](SourceLocation Location) {
+      return !LocationFilter->shouldInclude(Location, *SM);
+    };
+  }
+
   auto Finder =
       std::make_unique<ast_matchers::MatchFinder>(std::move(FinderOptions));
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index f7232645a329c..d35cd954e1fd7 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -36,7 +36,6 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/Support/FormatVariadic.h"
-#include "llvm/Support/Regex.h"
 #include <optional>
 #include <tuple>
 #include <utility>
@@ -372,6 +371,7 @@ void ClangTidyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts,
 
   assert(!InSourceFile);
   InSourceFile = true;
+  HeaderFilterLocation.reset();
 }
 
 void ClangTidyDiagnosticConsumer::EndSourceFile() {
@@ -568,9 +568,10 @@ void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
 
 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
                                                const SourceManager &Sources) {
-  // Invalid location may mean a diagnostic in a command line, don't skip these.
   if (!Location.isValid()) {
-    LastErrorRelatesToUserCode = true;
+    LastErrorRelatesToUserCode =
+        LastErrorRelatesToUserCode ||
+        getHeaderFilterLocationFilter().shouldInclude(Location, Sources);
     LastErrorPassesLineFilter = true;
     return;
   }
@@ -579,6 +580,10 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
       (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location)))
     return;
 
+  LastErrorRelatesToUserCode =
+      LastErrorRelatesToUserCode ||
+      getHeaderFilterLocationFilter().shouldInclude(Location, Sources);
+
   // FIXME: We start with a conservative approach here, but the actual type of
   // location needed depends on the check (in particular, where this check wants
   // to apply fixes).
@@ -588,34 +593,24 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
   // -DMACRO definitions on the command line have locations in a virtual buffer
   // that doesn't have a FileEntry. Don't skip these as well.
   if (!File) {
-    LastErrorRelatesToUserCode = true;
     LastErrorPassesLineFilter = true;
     return;
   }
 
   const StringRef FileName(File->getName());
-  LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
-                               Sources.isInMainFile(Location) ||
-                               (getHeaderFilter()->match(FileName) &&
-                                !getExcludeHeaderFilter()->match(FileName));
 
   const unsigned LineNumber = Sources.getExpansionLineNumber(Location);
   LastErrorPassesLineFilter =
       LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
 }
 
-llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
-  if (!HeaderFilter)
-    HeaderFilter = std::make_unique<llvm::Regex>(
-        Context.getOptions().HeaderFilterRegex.value_or(""));
-  return HeaderFilter.get();
-}
-
-llvm::Regex *ClangTidyDiagnosticConsumer::getExcludeHeaderFilter() {
-  if (!ExcludeHeaderFilter)
-    ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
+HeaderFilterLocationFilter &
+ClangTidyDiagnosticConsumer::getHeaderFilterLocationFilter() {
+  if (!HeaderFilterLocation)
+    HeaderFilterLocation = std::make_unique<HeaderFilterLocationFilter>(
+        Context.getOptions().HeaderFilterRegex.value_or(""),
         Context.getOptions().ExcludeHeaderFilterRegex.value_or(""));
-  return ExcludeHeaderFilter.get();
+  return *HeaderFilterLocation;
 }
 
 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index 8de5778dfefb0..241e70f915677 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -12,12 +12,12 @@
 #include "ClangTidyOptions.h"
 #include "ClangTidyProfiling.h"
 #include "FileExtensionsSet.h"
+#include "HeaderFilterHelpers.h"
 #include "NoLintDirectiveHandler.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Tooling/Core/Diagnostic.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringSet.h"
-#include "llvm/Support/Regex.h"
 #include <optional>
 #include <utility>
 
@@ -310,13 +310,9 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
   void removeIncompatibleErrors();
   void removeDuplicatedDiagnosticsOfAliasCheckers();
 
-  /// Returns the \c HeaderFilter constructed for the options set in the
-  /// context.
-  llvm::Regex *getHeaderFilter();
-
-  /// Returns the \c ExcludeHeaderFilter constructed for the options set in the
-  /// context.
-  llvm::Regex *getExcludeHeaderFilter();
+  /// Returns the cached header-filter evaluator for the current translation
+  /// unit.
+  HeaderFilterLocationFilter &getHeaderFilterLocationFilter();
 
   /// Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter
   /// according to the diagnostic \p Location.
@@ -331,8 +327,7 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
   bool GetFixesFromNotes;
   bool EnableNolintBlocks;
   std::vector<ClangTidyError> Errors;
-  std::unique_ptr<llvm::Regex> HeaderFilter;
-  std::unique_ptr<llvm::Regex> ExcludeHeaderFilter;
+  std::unique_ptr<HeaderFilterLocationFilter> HeaderFilterLocation;
   bool LastErrorRelatesToUserCode = false;
   bool LastErrorPassesLineFilter = false;
   bool LastErrorWasIgnored = false;
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 0a0f392346f6d..291b70225eed3 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -242,6 +242,8 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
+    IO.mapOptional("ExperimentalHeaderFilterMatching",
+                   Options.ExperimentalHeaderFilterMatching);
     IO.mapOptional("CustomChecks", Options.CustomChecks);
   }
 };
@@ -259,6 +261,7 @@ ClangTidyOptions ClangTidyOptions::getDefaults() {
   Options.HeaderFilterRegex = ".*";
   Options.ExcludeHeaderFilterRegex = "";
   Options.SystemHeaders = false;
+  Options.ExperimentalHeaderFilterMatching = false;
   Options.FormatStyle = "none";
   Options.User = std::nullopt;
   Options.RemovedArgs = std::nullopt;
@@ -300,6 +303,8 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
   overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
   overrideValue(ExcludeHeaderFilterRegex, Other.ExcludeHeaderFilterRegex);
   overrideValue(SystemHeaders, Other.SystemHeaders);
+  overrideValue(ExperimentalHeaderFilterMatching,
+                Other.ExperimentalHeaderFilterMatching);
   overrideValue(FormatStyle, Other.FormatStyle);
   overrideValue(User, Other.User);
   overrideValue(UseColor, Other.UseColor);
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 73fdbabd5bdba..7c4efe3c1e03d 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -92,6 +92,13 @@ struct ClangTidyOptions {
   /// Output warnings from system headers matching \c HeaderFilterRegex.
   std::optional<bool> SystemHeaders;
 
+  /// When set, clang-tidy experimentally skips AST matching for declarations
+  /// in headers that do not match \c HeaderFilterRegex or that match
+  /// \c ExcludeHeaderFilterRegex. This is a semantic opt-in: checks that rely
+  /// on declarations outside the filtered headers can produce different
+  /// results, including false negatives or false positives.
+  std::optional<bool> ExperimentalHeaderFilterMatching;
+
   /// Format code around applied fixes with clang-format using this
   /// style.
   ///
diff --git a/clang-tools-extra/clang-tidy/HeaderFilterHelpers.h b/clang-tools-extra/clang-tidy/HeaderFilterHelpers.h
new file mode 100644
index 0000000000000..23fad8b9533ad
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/HeaderFilterHelpers.h
@@ -0,0 +1,64 @@
+//===--- HeaderFilterHelpers.h - clang-tidy header filtering ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HEADERFILTERHELPERS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HEADERFILTERHELPERS_H
+
+#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Regex.h"
+
+namespace clang::tidy {
+
+/// Evaluates clang-tidy's header filters for source locations and caches the
+/// per-file results for a translation unit.
+class HeaderFilterLocationFilter {
+public:
+  HeaderFilterLocationFilter(llvm::StringRef HeaderFilterRegex,
+                             llvm::StringRef ExcludeHeaderFilterRegex)
+      : HeaderFilter(HeaderFilterRegex),
+        ExcludeHeaderFilter(ExcludeHeaderFilterRegex) {}
+
+  /// Returns true when the location should be treated as in scope for
+  /// clang-tidy's header filters.
+  ///
+  /// Main-file locations are always in scope. Invalid locations and locations
+  /// without a FileEntry (such as command-line buffers) are also treated as in
+  /// scope to match clang-tidy's diagnostic filtering behavior.
+  bool shouldInclude(SourceLocation Location, const SourceManager &Sources) {
+    if (!Location.isValid())
+      return true;
+
+    if (Sources.isInMainFile(Location))
+      return true;
+
+    const FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
+    if (const auto It = Cache.find(FID); It != Cache.end())
+      return It->second;
+
+    bool Result = true;
+    if (const OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID)) {
+      const llvm::StringRef FileName = File->getName();
+      Result =
+          HeaderFilter.match(FileName) && !ExcludeHeaderFilter.match(FileName);
+    }
+
+    Cache[FID] = Result;
+    return Result;
+  }
+
+private:
+  llvm::Regex HeaderFilter;
+  llvm::Regex ExcludeHeaderFilter;
+  llvm::DenseMap<FileID, bool> Cache;
+};
+
+} // namespace clang::tidy
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HEADERFILTERHELPERS_H
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index f61e2f40ed03b..ac43dc4500aaf 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -63,6 +63,9 @@ Configuration files:
   CustomChecks                 - Array of user defined checks based on
                                  Clang-Query syntax.
   ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
+  ExperimentalHeaderFilterMatching
+                               - Same as
+                                 '--experimental-header-filter-matching'.
   ExtraArgs                    - Same as '--extra-arg'.
   ExtraArgsBefore              - Same as '--extra-arg-before'.
   FormatStyle                  - Same as '--format-style'.
@@ -95,6 +98,7 @@ Configuration files:
     HeaderFileExtensions:         ['', 'h','hh','hpp','hxx']
     ImplementationFileExtensions: ['c','cc','cpp','cxx']
     HeaderFilterRegex:            '.*'
+    ExperimentalHeaderFilterMatching: false
     FormatStyle:                  none
     InheritParentConfig:          true
     User:                         user
@@ -158,6 +162,25 @@ option in .clang-tidy file, if any.
                                                 cl::init(""),
                                                 cl::cat(ClangTidyCategory));
 
+static cl::opt<bool> ExperimentalHeaderFilterMatching(
+    "experimental-header-filter-matching", desc(R"(
+When enabled, clang-tidy experimentally skips
+AST matching for declarations in headers that
+do not match -header-filter or that match
+-exclude-header-filter.
+This can improve performance for narrow
+header-filter runs, but checks that rely on
+declarations outside the filtered headers can
+produce different results, including false
+negatives or false positives.
+This is an opt-in semantic mode, not only a
+diagnostic filtering mode.
+This option overrides the
+'ExperimentalHeaderFilterMatching' option in
+.clang-tidy file, if any.
+)"),
+    cl::init(false), cl::cat(ClangTidyCategory));
+
 static cl::opt<bool> SystemHeaders("system-headers", desc(R"(
 Display the errors from system headers.
 This option overrides the 'SystemHeaders' option
@@ -412,6 +435,8 @@ createOptionsProvider(llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) {
   DefaultOptions.HeaderFilterRegex = HeaderFilter;
   DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
   DefaultOptions.SystemHeaders = SystemHeaders;
+  DefaultOptions.ExperimentalHeaderFilterMatching =
+      ExperimentalHeaderFilterMatching;
   DefaultOptions.FormatStyle = FormatStyle;
   DefaultOptions.User = llvm::sys::Process::GetEnv("USER");
   // USERNAME is used on Windows.
@@ -429,6 +454,9 @@ createOptionsProvider(llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) {
     OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
   if (SystemHeaders.getNumOccurrences() > 0)
     OverrideOptions.SystemHeaders = SystemHeaders;
+  if (ExperimentalHeaderFilterMatching.getNumOccurrences() > 0)
+    OverrideOptions.ExperimentalHeaderFilterMatching =
+        ExperimentalHeaderFilterMatching;
   if (FormatStyle.getNumOccurrences() > 0)
     OverrideOptions.FormatStyle = FormatStyle;
   if (UseColor.getNumOccurrences() > 0)
diff --git a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
index f827ef492f01c..c1445ea40ab35 100755
--- a/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
+++ b/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
@@ -94,6 +94,7 @@ def get_tidy_invocation(
     tmpdir: Optional[str],
     build_path: str,
     header_filter: Optional[str],
+    experimental_header_filter_matching: Optional[bool],
     allow_enabling_alpha_checkers: bool,
     extra_arg: List[str],
     extra_arg_before: List[str],
@@ -117,6 +118,11 @@ def get_tidy_invocation(
         start.append(f"--exclude-header-filter={exclude_header_filter}")
     if header_filter is not None:
         start.append(f"-header-filter={header_filter}")
+    if experimental_header_filter_matching is not None:
+        if experimental_header_filter_matching:
+            start.append("--experimental-header-filter-matching")
+        else:
+            start.append("--experimental-header-filter-matching=false")
     if line_filter is not None:
         start.append(f"-line-filter={line_filter}")
     if use_color is not None:
@@ -378,6 +384,7 @@ async def run_tidy(
         tmpdir,
         build_path,
         args.header_filter,
+        args.experimental_header_filter_matching,
         args.allow_enabling_alpha_checkers,
         args.extra_arg,
         args.extra_arg_before,
@@ -478,6 +485,17 @@ async def main() -> None:
         "the main file of each translation unit are always "
         "displayed.",
     )
+    parser.add_argument(
+        "-experimental-header-filter-matching",
+        "--experimental-header-filter-matching",
+        type=strtobool,
+        nargs="?",
+        const=True,
+        default=None,
+        help="Restrict experimental AST matching to headers "
+        "that match -header-filter and do not match "
+        "-exclude-header-filter.",
+    )
     parser.add_argument(
         "-source-filter",
         default=None,
@@ -647,6 +665,7 @@ async def main() -> None:
             None,
             build_path,
             args.header_filter,
+            args.experimental_header_filter_matching,
             args.allow_enabling_alpha_checkers,
             args.extra_arg,
             args.extra_arg_before,
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 2419917778182..08ddec7087de8 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -111,6 +111,15 @@ Improvements to clang-tidy
 - Improved :program:`clang-tidy` ``-store-check-profile`` by generating valid
   JSON when the source file path contains characters that require JSON escaping.
 
+- Added an experimental :program:`clang-tidy`
+  ``--experimental-header-filter-matching`` option that skips AST matching for
+  declarations in headers that do not match ``-header-filter`` (or that match
+  ``-exclude-header-filter``). This can improve performance for narrow
+  header-filter runs, but it is an opt-in semantic mode: checks that need AST
+  visibility outside the filtered headers, such as checks that build cross-file
+  state, can produce different results including false negatives or false
+  positives.
+
 New checks
 ^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index db7f2deade9ca..d437f50f30d58 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -185,6 +185,22 @@ An overview of all the command-line options:
                                        Can be used together with -line-filter.
                                        This option overrides the 'ExcludeHeaderFilterRegex'
                                        option in .clang-tidy file, if any.
+    --experimental-header-filter-matching
+                                     - When enabled, clang-tidy experimentally
+                                       skips AST matching for declarations in
+                                       headers that do not match -header-filter
+                                       or that match -exclude-header-filter.
+                                       This can improve performance for narrow
+                                       header-filter runs, but checks that rely
+                                       on declarations outside the filtered
+                                       headers can produce different results,
+                                       including false negatives or false
+                                       positives.
+                                       This is an opt-in semantic mode, not
+                                       only a diagnostic filtering mode.
+                                       This option overrides the
+                                       'ExperimentalHeaderFilterMatching' option in
+                                       .clang-tidy file, if any.
     --experimental-custom-checks     - Enable experimental clang-query based
                                        custom checks.
                                        see https://clang...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/194370


More information about the cfe-commits mailing list