njames93 created this revision.
njames93 added reviewers: aaron.ballman, alexfh, JonasToth, hokein.
njames93 added projects: clang-tools-extra, clang.
Herald added a subscriber: xazax.hun.

Clang-tidy has had an on growing issue with blindly adding all checks from a 
module using the `-checks=somemodule-* ` option. If somemodule alias' another 
enabled check. the check will get ran twice giving duplicated warning and fix 
options. This can lead to fixes overlapping preventing them from being applied 
or worse still applying twice resulting in malformed code. There are other 
issues with ConfigOptions being different in the 2 different instances so one 
instance of the check may be running a different set of options to the other.

This patch solves these issue by making clang-tidy only run 1 instance of an 
aliased check even if it has been turned on with multiple names. Warning 
messages are appended with the original check name if it has been enabled(via 
cmd line, or config file). If the original check hasn't been enabled then the 
name will be the first enabled alias that was registered.

Config options are also handled in a similar fashion, see example

  [{ key: MyAliasedName.Option1, value: 0 }.
  { key: OriginalCheckName.Option1, value: 1 }]

If the check `original-check-name` was turned on, then `Option1` will be parsed 
as being `1`, otherwise it ignores `OriginalCheckName.Option1` key and falls 
back to the first enabled alias check that declares `Option1`

To declare a check as an alias when you register then check, just add the check 
this alias' to as a second parameter

  CheckFactories.registerCheck<readability::BracesAroundStatementsCheck>(
      "hicpp-braces-around-statements");
  // Update to
  CheckFactories.registerCheck<readability::BracesAroundStatementsCheck>(
      "hicpp-braces-around-statements", "readability-braces-around-statements");

I haven't added in all the alias declarations in just yet (If you want me to 
just ask), nor have I added test cases. However once all the alias declarations 
are added in, the tests fall nicely in line with the current test 
infrastructure.
Adding this line to the readability-braces-around-statements checks causes no 
errors, no multiple fix attempts or warning messages.

  // RUN: %check_clang_tidy %s 
readability-braces-around-statements,hicpp-readability-braces-around-statements 
%t

Likewise changing it to

  // RUN: %check_clang_tidy %s hicpp-readability-braces-around-statements %t

Also runs without a hitch (obviously warning messages are appended with 
`[hicpp-readability-braces-around-statements]` instead of 
`[readability-braces-around-statements]` as above.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D72566

Files:
  clang-tools-extra/clang-tidy/ClangTidy.cpp
  clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
  clang-tools-extra/clang-tidy/ClangTidyCheck.h
  clang-tools-extra/clang-tidy/ClangTidyModule.cpp
  clang-tools-extra/clang-tidy/ClangTidyModule.h
  clang-tools-extra/docs/ReleaseNotes.rst

Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -67,6 +67,9 @@
 Improvements to clang-tidy
 --------------------------
 
+- Clang tidy is now aware of alias checks and will only run one instance of
+  an alias check that's enabled by different names.
+
 New checks
 ^^^^^^^^^^
 
Index: clang-tools-extra/clang-tidy/ClangTidyModule.h
===================================================================
--- clang-tools-extra/clang-tidy/ClangTidyModule.h
+++ clang-tools-extra/clang-tidy/ClangTidyModule.h
@@ -11,6 +11,7 @@
 
 #include "ClangTidy.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator_range.h"
 #include <functional>
 #include <map>
 #include <memory>
@@ -34,6 +35,13 @@
   /// For all checks that have default constructors, use \c registerCheck.
   void registerCheckFactory(StringRef Name, CheckFactory Factory);
 
+  /// Registers check \p Factory with name \p Name that is an alias to
+  /// another check called \p AliasToName.
+  ///
+  /// For all checks that have default constructors, use \c registerCheck.
+  void registerCheckFactory(StringRef Name, StringRef AliasToName,
+                            CheckFactory Factory);
+
   /// Registers the \c CheckType with the name \p Name.
   ///
   /// This method should be used for all \c ClangTidyChecks that don't require
@@ -62,6 +70,16 @@
                          });
   }
 
