Add option to exclude headers from clang-tidy analysis (#91400)

This is a renewed attempt to land @toddlipcon's D34654. The comments on
that patch indicate a broad desire for some ability to ignore headers.

After considering various options, including migrating to std::regex, I
believe this is the best path forward. It's intuitive to have separate
regexes for including headers versus excluding them, and this approach
has the added benefit of being completely opt-in. No existing configs
will break, regardless of existing HeaderFilterRegex values.

This functionality is useful for improving performance when analyzing a
targeted subset of code, as well as in cases where some collection of
headers cannot be modified (third party source, for example).
This commit is contained in:
Justin Cady 2024-05-14 08:48:04 -04:00 committed by GitHub
parent 415616daa0
commit cc54129b98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 207 additions and 126 deletions

View File

@ -311,7 +311,18 @@ ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
: Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
RemoveIncompatibleErrors(RemoveIncompatibleErrors),
GetFixesFromNotes(GetFixesFromNotes),
EnableNolintBlocks(EnableNolintBlocks) {}
EnableNolintBlocks(EnableNolintBlocks) {
if (Context.getOptions().HeaderFilterRegex &&
!Context.getOptions().HeaderFilterRegex->empty())
HeaderFilter =
std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
if (Context.getOptions().ExcludeHeaderFilterRegex &&
!Context.getOptions().ExcludeHeaderFilterRegex->empty())
ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
*Context.getOptions().ExcludeHeaderFilterRegex);
}
void ClangTidyDiagnosticConsumer::finalizeLastError() {
if (!Errors.empty()) {
@ -562,22 +573,17 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
}
StringRef FileName(File->getName());
LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
Sources.isInMainFile(Location) ||
getHeaderFilter()->match(FileName);
LastErrorRelatesToUserCode =
LastErrorRelatesToUserCode || Sources.isInMainFile(Location) ||
(HeaderFilter &&
(HeaderFilter->match(FileName) &&
!(ExcludeHeaderFilter && ExcludeHeaderFilter->match(FileName))));
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);
return HeaderFilter.get();
}
void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
// Each error is modelled as the set of intervals in which it applies
// replacements. To detect overlapping replacements, we use a sweep line

View File

@ -313,6 +313,7 @@ private:
bool EnableNolintBlocks;
std::vector<ClangTidyError> Errors;
std::unique_ptr<llvm::Regex> HeaderFilter;
std::unique_ptr<llvm::Regex> ExcludeHeaderFilter;
bool LastErrorRelatesToUserCode = false;
bool LastErrorPassesLineFilter = false;
bool LastErrorWasIgnored = false;

View File

