reapply "[clang-tidy] support query based custom check" (#159547)

reapply #131804 and #159289
Fixed cmake link issue.

---------

Co-authored-by: DeNiCoN <denicon1234@gmail.com>
Co-authored-by: Baranov Victor <bar.victor.2002@gmail.com>
This commit is contained in:
Congcong Cai 2025-09-19 21:47:57 +08:00 committed by GitHub
parent 2654b511fe
commit 584af2f89e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 746 additions and 36 deletions

View File

@ -5,6 +5,8 @@ include(GNUInstallDirs)
option(CLANG_TIDY_ENABLE_STATIC_ANALYZER
"Include static analyzer checks in clang-tidy" ON)
option(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
"Enable query-based custom checks in clang-tidy" ON)
if(CLANG_INCLUDE_TESTS)
umbrella_lit_testsuite_begin(check-clang-tools)

View File

@ -58,6 +58,7 @@ add_subdirectory(bugprone)
add_subdirectory(cert)
add_subdirectory(concurrency)
add_subdirectory(cppcoreguidelines)
add_subdirectory(custom)
add_subdirectory(darwin)
add_subdirectory(fuchsia)
add_subdirectory(google)
@ -101,6 +102,10 @@ set(ALL_CLANG_TIDY_CHECKS
clangTidyReadabilityModule
clangTidyZirconModule
)
if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyCustomModule)
endif()
if(CLANG_TIDY_ENABLE_STATIC_ANALYZER)
list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyMPIModule)
endif()

View File

@ -53,6 +53,13 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
namespace clang::tidy {
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
namespace custom {
void (*RegisterCustomChecks)(const ClangTidyOptions &O,
ClangTidyCheckFactories &Factories) = nullptr;
} // namespace custom
#endif
namespace {
#if CLANG_TIDY_ENABLE_STATIC_ANALYZER
#define ANALYZER_CHECK_NAME_PREFIX "clang-analyzer-"
@ -342,6 +349,10 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
: Context(Context), OverlayFS(std::move(OverlayFS)),
CheckFactories(new ClangTidyCheckFactories) {
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
if (Context.canExperimentalCustomChecks() && custom::RegisterCustomChecks)
custom::RegisterCustomChecks(Context.getOptions(), *CheckFactories);
#endif
for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
std::unique_ptr<ClangTidyModule> Module = E.instantiate();
Module->addCheckFactories(*CheckFactories);
@ -411,7 +422,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
.getCurrentWorkingDirectory();
if (WorkingDir)
Context.setCurrentBuildDirectory(WorkingDir.get());
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
if (Context.canExperimentalCustomChecks() && custom::RegisterCustomChecks)
custom::RegisterCustomChecks(Context.getOptions(), *CheckFactories);
#endif
std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
CheckFactories->createChecksForLanguage(&Context);
@ -497,13 +511,13 @@ ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() {
return Options;
}
std::vector<std::string>
getCheckNames(const ClangTidyOptions &Options,
bool AllowEnablingAnalyzerAlphaCheckers) {
std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks) {
clang::tidy::ClangTidyContext Context(
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
Options),
AllowEnablingAnalyzerAlphaCheckers);
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
ClangTidyASTConsumerFactory Factory(Context);
return Factory.getCheckNames();
}
@ -524,11 +538,12 @@ void filterCheckOptions(ClangTidyOptions &Options,
ClangTidyOptions::OptionMap
getCheckOptions(const ClangTidyOptions &Options,
bool AllowEnablingAnalyzerAlphaCheckers) {
bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks) {
clang::tidy::ClangTidyContext Context(
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
Options),
AllowEnablingAnalyzerAlphaCheckers);
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
ClangTidyDiagnosticConsumer DiagConsumer(Context);
auto DiagOpts = std::make_unique<DiagnosticOptions>();
DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@ -665,15 +680,19 @@ void exportReplacements(const llvm::StringRef MainFilePath,
YAML << TUD;
}
ChecksAndOptions
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks) {
ChecksAndOptions Result;
ClangTidyOptions Opts;
Opts.Checks = "*";
clang::tidy::ClangTidyContext Context(
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
AllowEnablingAnalyzerAlphaCheckers);
AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
ClangTidyCheckFactories Factories;
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
if (ExperimentalCustomChecks && custom::RegisterCustomChecks)
custom::RegisterCustomChecks(Context.getOptions(), Factories);
#endif
for (const ClangTidyModuleRegistry::entry &Module :
ClangTidyModuleRegistry::entries()) {
Module.instantiate()->addCheckFactories(Factories);

View File

@ -56,15 +56,16 @@ private:
/// Fills the list of check names that are enabled when the provided
/// filters are applied.
std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
bool AllowEnablingAnalyzerAlphaCheckers);
bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks);
struct ChecksAndOptions {
llvm::StringSet<> Checks;
llvm::StringSet<> Options;
};
ChecksAndOptions
getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks);
/// Returns the effective check-specific options.
///
@ -74,7 +75,8 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
/// Options.
ClangTidyOptions::OptionMap
getCheckOptions(const ClangTidyOptions &Options,
bool AllowEnablingAnalyzerAlphaCheckers);
bool AllowEnablingAnalyzerAlphaCheckers,
bool ExperimentalCustomChecks);
/// Filters CheckOptions in \p Options to only include options specified in
/// the \p EnabledChecks which is a sorted vector.
@ -125,6 +127,10 @@ void exportReplacements(StringRef MainFilePath,
const std::vector<ClangTidyError> &Errors,
raw_ostream &OS);
namespace custom {
extern void (*RegisterCustomChecks)(const ClangTidyOptions &O,
ClangTidyCheckFactories &Factories);
} // namespace custom
} // end namespace tidy
} // end namespace clang

