Skip to content

Commit 5ca68d5

Browse files
committed
[clang-tidy] Add -verify-config command line argument
Adds a `-verify-config` command line argument, that when specified will verify the Checks and CheckOptions fields in the config files: - A warning will be raised for any check that doesn't correspond to a registered check, a suggestion will also be emitted for close misses. - A warning will be raised for any check glob(containing *) that doesn't match any registered check. - A warning will be raised for any CheckOption that isn't read by any registered check, a suggestion will also be emitted for close misses. This can be useful if debuging why a certain check isn't enabled, or the options are being handled as you expect them to be. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D127446
1 parent 1ef5e6d commit 5ca68d5

File tree

8 files changed

+198
-5
lines changed

8 files changed

+198
-5
lines changed

clang-tools-extra/clang-tidy/ClangTidy.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,5 +623,40 @@ void exportReplacements(const llvm::StringRef MainFilePath,
623623
YAML << TUD;
624624
}
625625

626+
NamesAndOptions
627+
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
628+
NamesAndOptions Result;
629+
ClangTidyOptions Opts;
630+
Opts.Checks = "*";
631+
clang::tidy::ClangTidyContext Context(
632+
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
633+
AllowEnablingAnalyzerAlphaCheckers);
634+
ClangTidyCheckFactories Factories;
635+
for (const ClangTidyModuleRegistry::entry &Module :
636+
ClangTidyModuleRegistry::entries()) {
637+
Module.instantiate()->addCheckFactories(Factories);
638+
}
639+
640+
for (const auto &Factory : Factories)
641+
Result.Names.insert(Factory.getKey());
642+
643+
#if CLANG_TIDY_ENABLE_STATIC_ANALYZER
644+
SmallString<64> Buffer(AnalyzerCheckNamePrefix);
645+
size_t DefSize = Buffer.size();
646+
for (const auto &AnalyzerCheck : AnalyzerOptions::getRegisteredCheckers(
647+
AllowEnablingAnalyzerAlphaCheckers)) {
648+
Buffer.truncate(DefSize);
649+
Buffer.append(AnalyzerCheck);
650+
Result.Names.insert(Buffer);
651+
}
652+
#endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER
653+
654+
Context.setOptionsCollector(&Result.Options);
655+
for (const auto &Factory : Factories) {
656+
Factory.getValue()(Factory.getKey(), &Context);
657+
}
658+
659+
return Result;
660+
}
626661
} // namespace tidy
627662
} // namespace clang

clang-tools-extra/clang-tidy/ClangTidy.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "ClangTidyDiagnosticConsumer.h"
1313
#include "ClangTidyOptions.h"
14+
#include "llvm/ADT/StringSet.h"
1415
#include <memory>
1516
#include <vector>
1617

@@ -57,6 +58,14 @@ class ClangTidyASTConsumerFactory {
5758
std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
5859
bool AllowEnablingAnalyzerAlphaCheckers);
5960

61+
struct NamesAndOptions {
62+
llvm::StringSet<> Names;
63+
llvm::StringSet<> Options;
64+
};
65+
66+
NamesAndOptions
67+
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
68+
6069
/// Returns the effective check-specific options.
6170
///
6271
/// The method configures ClangTidy with the specified \p Options and collects

