Skip to content

Commit d05b7f1

Browse files
HerrCai0907DeNiCoNvbvictor
authored
[clang-tidy] support query based custom check (llvm#131804)
## summary ### Design for Compatibility For new field design 1. we must make sure the required fields do not change in quiet long time. 2. we should tolerant the unknown optional field (do not exist now) in the future. For large project integration (3rd party) 1. For config itself, since we can tolerant the unknown optional fields and required fields should not change, the user can decide whether to use custom check from third-party. 2.For clang-query, if there are some break change, since the query will be parsed at check time, the user can disable the check and it will not damage the whole clang-tidy ---- Inherit from llvm#123734 RFC: https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331 this patch introduce query based custom check by `CustomChecks` options. The major improvement compared with llvm#123734: 1. don't need to define config yaml file in command line 4. all behavior including `InheritFromParantConfig` is same as other config. 6. change configuration schema from KV structured to List structured to make extend new function easier. 7. Split bind string and diag message to two field to give more freedom to design query. example: ```yaml Checks: -*,custom-call-main-function CustomChecks: - Name: call-main-function Query: | match callExpr( callee( functionDecl(isMain()).bind("fn") ) ).bind("callee") Diagnostic: - BindName: fn Message: main function. Level: Note - BindName: callee Message: call to main function. Level: Warning ``` ```c++ int main(); // note: main function. void bar() { main(); // warning: call to main function. [custom-call-main-function] } ``` --- To make this PR don't do too much things that hard to review, here are some possible features not included in this PR - support language - support traverse - easier used template string in diagnostics message --------- Co-authored-by: DeNiCoN <[email protected]> Co-authored-by: Baranov Victor <[email protected]>
1 parent 2cc8d27 commit d05b7f1

35 files changed

+705
-28
lines changed

clang-tools-extra/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ include(GNUInstallDirs)
55

66
option(CLANG_TIDY_ENABLE_STATIC_ANALYZER
77
"Include static analyzer checks in clang-tidy" ON)
8+
option(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
9+
"Enable query-based custom checks in clang-tidy" ON)
810

911
if(CLANG_INCLUDE_TESTS)
1012
umbrella_lit_testsuite_begin(check-clang-tools)

clang-tools-extra/clang-tidy/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ add_subdirectory(bugprone)
5858
add_subdirectory(cert)
5959
add_subdirectory(concurrency)
6060
add_subdirectory(cppcoreguidelines)
61+
add_subdirectory(custom)
6162
add_subdirectory(darwin)
6263
add_subdirectory(fuchsia)
6364
add_subdirectory(google)
@@ -101,6 +102,10 @@ set(ALL_CLANG_TIDY_CHECKS
101102
clangTidyReadabilityModule
102103
clangTidyZirconModule
103104
)
105+
106+
if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
107+
list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyCustomModule)
108+
endif()
104109
if(CLANG_TIDY_ENABLE_STATIC_ANALYZER)
105110
list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyMPIModule)
106111
endif()

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
5353