View File

@ -160,11 +160,12 @@ ClangTidyError::ClangTidyError(StringRef CheckName,
ClangTidyContext::ClangTidyContext(
std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing)
bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing,
bool ExperimentalCustomChecks)
: OptionsProvider(std::move(OptionsProvider)),
AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
EnableModuleHeadersParsing(EnableModuleHeadersParsing) {
EnableModuleHeadersParsing(EnableModuleHeadersParsing),
ExperimentalCustomChecks(ExperimentalCustomChecks) {
// Before the first translation unit we can get errors related to command-line
// parsing, use dummy string for the file name in this case.
setCurrentFile("dummy");

View File

@ -19,6 +19,7 @@
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Regex.h"
#include <optional>
#include <utility>
namespace clang {
@ -68,10 +69,13 @@ struct ClangTidyStats {
/// \endcode
class ClangTidyContext {
public:
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
: ClangTidyContext(std::move(OptionsProvider), false, false, false) {}
/// Initializes \c ClangTidyContext instance.
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
bool AllowEnablingAnalyzerAlphaCheckers = false,
bool EnableModuleHeadersParsing = false);
bool AllowEnablingAnalyzerAlphaCheckers,
bool EnableModuleHeadersParsing,
bool ExperimentalCustomChecks);
/// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
// FIXME: this is required initialization, and should be a constructor param.
// Fix the context -> diag engine -> consumer -> context initialization cycle.
@ -210,6 +214,10 @@ public:
return EnableModuleHeadersParsing;
}
// whether experimental custom checks can be enabled.
// enabled with `--experimental-custom-checks`
bool canExperimentalCustomChecks() const { return ExperimentalCustomChecks; }
void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
bool areDiagsSelfContained() const { return SelfContainedDiags; }
@ -258,6 +266,7 @@ private:
bool AllowEnablingAnalyzerAlphaCheckers;
bool EnableModuleHeadersParsing;
bool ExperimentalCustomChecks;
bool SelfContainedDiags = false;

View File

@ -54,6 +54,13 @@ extern volatile int CppCoreGuidelinesModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
CppCoreGuidelinesModuleAnchorSource;
#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
// This anchor is used to force the linker to link the CustomModule.
extern volatile int CustomModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED CustomModuleAnchorDestination =
CustomModuleAnchorSource;
#endif
// This anchor is used to force the linker to link the DarwinModule.
extern volatile int DarwinModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED DarwinModuleAnchorDestination =

View File

@ -62,6 +62,8 @@ public:
});
}
void eraseCheck(llvm::StringRef CheckName) { Factories.erase(CheckName); }
/// Create instances of checks that are enabled.
std::vector<std::unique_ptr<ClangTidyCheck>>
createChecks(ClangTidyContext *Context) const;

View File