clang-tools-extra/clang-tidy/ClangTidyCheck.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,22 @@ ClangTidyCheck::OptionsView::OptionsView(
5353

5454
llvm::Optional<StringRef>
5555
ClangTidyCheck::OptionsView::get(StringRef LocalName) const {
56+
if (Context->getOptionsCollector())
57+
Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
5658
const auto &Iter = CheckOptions.find((NamePrefix + LocalName).str());
5759
if (Iter != CheckOptions.end())
5860
return StringRef(Iter->getValue().Value);
5961
return None;
6062
}
6163

6264
static ClangTidyOptions::OptionMap::const_iterator
63-
findPriorityOption(const ClangTidyOptions::OptionMap &Options, StringRef NamePrefix,
64-
StringRef LocalName) {
65+
findPriorityOption(const ClangTidyOptions::OptionMap &Options,
66+
StringRef NamePrefix, StringRef LocalName,
67+
llvm::StringSet<> *Collector) {
68+
if (Collector) {
69+
Collector->insert((NamePrefix + LocalName).str());
70+
Collector->insert(LocalName);
71+
}
6572
auto IterLocal = Options.find((NamePrefix + LocalName).str());
6673
auto IterGlobal = Options.find(LocalName);
6774
if (IterLocal == Options.end())
@@ -75,7 +82,8 @@ findPriorityOption(const ClangTidyOptions::OptionMap &Options, StringRef NamePre
7582

7683
llvm::Optional<StringRef>
7784
ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName) const {
78-
auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName);
85+
auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName,
86+
Context->getOptionsCollector());
7987
if (Iter != CheckOptions.end())
8088
return StringRef(Iter->getValue().Value);
8189
return None;
@@ -108,7 +116,8 @@ ClangTidyCheck::OptionsView::get<bool>(StringRef LocalName) const {
108116
template <>
109117
llvm::Optional<bool>
110118
ClangTidyCheck::OptionsView::getLocalOrGlobal<bool>(StringRef LocalName) const {
111-
auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName);
119+
auto Iter = findPriorityOption(CheckOptions, NamePrefix, LocalName,
120+
Context->getOptionsCollector());
112121
if (Iter != CheckOptions.end()) {
113122
if (auto Result = getAsBool(Iter->getValue().Value, Iter->getKey()))
114123
return Result;
@@ -139,8 +148,11 @@ void ClangTidyCheck::OptionsView::store<bool>(
139148
llvm::Optional<int64_t> ClangTidyCheck::OptionsView::getEnumInt(
140149
StringRef LocalName, ArrayRef<NameAndValue> Mapping, bool CheckGlobal,
141150
bool IgnoreCase) const {
151+
if (!CheckGlobal && Context->getOptionsCollector())
152+
Context->getOptionsCollector()->insert((NamePrefix + LocalName).str());
142153
auto Iter = CheckGlobal
143-
? findPriorityOption(CheckOptions, NamePrefix, LocalName)
154+
? findPriorityOption(CheckOptions, NamePrefix, LocalName,
155+
Context->getOptionsCollector())
144156
: CheckOptions.find((NamePrefix + LocalName).str());
145157
if (Iter == CheckOptions.end())
146158
return None;

clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "clang/Basic/Diagnostic.h"
1616
#include "clang/Tooling/Core/Diagnostic.h"
1717
#include "llvm/ADT/DenseMap.h"
18+
#include "llvm/ADT/StringSet.h"
1819
#include "llvm/Support/Regex.h"
1920

2021
namespace clang {
@@ -201,6 +202,11 @@ class ClangTidyContext {
201202
DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID)));
202203
}
203204

205+
void setOptionsCollector(llvm::StringSet<> *Collector) {
206+
OptionsCollector = Collector;
207+
}
208+
llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; }
209+
204210
private:
205211
// Writes to Stats.
206212
friend class ClangTidyDiagnosticConsumer;
@@ -230,6 +236,7 @@ class ClangTidyContext {
230236
bool SelfContainedDiags;
231237

232238
NoLintDirectiveHandler NoLintHandler;
239+
llvm::StringSet<> *OptionsCollector = nullptr;
233240
};
234241

235242
/// Gets the Fix attached to \p Diagnostic.

clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "../ClangTidyForceLinker.h"
2020
#include "../GlobList.h"
2121
#include "clang/Tooling/CommonOptionsParser.h"
22+
#include "llvm/ADT/StringSet.h"
2223
#include "llvm/Support/InitLLVM.h"
2324
#include "llvm/Support/PluginLoader.h"
2425
#include "llvm/Support/Process.h"
@@ -257,6 +258,12 @@ This option overrides the 'UseColor' option in
257258
)"),
258259
cl::init(false), cl::cat(ClangTidyCategory));
259260

261+
static cl::opt<bool> VerifyConfig("verify-config", cl::desc(R"(
262+
Check the config files to ensure each check and
263+
option is recognized.
264+
)"),
265+
cl::init(false), cl::cat(ClangTidyCategory));
266+
260267
namespace clang {
261268
namespace tidy {
262269

@@ -385,6 +392,74 @@ getVfsFromFile(const std::string &OverlayFile,
385392
return FS;
386393
}
387394

395+
static StringRef closest(StringRef Value, const StringSet<> &Allowed) {
396+
unsigned MaxEdit = 5U;
397+
StringRef Closest;
398+
for (auto Item : Allowed.keys()) {
399+
unsigned Cur = Value.edit_distance_insensitive(Item, true, MaxEdit);
400+
if (Cur < MaxEdit) {
401+
Closest = Item;
402+
MaxEdit = Cur;
403+
}
404+
}
405+
return Closest;
406+
}
407+
408+
static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n";
409+
410+
static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob,
411+
StringRef Source) {
412+
llvm::StringRef Cur, Rest;
413+
bool AnyInvalid = false;
414+
for (std::tie(Cur, Rest) = CheckGlob.split(',');
415+
!(Cur.empty() && Rest.empty()); std::tie(Cur, Rest) = Rest.split(',')) {
416+
Cur = Cur.trim();
417+
if (Cur.empty())
418+
continue;
419+
Cur.consume_front("-");
420+
if (Cur.startswith("clang-diagnostic"))
421+
continue;
422+
if (Cur.contains('*')) {
423+
SmallString<128> RegexText("^");
424+
StringRef MetaChars("()^$|*+?.[]\\{}");
425+
for (char C : Cur) {
426+
if (C == '*')
427+
RegexText.push_back('.');
428+
else if (MetaChars.contains(C))
429+
RegexText.push_back('\\');
430+
RegexText.push_back(C);
431+
}
432+
RegexText.push_back('$');
433+
llvm::Regex Glob(RegexText);
434+
std::string Error;
435+
if (!Glob.isValid(Error)) {
436+
AnyInvalid = true;
437+
llvm::WithColor::error(llvm::errs(), Source)
438+
<< "building check glob '" << Cur << "' " << Error << "'\n";
439+
continue;
440+
}
441+
if (llvm::none_of(AllChecks.keys(),
442+
[&Glob](StringRef S) { return Glob.match(S); })) {
443+
AnyInvalid = true;
444+
llvm::WithColor::warning(llvm::errs(), Source)
445+
<< "check glob '" << Cur << "' doesn't match any known check"
446+
<< VerifyConfigWarningEnd;
447+
}
448+
} else {
449+
if (AllChecks.contains(Cur))
450+
continue;
451+
AnyInvalid = true;
452+
llvm::raw_ostream &Output = llvm::WithColor::warning(llvm::errs(), Source)
453+
<< "unknown check '" << Cur << '\'';
454+
llvm::StringRef Closest = closest(Cur, AllChecks);
455+
if (!Closest.empty())
456+
Output << "; did you mean '" << Closest << '\'';
457+
Output << VerifyConfigWarningEnd;
458+
}
459+
}
460+
return AnyInvalid;
461+
}
462+
388463
int clangTidyMain(int argc, const char **argv) {
389464
llvm::InitLLVM X(argc, argv);
390465

@@ -478,6 +553,38 @@ int clangTidyMain(int argc, const char **argv) {
478553
return 0;
479554
}
480555

556+
if (VerifyConfig) {
557+
std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
558+
OptionsProvider->getRawOptions(FileName);
559+
NamesAndOptions Valid =
560+
getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers);
561+
bool AnyInvalid = false;
562+
for (const std::pair<ClangTidyOptions, std::string> &OptionWithSource :
563+
RawOptions) {
564+
const ClangTidyOptions &Opts = OptionWithSource.first;
565+
if (Opts.Checks)
566+
AnyInvalid |=
567+
verifyChecks(Valid.Names, *Opts.Checks, OptionWithSource.second);
568+
569+
for (auto Key : Opts.CheckOptions.keys()) {
570+
if (Valid.Options.contains(Key))
571+
continue;
572+
AnyInvalid = true;
573+
auto &Output =
574+
llvm::WithColor::warning(llvm::errs(), OptionWithSource.second)
575+
<< "unknown check option '" << Key << '\'';
576+
llvm::StringRef Closest = closest(Key, Valid.Options);
577+
if (!Closest.empty())
578+
Output << "; did you mean '" << Closest << '\'';
579+
Output << VerifyConfigWarningEnd;
580+
}
581+
}
582+
if (AnyInvalid)
583+
return 1;
584+
llvm::outs() << "No config errors detected.\n";
585+
return 0;
586+
}
587+
481588
if (EnabledChecks.empty()) {
482589
llvm::errs() << "Error: no checks enabled.\n";
483590
llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ Improvements to clang-tidy
110110
from suppressing diagnostics associated with macro arguments. This fixes
111111
`Issue 55134 <https://github.com/llvm/llvm-project/issues/55134>`_.
112112

113+
- Added an option -verify-config which will check the config file to ensure each
114+
`Checks` and `CheckOptions` entries are recognised.
115+
113116
New checks
114117
^^^^^^^^^^
115118

clang-tools-extra/docs/clang-tidy/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ An overview of all the command-line options:
244244
standard output supports colors.
245245
This option overrides the 'UseColor' option in
246246
.clang-tidy file, if any.
247+
--verify-config -
248+
Check the config files to ensure each check and
249+
option is recognized.
247250
--vfsoverlay=<filename> -
248251
Overlay the virtual filesystem described by file
249252
over the real file system.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: clang-tidy -verify-config --config='' | FileCheck %s -check-prefix=CHECK-VERIFY-OK
2+
// CHECK-VERIFY-OK: No config errors detected.
3+
4+
// RUN: not clang-tidy -verify-config \
5+
// RUN: --checks='-*,bad*glob,llvm*,llvm-includeorder,my-made-up-check' --config='{Checks: "readability-else-after-ret", \
6+
// RUN: CheckOptions: [{key: "IgnoreMacros", value: "true"}, \
7+
// RUN: {key: "StriceMode", value: "true"}, \
8+
// RUN: {key: modernize-lop-convert.UseCxx20ReverseRanges, value: true} \
9+
// RUN: ]}' 2>&1 | FileCheck %s \
10+
// RUN: -check-prefix=CHECK-VERIFY -implicit-check-not='{{warning|error}}:'
11+
12+
// CHECK-VERIFY-DAG: command-line option '-config': warning: unknown check 'readability-else-after-ret'; did you mean 'readability-else-after-return' [-verify-config]
13+
// CHECK-VERIFY-DAG: command-line option '-config': warning: unknown check option 'modernize-lop-convert.UseCxx20ReverseRanges'; did you mean 'modernize-loop-convert.UseCxx20ReverseRanges' [-verify-config]
14+
// CHECK-VERIFY-DAG: command-line option '-config': warning: unknown check option 'StriceMode'; did you mean 'StrictMode' [-verify-config]
15+
// CHECK-VERIFY: command-line option '-checks': warning: check glob 'bad*glob' doesn't match any known check [-verify-config]
16+
// CHECK-VERIFY: command-line option '-checks': warning: unknown check 'llvm-includeorder'; did you mean 'llvm-include-order' [-verify-config]
17+
// CHECK-VERIFY: command-line option '-checks': warning: unknown check 'my-made-up-check' [-verify-config]

0 commit comments

Comments
 (0)