[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