5454
namespace clang::tidy {
5555

56+
namespace custom {
57+
extern void registerCustomChecks(const ClangTidyOptions &O,
58+
ClangTidyCheckFactories &Factories);
59+
} // namespace custom
60+
5661
namespace {
5762
#if CLANG_TIDY_ENABLE_STATIC_ANALYZER
5863
#define ANALYZER_CHECK_NAME_PREFIX "clang-analyzer-"
@@ -342,6 +347,10 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
342347
IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
343348
: Context(Context), OverlayFS(std::move(OverlayFS)),
344349
CheckFactories(new ClangTidyCheckFactories) {
350+
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
351+
if (Context.canExperimentalCustomChecks())
352+
custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
353+
#endif
345354
for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
346355
std::unique_ptr<ClangTidyModule> Module = E.instantiate();
347356
Module->addCheckFactories(*CheckFactories);
@@ -411,7 +420,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
411420
.getCurrentWorkingDirectory();
412421
if (WorkingDir)
413422
Context.setCurrentBuildDirectory(WorkingDir.get());
414-
423+
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
424+
if (Context.canExperimentalCustomChecks())
425+
custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
426+
#endif
415427
std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
416428
CheckFactories->createChecksForLanguage(&Context);
417429

@@ -497,13 +509,13 @@ ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() {
497509
return Options;
498510
}
499511

500-
std::vector<std::string>
501-
getCheckNames(const ClangTidyOptions &Options,
502-
bool AllowEnablingAnalyzerAlphaCheckers) {
512+
std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
513+
bool AllowEnablingAnalyzerAlphaCheckers,
514+
bool ExperimentalCustomChecks) {
503515
clang::tidy::ClangTidyContext Context(
504516
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
505517
Options),
506-
AllowEnablingAnalyzerAlphaCheckers);
518+
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
507519
ClangTidyASTConsumerFactory Factory(Context);
508520
return Factory.getCheckNames();
509521
}
@@ -524,11 +536,12 @@ void filterCheckOptions(ClangTidyOptions &Options,
524536

525537
ClangTidyOptions::OptionMap
526538
getCheckOptions(const ClangTidyOptions &Options,
527-
bool AllowEnablingAnalyzerAlphaCheckers) {
539+
bool AllowEnablingAnalyzerAlphaCheckers,
540+
bool ExperimentalCustomChecks) {
528541
clang::tidy::ClangTidyContext Context(
529542
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
530543
Options),
531-
AllowEnablingAnalyzerAlphaCheckers);
544+
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
532545
ClangTidyDiagnosticConsumer DiagConsumer(Context);
533546
auto DiagOpts = std::make_unique<DiagnosticOptions>();
534547
DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@@ -665,15 +678,19 @@ void exportReplacements(const llvm::StringRef MainFilePath,
665678
YAML << TUD;
666679
}
667680

668-
ChecksAndOptions
669-
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
681+
ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
682+
bool ExperimentalCustomChecks) {
670683
ChecksAndOptions Result;
671684
ClangTidyOptions Opts;
672685
Opts.Checks = "*";
673686
clang::tidy::ClangTidyContext Context(
674687
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
675-
AllowEnablingAnalyzerAlphaCheckers);
688+
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
676689
ClangTidyCheckFactories Factories;
690+
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
691+
if (ExperimentalCustomChecks)
692+
custom::registerCustomChecks(Context.getOptions(), Factories);
693+
#endif
677694
for (const ClangTidyModuleRegistry::entry &Module :
678695
ClangTidyModuleRegistry::entries()) {
679696
Module.instantiate()->addCheckFactories(Factories);

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,16 @@ class ClangTidyASTConsumerFactory {
5656
/// Fills the list of check names that are enabled when the provided
5757
/// filters are applied.
5858
std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
59-
bool AllowEnablingAnalyzerAlphaCheckers);
59+
bool AllowEnablingAnalyzerAlphaCheckers,
60+
bool ExperimentalCustomChecks);
6061

6162
struct ChecksAndOptions {
6263
llvm::StringSet<> Checks;
6364
llvm::StringSet<> Options;
6465
};
6566

66-
ChecksAndOptions
67-
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
67+
ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
68+
bool ExperimentalCustomChecks);
6869

6970
/// Returns the effective check-specific options.
7071
///
@@ -74,7 +75,8 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
7475
/// Options.
7576
ClangTidyOptions::OptionMap
7677
getCheckOptions(const ClangTidyOptions &Options,
77-
bool AllowEnablingAnalyzerAlphaCheckers);
78+
bool AllowEnablingAnalyzerAlphaCheckers,
79+
bool ExperimentalCustomChecks);
7880

7981
/// Filters CheckOptions in \p Options to only include options specified in
8082
/// the \p EnabledChecks which is a sorted vector.

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,12 @@ ClangTidyError::ClangTidyError(StringRef CheckName,
160160

161161
ClangTidyContext::ClangTidyContext(
162162
std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
163-
bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing)
163+
bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing,
164+
bool ExperimentalCustomChecks)
164165
: OptionsProvider(std::move(OptionsProvider)),
165-
166166
AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
167-
EnableModuleHeadersParsing(EnableModuleHeadersParsing) {
167+
EnableModuleHeadersParsing(EnableModuleHeadersParsing),
168+
ExperimentalCustomChecks(ExperimentalCustomChecks) {
168169
// Before the first translation unit we can get errors related to command-line
169170
// parsing, use dummy string for the file name in this case.
170171
setCurrentFile("dummy");

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "llvm/ADT/StringSet.h"
2020
#include "llvm/Support/Regex.h"
2121
#include <optional>
22+
#include <utility>
2223

2324
namespace clang {
2425

@@ -68,10 +69,13 @@ struct ClangTidyStats {
6869
/// \endcode
6970
class ClangTidyContext {
7071
public:
72+
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
73+
: ClangTidyContext(std::move(OptionsProvider), false, false, false) {}
7174
/// Initializes \c ClangTidyContext instance.
7275
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
73-
bool AllowEnablingAnalyzerAlphaCheckers = false,
74-
bool EnableModuleHeadersParsing = false);
76+
bool AllowEnablingAnalyzerAlphaCheckers,
77+
bool EnableModuleHeadersParsing,
78+
bool ExperimentalCustomChecks);
7579
/// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
7680
// FIXME: this is required initialization, and should be a constructor param.
7781
// Fix the context -> diag engine -> consumer -> context initialization cycle.
@@ -210,6 +214,10 @@ class ClangTidyContext {
210214
return EnableModuleHeadersParsing;
211215
}
212216

217+
// whether experimental custom checks can be enabled.
218+
// enabled with `--experimental-custom-checks`
219+
bool canExperimentalCustomChecks() const { return ExperimentalCustomChecks; }
220+
213221
void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
214222

215223
bool areDiagsSelfContained() const { return SelfContainedDiags; }
@@ -258,6 +266,7 @@ class ClangTidyContext {
258266

259267
bool AllowEnablingAnalyzerAlphaCheckers;
260268
bool EnableModuleHeadersParsing;
269+
bool ExperimentalCustomChecks;
261270

262271
bool SelfContainedDiags = false;
263272

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ extern volatile int CppCoreGuidelinesModuleAnchorSource;
5454
static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
5555
CppCoreGuidelinesModuleAnchorSource;
5656

57+
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
58+
// This anchor is used to force the linker to link the CustomModule.
59+
extern volatile int CustomModuleAnchorSource;
60+
static int LLVM_ATTRIBUTE_UNUSED CustomModuleAnchorDestination =
61+
CustomModuleAnchorSource;
62+
#endif
63+
5764
// This anchor is used to force the linker to link the DarwinModule.
5865
extern volatile int DarwinModuleAnchorSource;
5966
static int LLVM_ATTRIBUTE_UNUSED DarwinModuleAnchorDestination =

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class ClangTidyCheckFactories {
6262
});
6363
}
6464

65+
void eraseCheck(llvm::StringRef CheckName) { Factories.erase(CheckName); }
66+
6567
/// Create instances of checks that are enabled.
6668
std::vector<std::unique_ptr<ClangTidyCheck>>
6769
createChecks(ClangTidyContext *Context) const;

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
#include "ClangTidyOptions.h"
1010
#include "ClangTidyModuleRegistry.h"
11+
#include "clang/Basic/DiagnosticIDs.h"
1112
#include "clang/Basic/LLVM.h"
1213
#include "llvm/ADT/SmallString.h"
14+
#include "llvm/ADT/StringExtras.h"
1315
#include "llvm/Support/Debug.h"
1416
#include "llvm/Support/ErrorOr.h"
1517
#include "llvm/Support/MemoryBufferRef.h"
@@ -129,6 +131,51 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
129131
}
130132
}
131133

134+
namespace {
135+
struct MultiLineString {
136+
std::string &S;
137+
};
138+
} // namespace
139+
140+
template <> struct BlockScalarTraits<MultiLineString> {
141+
static void output(const MultiLineString &S, void *Ctxt, raw_ostream &OS) {
142+
OS << S.S;
143+
}
144+
static StringRef input(StringRef Str, void *Ctxt, MultiLineString &S) {
145+
S.S = Str;
146+
return "";
147+
}
148+
};
149+
150+
template <> struct ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {
151+
static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {
152+
IO.enumCase(Level, "Warning", clang::DiagnosticIDs::Level::Warning);
153+
IO.enumCase(Level, "Note", clang::DiagnosticIDs::Level::Note);
154+
}
155+
};
156+
template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckDiag> {
157+
static const bool flow = false;
158+
};
159+
template <> struct MappingTraits<ClangTidyOptions::CustomCheckDiag> {
160+
static void mapping(IO &IO, ClangTidyOptions::CustomCheckDiag &D) {
161+
IO.mapRequired("BindName", D.BindName);
162+
MultiLineString MLS{D.Message};
163+
IO.mapRequired("Message", MLS);
164+
IO.mapOptional("Level", D.Level);
165+
}
166+
};
167+
template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckValue> {
168+
static const bool flow = false;
169+
};
170+
template <> struct MappingTraits<ClangTidyOptions::CustomCheckValue> {
171+
static void mapping(IO &IO, ClangTidyOptions::CustomCheckValue &V) {
172+
IO.mapRequired("Name", V.Name);
173+
MultiLineString MLS{V.Query};
174+
IO.mapRequired("Query", MLS);
175+
IO.mapRequired("Diagnostic", V.Diags);
176+
}
177+
};
178+
132179
struct ChecksVariant {
133180
std::optional<std::string> AsString;
134181
std::optional<std::vector<std::string>> AsVector;
@@ -184,6 +231,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
184231
IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
185232
IO.mapOptional("UseColor", Options.UseColor);
186233
IO.mapOptional("SystemHeaders", Options.SystemHeaders);
234+
IO.mapOptional("CustomChecks", Options.CustomChecks);
187235
}
188236
};
189237