+  /// Registers the \c CheckType with the name \p Name that is an alias to
+  /// another check called \p AliasToName.
+  template <typename CheckType>
+  void registerCheck(StringRef CheckName, StringRef AliasToName) {
+    registerCheckFactory(CheckName, AliasToName,
+                         [](StringRef Name, ClangTidyContext *Context) {
+                           return std::make_unique<CheckType>(Name, Context);
+                         });
+  }
+
   /// Create instances of checks that are enabled.
   std::vector<std::unique_ptr<ClangTidyCheck>>
   createChecks(ClangTidyContext *Context);
@@ -69,10 +87,22 @@
   typedef std::map<std::string, CheckFactory> FactoryMap;
   FactoryMap::const_iterator begin() const { return Factories.begin(); }
   FactoryMap::const_iterator end() const { return Factories.end(); }
-  bool empty() const { return Factories.empty(); }
+  typedef std::map<std::string, std::pair<std::string, CheckFactory>>
+      AliasFactoryMap;
+  AliasFactoryMap::const_iterator alias_begin() const {
+    return AliasFactories.begin();
+  }
+  AliasFactoryMap::const_iterator alias_end() const {
+    return AliasFactories.end();
+  }
+  llvm::iterator_range<AliasFactoryMap::const_iterator> aliases() const {
+    return {alias_begin(), alias_end()};
+  }
+  bool empty() const { return Factories.empty() && AliasFactories.empty(); }
 
 private:
   FactoryMap Factories;
+  AliasFactoryMap AliasFactories;
 };
 
 /// A clang-tidy module groups a number of \c ClangTidyChecks and gives
Index: clang-tools-extra/clang-tidy/ClangTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/ClangTidyModule.cpp
+++ clang-tools-extra/clang-tidy/ClangTidyModule.cpp
@@ -11,15 +11,31 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidyModule.h"
+#include "llvm/Support/raw_ostream.h"
+#include <utility>
 
 namespace clang {
 namespace tidy {
 
 void ClangTidyCheckFactories::registerCheckFactory(StringRef Name,
                                                    CheckFactory Factory) {
+  if (AliasFactories.count(Name))
+    return; // Check already registered as alias
   Factories[Name] = std::move(Factory);
 }
 
+void ClangTidyCheckFactories::registerCheckFactory(StringRef Name,
+                                                   StringRef AliasToName,
+                                                   CheckFactory Factory) {
+  Factories.erase(AliasToName);
+  auto &Current = AliasFactories[AliasToName];
+  if (Current.second) // Check if already declared
+    Current.first.append((";" + Name).str());
+  else
+    Current = std::make_pair(Name, std::move(Factory));
+  assert(AliasFactories.count(AliasToName) != 0);
+}
+
 std::vector<std::unique_ptr<ClangTidyCheck>>
 ClangTidyCheckFactories::createChecks(ClangTidyContext *Context) {
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks;
@@ -27,6 +43,23 @@
     if (Context->isCheckEnabled(Factory.first))
       Checks.emplace_back(Factory.second(Factory.first, Context));
   }
+  for (const auto &AliasFactory : AliasFactories) {
+    SmallString<256> Buffer;
+    llvm::raw_svector_ostream Builder(Buffer);
+    if (Context->isCheckEnabled(AliasFactory.first))
+      Builder << AliasFactory.first << ';';
+    SmallVector<StringRef, 4> Split;
+    StringRef AliasNames = AliasFactory.second.first;
+    AliasNames.split(Split, ';', -1, false);
+    for (StringRef Aliases : Split) {
+      if (Context->isCheckEnabled(Aliases))
+        Builder << Aliases << ';';
+    }
+    if (!Buffer.empty()) {
+      Buffer.pop_back(); // Remove the last ';'
+      Checks.emplace_back(AliasFactory.second.second(Buffer, Context));
+    }
+  }
   return Checks;
 }
 
Index: clang-tools-extra/clang-tidy/ClangTidyCheck.h
===================================================================
--- clang-tools-extra/clang-tidy/ClangTidyCheck.h
+++ clang-tools-extra/clang-tidy/ClangTidyCheck.h
@@ -170,7 +170,7 @@
                int64_t Value) const;
 
   private:
