diff --git a/clang-tools-extra/CMakeLists.txt b/clang-tools-extra/CMakeLists.txt index 6b6f2b1ca2276..87050db4e0e75 100644 --- a/clang-tools-extra/CMakeLists.txt +++ b/clang-tools-extra/CMakeLists.txt @@ -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) diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt index 93117cf1d6373..153356245cfd1 100644 --- a/clang-tools-extra/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/CMakeLists.txt @@ -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() diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index f4ab93b51f4a7..95726337df91f 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -53,6 +53,11 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry) namespace clang::tidy { +namespace custom { +extern void registerCustomChecks(const ClangTidyOptions &O, + ClangTidyCheckFactories &Factories); +} // namespace custom + namespace { #if CLANG_TIDY_ENABLE_STATIC_ANALYZER static const char *AnalyzerCheckNamePrefix = "clang-analyzer-"; @@ -341,6 +346,9 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory( IntrusiveRefCntPtr OverlayFS) : Context(Context), OverlayFS(std::move(OverlayFS)), CheckFactories(new ClangTidyCheckFactories) { +#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS + custom::registerCustomChecks(Context.getOptions(), *CheckFactories); +#endif for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) { std::unique_ptr Module = E.instantiate(); Module->addCheckFactories(*CheckFactories); @@ -411,7 +419,9 @@ ClangTidyASTConsumerFactory::createASTConsumer( .getCurrentWorkingDirectory(); if (WorkingDir) Context.setCurrentBuildDirectory(WorkingDir.get()); - +#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS + custom::registerCustomChecks(Context.getOptions(), *CheckFactories); +#endif std::vector> Checks = CheckFactories->createChecksForLanguage(&Context); @@ -651,6 +661,9 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) { std::make_unique(ClangTidyGlobalOptions(), Opts), AllowEnablingAnalyzerAlphaCheckers); ClangTidyCheckFactories Factories; +#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS + custom::registerCustomChecks(Context.getOptions(), Factories); +#endif for (const ClangTidyModuleRegistry::entry &Module : ClangTidyModuleRegistry::entries()) { Module.instantiate()->addCheckFactories(Factories); diff --git a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h index adde9136ff1dd..cdf6ce2045a5d 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h +++ b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h @@ -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 = diff --git a/clang-tools-extra/clang-tidy/ClangTidyModule.h b/clang-tools-extra/clang-tidy/ClangTidyModule.h index 28f54331755a7..d818ea926a567 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyModule.h +++ b/clang-tools-extra/clang-tidy/ClangTidyModule.h @@ -62,6 +62,8 @@ class ClangTidyCheckFactories { }); } + void eraseCheck(llvm::StringRef CheckName) { Factories.erase(CheckName); } + /// Create instances of checks that are enabled. std::vector> createChecks(ClangTidyContext *Context) const; diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp index e59f157b468bc..646a3a0a08c8b 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -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" @@ -125,6 +127,51 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool, } } +namespace { +struct MultiLineString { + std::string &S; +}; +} // namespace + +template <> struct BlockScalarTraits { + 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 { + 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 { + static const bool flow = false; +}; +template <> struct MappingTraits { + 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 { + static const bool flow = false; +}; +template <> struct MappingTraits { + 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 AsString; std::optional> AsVector; @@ -180,6 +227,7 @@ template <> struct MappingTraits { IO.mapOptional("InheritParentConfig", Options.InheritParentConfig); IO.mapOptional("UseColor", Options.UseColor); IO.mapOptional("SystemHeaders", Options.SystemHeaders); + IO.mapOptional("CustomChecks", Options.CustomChecks); } }; @@ -241,7 +289,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(), diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h index 6ddc5f9b9cf9e..0f52925a7c765 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h @@ -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 Level; + }; + struct CustomCheckValue { + std::string Name; + std::string Query; + llvm::SmallVector Diags; + }; + using CustomCheckValueList = llvm::SmallVector; + std::optional CustomChecks; + using ArgList = std::vector; /// Add extra compilation arguments to the end of the list. diff --git a/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake b/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake index f4d1a4b38004b..400e89ea60b33 100644 --- a/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake +++ b/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake @@ -6,5 +6,6 @@ #define CLANG_TIDY_CONFIG_H #cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER +#cmakedefine01 CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS #endif diff --git a/clang-tools-extra/clang-tidy/custom/CMakeLists.txt b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt new file mode 100644 index 0000000000000..0b43387970903 --- /dev/null +++ b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt @@ -0,0 +1,22 @@ +if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS) + set(LLVM_LINK_COMPONENTS + support + ) + + add_clang_library(clangTidyCustomModule STATIC + CustomTidyModule.cpp + QueryCheck.cpp + + LINK_LIBS + clangTidy + clangTidyUtils + + DEPENDS + ClangDriverOptions + ) + + clang_target_link_libraries(clangTidyCustomModule + PRIVATE + clangQuery + ) +endif() diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp new file mode 100644 index 0000000000000..4ec9501dd4c2c --- /dev/null +++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp @@ -0,0 +1,50 @@ +#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 + +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, 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(Name, V, Context); + }); + CustomCheckNames.insert(std::move(Name)); + } +} + +} // namespace custom + +// Register the CustomTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + 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 diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp new file mode 100644 index 0000000000000..f83c138fbfaf5 --- /dev/null +++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp @@ -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 + +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 +parseQuery(const ClangTidyOptions::CustomCheckValue &V, + ClangTidyContext *Context) { + SmallVector 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(*Q); + Matchers.push_back(MatchQuery.Matcher); + break; + } + case query::QK_Let: { + const auto &LetQuery = llvm::cast(*Q); + LetQuery.run(llvm::errs(), QS); + break; + } + case query::QK_NoOp: { + const auto &NoOpQuery = llvm::cast(*Q); + NoOpQuery.run(llvm::errs(), QS); + break; + } + case query::QK_Invalid: { + const auto &InvalidQuery = llvm::cast(*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>{}) + .first; + auto DiagMessageIt = + DiagnosticIdIt->getSecond() + .try_emplace(D.BindName, llvm::SmallVector{}) + .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 diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h new file mode 100644 index 0000000000000..3dcdc518736c2 --- /dev/null +++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h @@ -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 Matchers; + using BindNameMapToDiagMessage = + llvm::StringMap>; + using DiagMaps = + llvm::DenseMap; + DiagMaps Diags; +}; + +} // namespace clang::tidy::custom + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index 4336c723bd7cd..d42c84585d357 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -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'. diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index f8f183e9de1cc..27934405a59dc 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -105,6 +105,10 @@ Improvements to clang-tidy specific tests. This may break tests for users with custom out-of-tree checks who use :program:`check_clang_tidy.py` as-is. +- :program:`clang-tidy` now supports query based custom checks by `CustomChecks` + configuration option. + :doc:`Query Based Custom Check Document ` + - Improved :program:`clang-tidy-diff.py` script. Add the `-warnings-as-errors` argument to treat warnings as errors. diff --git a/clang-tools-extra/docs/clang-tidy/Contributing.rst b/clang-tools-extra/docs/clang-tidy/Contributing.rst index 9611c655886f2..49621ea1bed04 100644 --- a/clang-tools-extra/docs/clang-tidy/Contributing.rst +++ b/clang-tools-extra/docs/clang-tidy/Contributing.rst @@ -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 diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst new file mode 100644 index 0000000000000..0f8bcf1ec84be --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst @@ -0,0 +1,63 @@ +==================================== +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] + } diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst index b7a366e874130..b218e0034825c 100644 --- a/clang-tools-extra/docs/clang-tidy/index.rst +++ b/clang-tools-extra/docs/clang-tidy/index.rst @@ -10,6 +10,7 @@ See also: :maxdepth: 1 List of Clang-Tidy Checks + Query Based Custom Clang-Tidy Checks Clang-tidy IDE/Editor Integrations Getting Involved External Clang-Tidy Examples @@ -292,6 +293,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'. diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml new file mode 100644 index 0000000000000..b4524e247feae --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml new file mode 100644 index 0000000000000..b94ba32997029 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp new file mode 100644 index 0000000000000..f9a73750b4c3e --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp @@ -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] diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp new file mode 100644 index 0000000000000..962313ddb587b --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp @@ -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] diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp new file mode 100644 index 0000000000000..e9a27301bd611 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp @@ -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] diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml new file mode 100644 index 0000000000000..5b25ec061ba63 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml @@ -0,0 +1,8 @@ +InheritParentConfig: true +CustomChecks: + - Name: function-decl + Query: match functionDecl().bind("func") + Diagnostic: + - BindName: func + Message: find function decl + Level: Warning diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml new file mode 100644 index 0000000000000..2e9a13e720f4e --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml @@ -0,0 +1 @@ +InheritParentConfig: false diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml new file mode 100644 index 0000000000000..ec243b8396ea8 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml new file mode 100644 index 0000000000000..861ed10be1a5f --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml new file mode 100644 index 0000000000000..2b507f60092c3 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp new file mode 100644 index 0000000000000..13834660385d1 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp @@ -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 %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR +// RUN: clang-tidy %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE +// RUN: clang-tidy %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE +// RUN: clang-tidy %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY +// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND +// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK +// RUN: clang-tidy %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 diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt index 3304924d39757..a3f20f5c1359f 100644 --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries(ClangTidyTests PRIVATE clangTidy clangTidyAndroidModule + clangTidyCustomModule clangTidyGoogleModule clangTidyMiscModule clangTidyLLVMModule