@@ -245,7 +293,8 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
245293
overrideValue(UseColor, Other.UseColor);
246294
mergeVectors(ExtraArgs, Other.ExtraArgs);
247295
mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
248-
296+
// FIXME: how to handle duplicate names check?
297+
mergeVectors(CustomChecks, Other.CustomChecks);
249298
for (const auto &KeyValue : Other.CheckOptions) {
250299
CheckOptions.insert_or_assign(
251300
KeyValue.getKey(),

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
1111

12+
#include "clang/Basic/DiagnosticIDs.h"
1213
#include "llvm/ADT/IntrusiveRefCntPtr.h"
1314
#include "llvm/ADT/SmallString.h"
1415
#include "llvm/ADT/StringMap.h"
@@ -129,6 +130,19 @@ struct ClangTidyOptions {
129130
/// Key-value mapping used to store check-specific options.
130131
OptionMap CheckOptions;
131132

133+
struct CustomCheckDiag {
134+
std::string BindName;
135+
std::string Message;
136+
std::optional<DiagnosticIDs::Level> Level;
137+
};
138+
struct CustomCheckValue {
139+
std::string Name;
140+
std::string Query;
141+
llvm::SmallVector<CustomCheckDiag> Diags;
142+
};
143+
using CustomCheckValueList = llvm::SmallVector<CustomCheckValue>;
144+
std::optional<CustomCheckValueList> CustomChecks;
145+
132146
using ArgList = std::vector<std::string>;
133147

134148
/// Add extra compilation arguments to the end of the list.

0 commit comments

Comments
 (0)