@ -170,6 +170,8 @@ template <> struct MappingTraits<ClangTidyOptions> {
IO.mapOptional("ImplementationFileExtensions",
Options.ImplementationFileExtensions);
IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
IO.mapOptional("ExcludeHeaderFilterRegex",
Options.ExcludeHeaderFilterRegex);
IO.mapOptional("FormatStyle", Options.FormatStyle);
IO.mapOptional("User", Options.User);
IO.mapOptional("CheckOptions", Options.CheckOptions);
@ -191,7 +193,8 @@ ClangTidyOptions ClangTidyOptions::getDefaults() {
Options.WarningsAsErrors = "";
Options.HeaderFileExtensions = {"", "h", "hh", "hpp", "hxx"};
Options.ImplementationFileExtensions = {"c", "cc", "cpp", "cxx"};
Options.HeaderFilterRegex = "";
Options.HeaderFilterRegex = std::nullopt;
Options.ExcludeHeaderFilterRegex = std::nullopt;
Options.SystemHeaders = false;
Options.FormatStyle = "none";
Options.User = std::nullopt;
@ -231,6 +234,7 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
overrideValue(ImplementationFileExtensions,
Other.ImplementationFileExtensions);
overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
overrideValue(ExcludeHeaderFilterRegex, Other.ExcludeHeaderFilterRegex);
overrideValue(SystemHeaders, Other.SystemHeaders);
overrideValue(FormatStyle, Other.FormatStyle);
overrideValue(User, Other.User);

View File

@ -83,6 +83,10 @@ struct ClangTidyOptions {
/// main files will always be displayed.
std::optional<std::string> HeaderFilterRegex;
/// \brief Exclude warnings from headers matching this filter, even if they
/// match \c HeaderFilterRegex.
std::optional<std::string> ExcludeHeaderFilterRegex;
/// Output warnings from system headers matching \c HeaderFilterRegex.
std::optional<bool> SystemHeaders;

View File

@ -53,6 +53,7 @@ Configuration files:
Checks - Same as '--checks'. Additionally, the list of
globs can be specified as a list instead of a
string.
ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'.
ExtraArgs - Same as '--extra-args'.
ExtraArgsBefore - Same as '--extra-args-before'.
FormatStyle - Same as '--format-style'.
@ -132,6 +133,20 @@ option in .clang-tidy file, if any.
cl::init(""),
cl::cat(ClangTidyCategory));
static cl::opt<std::string> ExcludeHeaderFilter("exclude-header-filter",
desc(R"(
Regular expression matching the names of the
headers to exclude diagnostics from. Diagnostics
from the main file of each translation unit are
always displayed.
Must be used together with --header-filter.
Can be used together with -line-filter.
This option overrides the 'ExcludeHeaderFilterRegex'
option in .clang-tidy file, if any.
)"),
cl::init(""),
cl::cat(ClangTidyCategory));
static cl::opt<bool> SystemHeaders("system-headers", desc(R"(
Display the errors from system headers.
This option overrides the 'SystemHeaders' option
@ -353,6 +368,7 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
DefaultOptions.Checks = DefaultChecks;
DefaultOptions.WarningsAsErrors = "";
DefaultOptions.HeaderFilterRegex = HeaderFilter;
DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
DefaultOptions.SystemHeaders = SystemHeaders;
DefaultOptions.FormatStyle = FormatStyle;
DefaultOptions.User = llvm::sys::Process::GetEnv("USER");
@ -367,6 +383,8 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
OverrideOptions.WarningsAsErrors = WarningsAsErrors;
if (HeaderFilter.getNumOccurrences() > 0)
OverrideOptions.HeaderFilterRegex = HeaderFilter;
if (ExcludeHeaderFilter.getNumOccurrences() > 0)
OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
if (SystemHeaders.getNumOccurrences() > 0)
OverrideOptions.SystemHeaders = SystemHeaders;
if (FormatStyle.getNumOccurrences() > 0)

View File

@ -106,11 +106,14 @@ def get_tidy_invocation(
use_color,
plugins,
warnings_as_errors,
exclude_header_filter,
):
"""Gets a command line for clang-tidy."""
start = [clang_tidy_binary]
if allow_enabling_alpha_checkers:
start.append("-allow-enabling-analyzer-alpha-checkers")
if exclude_header_filter is not None:
start.append("--exclude-header-filter=" + exclude_header_filter)
if header_filter is not None:
start.append("-header-filter=" + header_filter)
if line_filter is not None:
@ -228,6 +231,7 @@ def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, failed_fi
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
)
proc = subprocess.Popen(
@ -292,6 +296,14 @@ def main():
"-config option after reading specified config file. "
"Use either -config-file or -config, not both.",
)
parser.add_argument(
"-exclude-header-filter",
default=None,
help="Regular expression matching the names of the "
"headers to exclude diagnostics from. Diagnostics from "
"the main file of each translation unit are always "
"displayed.",
)
parser.add_argument(
"-header-filter",
default=None,
@ -450,6 +462,7 @@ def main():
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
)
invocation.append("-list-checks")
invocation.append("-")

View File

@ -115,6 +115,9 @@ Improvements to clang-tidy
- Fixed `--verify-config` option not properly parsing checks when using the
literal operator in the `.clang-tidy` config.
- Added argument `--exclude-header-filter` and config option `ExcludeHeaderFilterRegex`
to exclude headers from analysis via a RegEx.
New checks
^^^^^^^^^^

View File

@ -157,6 +157,14 @@ An overview of all the command-line options:
to detect macro definitions within modules. This
feature may cause performance and parsing issues
and is therefore considered experimental.
--exclude-header-filter=<string> - Regular expression matching the names of the
headers to exclude diagnostics from. Diagnostics
from the main file of each translation unit are
always displayed.
Must be used together with --header-filter.
Can be used together with -line-filter.
This option overrides the 'ExcludeHeaderFilterRegex'
option in .clang-tidy file, if any.
--explain-config - For each enabled check explains, where it is
enabled, i.e. in clang-tidy binary, command
line or a specific configuration file.
@ -269,6 +277,7 @@ An overview of all the command-line options:
Checks - Same as '--checks'. Additionally, the list of
globs can be specified as a list instead of a
string.
ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'.
ExtraArgs - Same as '--extra-args'.
ExtraArgsBefore - Same as '--extra-args-before'.
FormatStyle - Same as '--format-style'.

View File

@ -1,2 +1,3 @@
Checks: 'from-parent'
HeaderFilterRegex: 'parent'
ExcludeHeaderFilterRegex: 'exc-parent'

View File

@ -1,2 +1,3 @@
Checks: 'from-child1'
HeaderFilterRegex: 'child1'
ExcludeHeaderFilterRegex: 'exc-child1'

View File

@ -1,3 +1,4 @@
InheritParentConfig: true
Checks: 'from-child3'
HeaderFilterRegex: 'child3'
ExcludeHeaderFilterRegex: 'exc-child3'

View File

@ -1,18 +1,23 @@
// RUN: clang-tidy -dump-config %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=CHECK-BASE
// CHECK-BASE: Checks: {{.*}}from-parent
// CHECK-BASE: HeaderFilterRegex: parent
// CHECK-BASE: ExcludeHeaderFilterRegex: exc-parent
// RUN: clang-tidy -dump-config %S/Inputs/config-files/1/- -- | FileCheck %s -check-prefix=CHECK-CHILD1
// CHECK-CHILD1: Checks: {{.*}}from-child1
// CHECK-CHILD1: HeaderFilterRegex: child1
// CHECK-CHILD1: ExcludeHeaderFilterRegex: exc-child1
// RUN: clang-tidy -dump-config %S/Inputs/config-files/2/- -- | FileCheck %s -check-prefix=CHECK-CHILD2
// CHECK-CHILD2: Checks: {{.*}}from-parent
// CHECK-CHILD2: HeaderFilterRegex: parent
// CHECK-CHILD2: ExcludeHeaderFilterRegex: exc-parent
// RUN: clang-tidy -dump-config %S/Inputs/config-files/3/- -- | FileCheck %s -check-prefix=CHECK-CHILD3
// CHECK-CHILD3: Checks: {{.*}}from-parent,from-child3
// CHECK-CHILD3: HeaderFilterRegex: child3
// RUN: clang-tidy -dump-config -checks='from-command-line' -header-filter='from command line' %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=CHECK-COMMAND-LINE
// CHECK-CHILD3: ExcludeHeaderFilterRegex: exc-child3
// RUN: clang-tidy -dump-config -checks='from-command-line' -header-filter='from command line' -exclude-header-filter='from_command_line' %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=CHECK-COMMAND-LINE
// CHECK-COMMAND-LINE: Checks: {{.*}}from-parent,from-command-line
// CHECK-COMMAND-LINE: HeaderFilterRegex: from command line
// CHECK-COMMAND-LINE: ExcludeHeaderFilterRegex: from_command_line
// For this test we have to use names of the real checks because otherwise values are ignored.
// Running with the old key: <Key>, value: <value> CheckOptions
@ -68,3 +73,11 @@
// Dumped config does not overflow for unsigned options
// RUN: clang-tidy --dump-config %S/Inputs/config-files/5/- -- | FileCheck %s -check-prefix=CHECK-OVERFLOW
// CHECK-OVERFLOW: misc-throw-by-value-catch-by-reference.MaxSize: '1152921504606846976'
// RUN: clang-tidy -dump-config -checks='readability-function-size' -header-filter='foo/*' -exclude-header-filter='bar*' %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=CHECK-EXCLUDE-HEADERS
// CHECK-EXCLUDE-HEADERS: HeaderFilterRegex: 'foo/*'
// CHECK-EXCLUDE-HEADERS: ExcludeHeaderFilterRegex: 'bar*'
// RUN: clang-tidy -dump-config -checks='readability-function-size' -header-filter='' -exclude-header-filter='' %S/Inputs/config-files/- -- | FileCheck %s -check-prefix=EMPTY-CHECK-EXCLUDE-HEADERS
// EMPTY-CHECK-EXCLUDE-HEADERS: HeaderFilterRegex: ''
// EMPTY-CHECK-EXCLUDE-HEADERS: ExcludeHeaderFilterRegex: ''

View File

@ -11,6 +11,7 @@
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -system-headers -quiet %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK4-QUIET %s
// RUN: clang-tidy -checks='-*,cppcoreguidelines-pro-type-cstyle-cast' -header-filter='.*' -system-headers %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK5 %s
// RUN: clang-tidy -checks='-*,cppcoreguidelines-pro-type-cstyle-cast' -header-filter='.*' %s -- -I %S/Inputs/file-filter/system/.. -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK5-NO-SYSTEM-HEADERS %s
// RUN: clang-tidy -checks='-*,google-explicit-constructor' -header-filter='.*' -exclude-header-filter='header1\.h' %s -- -I %S/Inputs/file-filter/ -isystem %S/Inputs/file-filter/system 2>&1 | FileCheck --check-prefix=CHECK6 %s
#include "header1.h"
// CHECK-NOT: warning:
@ -21,6 +22,7 @@
// CHECK3-QUIET-NOT: warning:
// CHECK4: header1.h:1:12: warning: single-argument constructors
// CHECK4-QUIET: header1.h:1:12: warning: single-argument constructors
// CHECK6-NOT: warning:
#include "header2.h"
// CHECK-NOT: warning:
@ -31,6 +33,7 @@
// CHECK3-QUIET: header2.h:1:12: warning: single-argument constructors
// CHECK4: header2.h:1:12: warning: single-argument constructors
// CHECK4-QUIET: header2.h:1:12: warning: single-argument constructors
// CHECK6: header2.h:1:12: warning: single-argument constructors
#include <system-header.h>
// CHECK-NOT: warning:
@ -41,6 +44,7 @@
// CHECK3-QUIET-NOT: warning:
// CHECK4: system-header.h:1:12: warning: single-argument constructors
// CHECK4-QUIET: system-header.h:1:12: warning: single-argument constructors
// CHECK6-NOT: warning:
class A { A(int); };
// CHECK: :[[@LINE-1]]:11: warning: single-argument constructors
@ -51,6 +55,7 @@ class A { A(int); };
// CHECK3-QUIET: :[[@LINE-6]]:11: warning: single-argument constructors
// CHECK4: :[[@LINE-7]]:11: warning: single-argument constructors
// CHECK4-QUIET: :[[@LINE-8]]:11: warning: single-argument constructors
// CHECK6: :[[@LINE-9]]:11: warning: single-argument constructors
// CHECK-NOT: warning:
// CHECK-QUIET-NOT: warning:
@ -73,6 +78,8 @@ class A { A(int); };
// CHECK4-NOT: Suppressed {{.*}} warnings
// CHECK4-NOT: Use -header-filter=.* {{.*}}
// CHECK4-QUIET-NOT: Suppressed
// CHECK6: Suppressed 2 warnings (2 in non-user code)
// CHECK6: Use -header-filter=.* {{.*}}
int x = 123;
auto x_ptr = TO_FLOAT_PTR(&x);