-    std::string NamePrefix;
+    SmallVector<std::string, 2> NamePrefixes;
     const ClangTidyOptions::OptionMap &CheckOptions;
   };
 
Index: clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
+++ clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
@@ -7,12 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidyCheck.h"
+#include "llvm/ADT/SmallVector.h"
 
 namespace clang {
 namespace tidy {
 
 ClangTidyCheck::ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context)
-    : CheckName(CheckName), Context(Context),
+    : CheckName(CheckName.split(';').first), Context(Context),
       Options(CheckName, Context->getOptions().CheckOptions) {
   assert(Context != nullptr);
   assert(!CheckName.empty());
@@ -30,35 +31,46 @@
   check(Result);
 }
 
-ClangTidyCheck::OptionsView::OptionsView(StringRef CheckName,
-                         const ClangTidyOptions::OptionMap &CheckOptions)
-    : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {}
+ClangTidyCheck::OptionsView::OptionsView(
+    StringRef CheckName, const ClangTidyOptions::OptionMap &CheckOptions)
+    : CheckOptions(CheckOptions) {
+  llvm::SmallVector<StringRef, 4> CheckNames;
+  CheckName.split(CheckNames, ';', -1, false);
+  for (const auto &Name : CheckNames) {
+    NamePrefixes.emplace_back((Name + ".").str());
+  }
+  assert(!NamePrefixes.empty());
+}
 
 std::string ClangTidyCheck::OptionsView::get(StringRef LocalName,
                                              StringRef Default) const {
-  const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str());
-  if (Iter != CheckOptions.end())
-    return Iter->second;
+  for (const auto &NamePrefix : NamePrefixes) {
+    const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str());
+    if (Iter != CheckOptions.end())
+      return Iter->second;
+  }
   return Default;
 }
 
 std::string
 ClangTidyCheck::OptionsView::getLocalOrGlobal(StringRef LocalName,
                                               StringRef Default) const {
-  auto Iter = CheckOptions.find(NamePrefix + LocalName.str());
-  if (Iter != CheckOptions.end())
-    return Iter->second;
-  // Fallback to global setting, if present.
-  Iter = CheckOptions.find(LocalName.str());
-  if (Iter != CheckOptions.end())
-    return Iter->second;
+  for (const auto &NamePrefix : NamePrefixes) {
+    auto Iter = CheckOptions.find(NamePrefix + LocalName.str());
+    if (Iter != CheckOptions.end())
+      return Iter->second;
+    // Fallback to global setting, if present.
+    Iter = CheckOptions.find(LocalName.str());
+    if (Iter != CheckOptions.end())
+      return Iter->second;
+  }
   return Default;
 }
 
 void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
                                         StringRef LocalName,
                                         StringRef Value) const {
-  Options[NamePrefix + LocalName.str()] = Value;
+  Options[NamePrefixes.front() + LocalName.str()] = Value;
 }
 
 void ClangTidyCheck::OptionsView::store(ClangTidyOptions::OptionMap &Options,
Index: clang-tools-extra/clang-tidy/ClangTidy.cpp
===================================================================
--- clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -445,6 +445,17 @@
     if (Context.isCheckEnabled(CheckFactory.first))
       CheckNames.push_back(CheckFactory.first);
   }
+  for (const auto &AliasCheckFactory : CheckFactories->aliases()) {
+    if (Context.isCheckEnabled(AliasCheckFactory.first))
+      CheckNames.push_back(AliasCheckFactory.first);
+    for (auto StringRefPair =
+             StringRef(AliasCheckFactory.second.first).split(';');
+         !StringRefPair.first.empty();
+         StringRefPair = StringRefPair.second.split(';')) {
+      if (Context.isCheckEnabled(StringRefPair.first))
+        CheckNames.push_back(StringRefPair.first);
+    }
+  }
 
 #if CLANG_ENABLE_STATIC_ANALYZER
   for (const auto &AnalyzerCheck : getAnalyzerCheckersAndPackages(
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to