@ -8,8 +8,10 @@
#include "ClangTidyOptions.h"
#include "ClangTidyModuleRegistry.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/MemoryBufferRef.h"
@ -129,6 +131,51 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
}
}
namespace {
struct MultiLineString {
std::string &S;
};
} // namespace
template <> struct BlockScalarTraits<MultiLineString> {
static void output(const MultiLineString &S, void *Ctxt, raw_ostream &OS) {
OS << S.S;
}
static StringRef input(StringRef Str, void *Ctxt, MultiLineString &S) {
S.S = Str;
return "";
}
};
template <> struct ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {
static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {
IO.enumCase(Level, "Warning", clang::DiagnosticIDs::Level::Warning);
IO.enumCase(Level, "Note", clang::DiagnosticIDs::Level::Note);
}
};
template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckDiag> {
static const bool flow = false;
};
template <> struct MappingTraits<ClangTidyOptions::CustomCheckDiag> {
static void mapping(IO &IO, ClangTidyOptions::CustomCheckDiag &D) {
IO.mapRequired("BindName", D.BindName);
MultiLineString MLS{D.Message};
IO.mapRequired("Message", MLS);
IO.mapOptional("Level", D.Level);
}
};
template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckValue> {
static const bool flow = false;
};
template <> struct MappingTraits<ClangTidyOptions::CustomCheckValue> {
static void mapping(IO &IO, ClangTidyOptions::CustomCheckValue &V) {
IO.mapRequired("Name", V.Name);
MultiLineString MLS{V.Query};
IO.mapRequired("Query", MLS);
IO.mapRequired("Diagnostic", V.Diags);
}
};
struct ChecksVariant {
std::optional<std::string> AsString;
std::optional<std::vector<std::string>> AsVector;
@ -184,6 +231,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
IO.mapOptional("UseColor", Options.UseColor);
IO.mapOptional("SystemHeaders", Options.SystemHeaders);
IO.mapOptional("CustomChecks", Options.CustomChecks);
}
};
@ -245,7 +293,8 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
overrideValue(UseColor, Other.UseColor);
mergeVectors(ExtraArgs, Other.ExtraArgs);
mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
// FIXME: how to handle duplicate names check?
mergeVectors(CustomChecks, Other.CustomChecks);
for (const auto &KeyValue : Other.CheckOptions) {
CheckOptions.insert_or_assign(
KeyValue.getKey(),

View File

@ -9,6 +9,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
#include "clang/Basic/DiagnosticIDs.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
@ -129,6 +130,19 @@ struct ClangTidyOptions {
/// Key-value mapping used to store check-specific options.
OptionMap CheckOptions;
struct CustomCheckDiag {
std::string BindName;
std::string Message;
std::optional<DiagnosticIDs::Level> Level;
};
struct CustomCheckValue {
std::string Name;
std::string Query;
llvm::SmallVector<CustomCheckDiag> Diags;
};
using CustomCheckValueList = llvm::SmallVector<CustomCheckValue>;
std::optional<CustomCheckValueList> CustomChecks;
using ArgList = std::vector<std::string>;
/// Add extra compilation arguments to the end of the list.

View File

@ -6,5 +6,6 @@
#define CLANG_TIDY_CONFIG_H
#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER
#cmakedefine01 CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
#endif

View File

@ -0,0 +1,38 @@
if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
set(LLVM_LINK_COMPONENTS
FrontendOpenMP
support
)
add_clang_library(clangTidyCustomModule STATIC
CustomTidyModule.cpp
QueryCheck.cpp
LINK_LIBS
clangTidy
clangTidyBugproneModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyPerformanceModule
clangTidyReadabilityModule
clangTidyUtils
DEPENDS
omp_gen
ClangDriverOptions
)
clang_target_link_libraries(clangTidyCustomModule
PRIVATE
clangAnalysis
clangAST
clangASTMatchers
clangBasic
clangDynamicASTMatchers
clangFrontend
clangLex
clangQuery
clangSerialization
clangTooling
)
endif()

View File

@ -0,0 +1,59 @@
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "../ClangTidyOptions.h"
#include "QueryCheck.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include <cassert>
#include <memory>
namespace clang::tidy {
namespace custom {
class CustomModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {}
};
// We need to register the checks more flexibly than builtin modules. The checks
// will changed dynamically when switching to different source file.
extern void registerCustomChecks(const ClangTidyOptions &Options,
ClangTidyCheckFactories &Factories) {
static llvm::SmallSet<llvm::SmallString<32>, 8> CustomCheckNames{};
if (!Options.CustomChecks.has_value() || Options.CustomChecks->empty())
return;
for (const llvm::SmallString<32> &Name : CustomCheckNames)
Factories.eraseCheck(Name);
for (const ClangTidyOptions::CustomCheckValue &V :
Options.CustomChecks.value()) {
llvm::SmallString<32> Name = llvm::StringRef{"custom-" + V.Name};
Factories.registerCheckFactory(
// add custom- prefix to avoid conflicts with builtin checks
Name, [&V](llvm::StringRef Name, ClangTidyContext *Context) {
return std::make_unique<custom::QueryCheck>(Name, V, Context);
});
CustomCheckNames.insert(std::move(Name));
}
}
struct CustomChecksRegisterInitializer {
CustomChecksRegisterInitializer() noexcept {
RegisterCustomChecks = &custom::registerCustomChecks;
}
};
static CustomChecksRegisterInitializer Init{};
} // namespace custom
// Register the CustomTidyModule using this statically initialized variable.
static ClangTidyModuleRegistry::Add<custom::CustomModule>
X("custom-module", "Adds custom query lint checks.");
// This anchor is used to force the linker to link in the generated object file
// and thus register the AlteraModule.
volatile int CustomModuleAnchorSource = 0; // NOLINT (misc-use-internal-linkage)
} // namespace clang::tidy

View File

@ -0,0 +1,146 @@
//===--- QueryCheck.cpp - clang-tidy --------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "QueryCheck.h"
#include "../../clang-query/Query.h"
#include "../../clang-query/QueryParser.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <string>
using namespace clang::ast_matchers;
namespace clang::tidy::custom {
static void emitConfigurationDiag(ClangTidyContext *Context, StringRef Message,
StringRef CheckName) {
Context->configurationDiag("%0 in '%1'", DiagnosticIDs::Warning)
<< Message << CheckName;
}
static SmallVector<ast_matchers::dynamic::DynTypedMatcher>
parseQuery(const ClangTidyOptions::CustomCheckValue &V,
ClangTidyContext *Context) {
SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
clang::query::QuerySession QS({});
llvm::StringRef QueryStringRef{V.Query};
while (!QueryStringRef.empty()) {
query::QueryRef Q = query::QueryParser::parse(QueryStringRef, QS);
switch (Q->Kind) {
case query::QK_Match: {
const auto &MatchQuery = llvm::cast<query::MatchQuery>(*Q);
Matchers.push_back(MatchQuery.Matcher);
break;
}
case query::QK_Let: {
const auto &LetQuery = llvm::cast<query::LetQuery>(*Q);
LetQuery.run(llvm::errs(), QS);
break;
}
case query::QK_NoOp: {
const auto &NoOpQuery = llvm::cast<query::NoOpQuery>(*Q);
NoOpQuery.run(llvm::errs(), QS);
break;
}
case query::QK_Invalid: {
const auto &InvalidQuery = llvm::cast<query::InvalidQuery>(*Q);
emitConfigurationDiag(Context, InvalidQuery.ErrStr, V.Name);
return {};
}
// FIXME: TODO
case query::QK_File: {
emitConfigurationDiag(Context, "unsupported query kind 'File'", V.Name);
return {};
}
case query::QK_DisableOutputKind: {
emitConfigurationDiag(
Context, "unsupported query kind 'DisableOutputKind'", V.Name);
return {};
}
case query::QK_EnableOutputKind: {
emitConfigurationDiag(
Context, "unsupported query kind 'EnableOutputKind'", V.Name);
return {};
}
case query::QK_SetOutputKind: {
emitConfigurationDiag(Context, "unsupported query kind 'SetOutputKind'",
V.Name);
return {};
}
case query::QK_SetTraversalKind: {
emitConfigurationDiag(
Context, "unsupported query kind 'SetTraversalKind'", V.Name);
return {};
}
case query::QK_SetBool: {
emitConfigurationDiag(Context, "unsupported query kind 'SetBool'",
V.Name);
return {};
}
case query::QK_Help: {
emitConfigurationDiag(Context, "unsupported query kind 'Help'", V.Name);
return {};
}
case query::QK_Quit: {
emitConfigurationDiag(Context, "unsupported query kind 'Quit'", V.Name);
return {};
}
}
QueryStringRef = Q->RemainingContent;
}
return Matchers;
}
QueryCheck::QueryCheck(llvm::StringRef Name,
const ClangTidyOptions::CustomCheckValue &V,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {
for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) {
auto DiagnosticIdIt =
Diags
.try_emplace(D.Level.value_or(DiagnosticIDs::Warning),
llvm::StringMap<llvm::SmallVector<std::string>>{})
.first;
auto DiagMessageIt =
DiagnosticIdIt->getSecond()
.try_emplace(D.BindName, llvm::SmallVector<std::string>{})
.first;
DiagMessageIt->second.emplace_back(D.Message);
}
Matchers = parseQuery(V, Context);
}
void QueryCheck::registerMatchers(MatchFinder *Finder) {
for (const ast_matchers::dynamic::DynTypedMatcher &M : Matchers)
Finder->addDynamicMatcher(M, this);
}
void QueryCheck::check(const MatchFinder::MatchResult &Result) {
auto Emit = [this](const DiagMaps &DiagMaps, const std::string &BindName,
const DynTypedNode &Node, DiagnosticIDs::Level Level) {
DiagMaps::const_iterator DiagMapIt = DiagMaps.find(Level);
if (DiagMapIt == DiagMaps.end())
return;
const BindNameMapToDiagMessage &BindNameMap = DiagMapIt->second;
BindNameMapToDiagMessage::const_iterator BindNameMapIt =
BindNameMap.find(BindName);
if (BindNameMapIt == BindNameMap.end())
return;
for (const std::string &Message : BindNameMapIt->second)
diag(Node.getSourceRange().getBegin(), Message, Level);
};
for (const auto &[Name, Node] : Result.Nodes.getMap())
Emit(Diags, Name, Node, DiagnosticIDs::Warning);
// place Note last, otherwise it will not be emitted
for (const auto &[Name, Node] : Result.Nodes.getMap())
Emit(Diags, Name, Node, DiagnosticIDs::Note);
}
} // namespace clang::tidy::custom

View File

@ -0,0 +1,41 @@
//===--- QueryCheck.h - clang-tidy ------------------------------*- 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_CUSTOM_QUERYCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H
#include "../ClangTidyCheck.h"
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
namespace clang::tidy::custom {
/// Implement of Clang-Query based check.
/// Not directly visible to users.
class QueryCheck : public ClangTidyCheck {
public:
QueryCheck(llvm::StringRef Name, const ClangTidyOptions::CustomCheckValue &V,
ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers;
using BindNameMapToDiagMessage =
llvm::StringMap<llvm::SmallVector<std::string>>;
using DiagMaps =
llvm::DenseMap<DiagnosticIDs::Level, BindNameMapToDiagMessage>;
DiagMaps Diags;
};
} // namespace clang::tidy::custom
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H

View File

@ -60,6 +60,8 @@ Configuration files:
Checks - Same as '--checks'. Additionally, the list of
globs can be specified as a list instead of a
string.
CustomChecks - Array of user defined checks based on
Clang-Query syntax.
ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'.
ExtraArgs - Same as '--extra-arg'.
ExtraArgsBefore - Same as '--extra-arg-before'.
@ -344,6 +346,15 @@ all of the checks.
)"),
cl::init(false), cl::cat(ClangTidyCategory));
static cl::opt<bool> ExperimentalCustomChecks("experimental-custom-checks",
desc(R"(
Enable experimental clang-query based
custom checks.
see https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html.
)"),
cl::init(false),
cl::cat(ClangTidyCategory));
namespace clang::tidy {
static void printStats(const ClangTidyStats &Stats) {
@ -631,7 +642,8 @@ int clangTidyMain(int argc, const char **argv) {
ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
std::vector<std::string> EnabledChecks =
getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
ExperimentalCustomChecks);
if (ExplainConfig) {
// FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
@ -663,7 +675,8 @@ int clangTidyMain(int argc, const char **argv) {
if (DumpConfig) {
EffectiveOptions.CheckOptions =
getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
ExperimentalCustomChecks);
ClangTidyOptions OptionsToDump =
ClangTidyOptions::getDefaults().merge(EffectiveOptions, 0);
filterCheckOptions(OptionsToDump, EnabledChecks);
@ -674,8 +687,8 @@ int clangTidyMain(int argc, const char **argv) {
if (VerifyConfig) {
std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
OptionsProvider->getRawOptions(FileName);
ChecksAndOptions Valid =
getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers);
ChecksAndOptions Valid = getAllChecksAndOptions(
AllowEnablingAnalyzerAlphaCheckers, ExperimentalCustomChecks);
bool AnyInvalid = false;
for (const auto &[Opts, Source] : RawOptions) {
if (Opts.Checks)
@ -712,9 +725,9 @@ int clangTidyMain(int argc, const char **argv) {
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmParsers();
ClangTidyContext Context(std::move(OwningOptionsProvider),
AllowEnablingAnalyzerAlphaCheckers,
EnableModuleHeadersParsing);
ClangTidyContext Context(
std::move(OwningOptionsProvider), AllowEnablingAnalyzerAlphaCheckers,
EnableModuleHeadersParsing, ExperimentalCustomChecks);
std::vector<ClangTidyError> Errors =
runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
FixNotes, EnableCheckProfile, ProfilePrefix, Quiet);

View File

@ -127,6 +127,10 @@ Improvements to clang-tidy
by default, greatly improving performance. This behavior is disabled if the
`SystemHeaders` option is enabled.
- :program:`clang-tidy` now supports query based custom checks by `CustomChecks`
configuration option.
:doc:`Query Based Custom Check Document <clang-tidy/QueryBasedCustomChecks>`
- The :program:`run-clang-tidy.py` and :program:`clang-tidy-diff.py` scripts
now run checks in parallel by default using all available hardware threads.
Both scripts display the number of threads being used in their output.

View File

@ -33,6 +33,9 @@ If CMake is configured with ``CLANG_TIDY_ENABLE_STATIC_ANALYZER=NO``,
:program:`clang-tidy` will not be built with support for the
``clang-analyzer-*`` checks or the ``mpi-*`` checks.
If CMake is configured with ``CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS=NO``,
:program:`clang-tidy` will not be built with support for query based checks.
.. _AST Matchers: https://clang.llvm.org/docs/LibASTMatchers.html
.. _PPCallbacks: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html

View File

@ -0,0 +1,82 @@
====================================
Query Based Custom Clang-Tidy Checks
====================================
Introduction
============
This page provides examples of how to add query based custom checks for
:program:`clang-tidy`.
Custom checks are based on :program:`clang-query` syntax. Every custom checks
will be registered in `custom` module to avoid name conflict. They can be
enabled or disabled by the checks option like the built-in checks.
Custom checks support inheritance from parent configurations like other
configuration items.
Goals: easy to write, cross platform, multiple versions supported toolkit for
custom clang-tidy rules.
Non-Goals: complex checks, performance, fix-its, etc.
Configuration
=============
`CustomChecks` is a list of custom checks. Each check must contain
- Name: check name can be used in `-checks` option.
- Query: `clang-query` string
- Diagnostic: list of diagnostics to be reported.
- BindName: name of the node to be bound in `Query`.
- Message: message to be reported.
- Level: severity of the diagnostic, the possible values are `Note`, `Warning`.
`CustomChecks` can be configured by `Checks` option in the configuration file.
Example
=======
.. code-block:: 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
.. code-block:: c++
int main(); // note: main function.
void bar() {
main(); // warning: call to main function. [custom-call-main-function]
}
Matters Need Attention
======================
This feature needs to be explicitly enabled by `--experimental-custom-checks`
because it is currently in the experimental stage. Welcome to submit any
suggestions in the `link <https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331>`_.
During the experimental stage, the required configuration structure of this
feature may be changed in the future. Future changes will be as
forward-compatible as possible, but this is not a guarantee.
In subsequent versions, including non-experimental stage, the query statements
will change at any time. The essence of :program:`clang-query` is to parse the
query string and dynamically generate the corresponding AST matcher.
Therefore, its functionality is entirely dependent on the functions provided by
the AST matcher library.
The ast matcher will change along with the changes in the clang AST.
Please refer to `ast matcher reference <https://clang.llvm.org/docs/LibASTMatchersReference.html>`_.

View File

@ -10,6 +10,7 @@ See also:
:maxdepth: 1
List of Clang-Tidy Checks <checks/list>
Query Based Custom Clang-Tidy Checks <QueryBasedCustomChecks>
Clang-tidy IDE/Editor Integrations <Integrations>
Getting Involved <Contributing>
External Clang-Tidy Examples <ExternalClang-TidyExamples>
@ -304,6 +305,8 @@ 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.
CustomChecks - List of user defined checks based on
Clang-Query syntax.
ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'.
ExtraArgs - Same as '--extra-arg'.
ExtraArgsBefore - Same as '--extra-arg-before'.

View File

@ -209,6 +209,7 @@ class CheckRunner:
args = (
[
"clang-tidy",
"--experimental-custom-checks",
self.temp_file_name,
]
+ [

View File

@ -0,0 +1,22 @@
CustomChecks:
- Name: test-diag-level
Query: |
match varDecl(
hasType(asString("long")),
hasTypeLoc(typeLoc().bind("long"))
).bind("decl")
Diagnostic:
- BindName: long
Message: use 'int' instead of 'long'
Level: Warning
- BindName: decl
Message: declaration of 'long'
Level: Note
- Name: test-let-bind
Query: |
let expr varDecl(isStaticStorageClass()).bind("vd")
match expr
Diagnostic:
- BindName: vd
Message: find static variable
Level: Warning

View File

@ -0,0 +1,25 @@
CustomChecks:
- Name: test-let-bind-invalid-1
Query: |
let expr varDecl(isStaticStorageClass()).bind("vd")
match expr
set output print
Diagnostic:
- BindName: vd
Message: find static variable
Level: Warning
- Name: test-let-bind-invalid-2
Query: |
match varDeclInvalid(isStaticStorageClass()).bind("vd")
Diagnostic:
- BindName: vd
Message: find static variable
Level: Warning
- Name: test-let-bind-valid
Query: |
let expr varDecl(isStaticStorageClass()).bind("vd")
match expr
Diagnostic:
- BindName: vd
Message: find static variable
Level: Warning

View File

@ -0,0 +1,7 @@
// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
// CHECK-MESSAGES: warning: 1:1: Matcher not found: varDeclInvalid in 'test-let-bind-invalid-2' [clang-tidy-config]
// CHECK-MESSAGES: warning: unsupported query kind 'SetOutputKind' in 'test-let-bind-invalid-1' [clang-tidy-config]
static int S;
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind-valid]

View File

@ -0,0 +1,5 @@
// RUN: %check_clang_tidy %s custom-test-let-bind %t --config-file=%S/Inputs/clang-tidy.yml
extern long E;
static int S;
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind]

View File

@ -0,0 +1,7 @@
// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/clang-tidy.yml
extern long E;
// CHECK-MESSAGES: [[@LINE-1]]:8: warning: use 'int' instead of 'long' [custom-test-diag-level]
// CHECK-MESSAGES: [[@LINE-2]]:1: note: declaration of 'long'
static int S;
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind]

View File

@ -0,0 +1,8 @@
InheritParentConfig: true
CustomChecks:
- Name: function-decl
Query: match functionDecl().bind("func")
Diagnostic:
- BindName: func
Message: find function decl
Level: Warning

View File

@ -0,0 +1 @@
InheritParentConfig: false

View File

@ -0,0 +1,11 @@
CustomChecks:
- Name: avoid-long-type
Query: |
match varDecl(
hasType(asString("long")),
hasTypeLoc(typeLoc().bind("long"))
)
Diagnostic:
- BindName: long
Message: use 'int' instead of 'long' override
Level: Warning

View File

@ -0,0 +1,11 @@
CustomChecks:
- Name: avoid-long-type
Query: |
match varDecl(
hasType(asString("long")),
hasTypeLoc(typeLoc().bind("long"))
)
Diagnostic:
- BindName: long
Message: use 'int' instead of 'long'
Level: Warning

View File

@ -0,0 +1,44 @@
version: 0
roots:
- name: OUT_DIR
type: directory
contents:
- name: .clang-tidy
type: file
external-contents: INPUT_DIR/root-clang-tidy.yml
- name: main.cpp
type: file
external-contents: MAIN_FILE
- name: subdir
type: directory
contents:
- name: main.cpp
type: file
external-contents: MAIN_FILE
- name: subdir-override
type: directory
contents:
- name: main.cpp
type: file
external-contents: MAIN_FILE
- name: .clang-tidy
type: file
external-contents: INPUT_DIR/override-clang-tidy.yml
- name: subdir-empty
type: directory
contents:
- name: main.cpp
type: file
external-contents: MAIN_FILE
- name: .clang-tidy
type: file
external-contents: INPUT_DIR/empty-clang-tidy.yml
- name: subdir-append
type: directory
contents:
- name: main.cpp
type: file
external-contents: MAIN_FILE
- name: .clang-tidy
type: file
external-contents: INPUT_DIR/append-clang-tidy.yml

View File

@ -0,0 +1,14 @@
// RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
// RUN: clang-tidy --allow-no-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK
// RUN: clang-tidy --allow-no-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
// REQUIRES: shell
long V;
// CHECK: No checks enabled.
void f();
// CHECK-SUB-DIR-APPEND: [[@LINE-1]]:1: warning: find function decl [custom-function-decl]
// LIST-CHECK: Enabled checks:
// LIST-CHECK-EMPTY:

View File

@ -0,0 +1,45 @@
// RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
// RUN: clang-tidy --experimental-custom-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
// RUN: clang-tidy --experimental-custom-checks %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
// RUN: clang-tidy --experimental-custom-checks %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
// RUN: clang-tidy --experimental-custom-checks %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
// REQUIRES: shell
long V;
// CHECK-SAME-DIR: [[@LINE-1]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
// CHECK-SUB-DIR-BASE: [[@LINE-2]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
// CHECK-SUB-DIR-OVERRIDE: [[@LINE-3]]:1: warning: use 'int' instead of 'long' override [custom-avoid-long-type]
// CHECK-SUB-DIR-EMPTY: No checks enabled.
// CHECK-SUB-DIR-APPEND: [[@LINE-5]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
void f();
// CHECK-SUB-DIR-APPEND: [[@LINE-1]]:1: warning: find function decl [custom-function-decl]
// LIST-CHECK: Enabled checks:
// LIST-CHECK: custom-avoid-long-type
// LIST-CHECK: custom-function-decl
// DUMP-CONFIG: CustomChecks:
// DUMP-CONFIG: - Name: avoid-long-type
// DUMP-CONFIG: Query: |
// DUMP-CONFIG: match varDecl(
// DUMP-CONFIG: hasType(asString("long")),
// DUMP-CONFIG: hasTypeLoc(typeLoc().bind("long"))
// DUMP-CONFIG: )
// DUMP-CONFIG: Diagnostic:
// DUMP-CONFIG: - BindName: long
// DUMP-CONFIG: Message: |
// DUMP-CONFIG: use 'int' instead of 'long'
// DUMP-CONFIG: Level: Warning
// DUMP-CONFIG: - Name: function-decl
// DUMP-CONFIG: Query: |
// DUMP-CONFIG: match functionDecl().bind("func")
// DUMP-CONFIG: Diagnostic:
// DUMP-CONFIG: - BindName: func
// DUMP-CONFIG: Message: |
// DUMP-CONFIG: find function decl
// DUMP-CONFIG: Level: Warning

View File

@ -54,6 +54,7 @@ target_link_libraries(ClangTidyTests
PRIVATE
clangTidy
clangTidyAndroidModule
clangTidyCustomModule
clangTidyGoogleModule
clangTidyMiscModule
clangTidyLLVMModule

View File

@ -94,7 +94,8 @@ runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
ClangTidyOptions Options = ExtraOptions;
Options.Checks = "*";
ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
ClangTidyGlobalOptions(), Options));
ClangTidyGlobalOptions(), Options),
false, false, false);
ClangTidyDiagnosticConsumer DiagConsumer(Context);
auto DiagOpts = std::make_unique<DiagnosticOptions>();
DiagnosticsEngine DE(DiagnosticIDs::create(), *DiagOpts, &DiagConsumer,

View File

@ -33,14 +33,17 @@ config_setting(
expand_template(
name = "config",
out = "clang-tidy-config.h",
substitutions = select({
":static_analyzer_enabled": {
"#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 1",
},
"//conditions:default": {
"#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0",
},
}),
substitutions =
{
"#cmakedefine01 CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS": "#define CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS 0",
} | select({
":static_analyzer_enabled": {
"#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 1",
},
"//conditions:default": {
"#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0",
},
}),
template = "clang-tidy-config.h.cmake",
visibility = ["//visibility:private"],
)