congliu updated this revision to Diff 44328.
congliu marked 33 inline comments as done.
congliu added a comment.

- Completed [class.virtual] p7 covarient check. Updated test.
- Corrected implement of checkParamType.
- Clean up.
- Corrected typos. Changed some methods to free-standing. Changed warning 
message. Updated tests.
- Finished correcting cpp. Updated doc.


http://reviews.llvm.org/D15823

Files:
  clang-tidy/misc/CMakeLists.txt
  clang-tidy/misc/MiscTidyModule.cpp
  clang-tidy/misc/VirtualNearMissCheck.cpp
  clang-tidy/misc/VirtualNearMissCheck.h
  docs/clang-tidy/checks/list.rst
  docs/clang-tidy/checks/misc-virtual-near-miss.rst
  test/clang-tidy/misc-virtual-near-miss.cpp

Index: test/clang-tidy/misc-virtual-near-miss.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/misc-virtual-near-miss.cpp
@@ -0,0 +1,65 @@
+// RUN: %check_clang_tidy %s misc-virtual-near-miss %t
+
+struct Base {
+  virtual void func();
+  virtual void gunk();
+};
+
+struct Derived : Base {
+  // Should not warn "do you want to override 'gunk'?", because gunk is already
+  // overriden by this class.
+  virtual void funk();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::funk' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it? [misc-virtual-near-miss]
+
+  void func2();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::func2' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it?
+
+  void func22(); // Should not warn.
+
+  void gunk(); // Should not warn: gunk is override.
+
+  void fun();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Derived::fun' has a similar name and the same signature with virtual method 'Base::func'. Did you meant to override it?
+};
+
+class Father {
+public:
+  Father();
+  virtual void func();
+  virtual Father *create(int i);
+  virtual Base &&generate();
+};
+
+class Mother {
+public:
+  Mother();
+  static void method();
+  virtual int method(int argc, const char **argv);
+  virtual int method(int argc) const;
+};
+
+class Child : Father, Mother {
+public:
+  Child();
+
+  virtual void func2();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::func2' has a similar name and the same signature with virtual method 'Father::func'. Did you meant to override it?
+
+  int methoe(int x, char **strs); // Should not warn: parameter types don't match.
+
+  int methoe(int x); // Should not warn: method is not const.
+
+  void methof(int x, const char **strs); // Should not warn: return types don't match.
+
+  int methoh(int x, const char **strs);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::methoh' has a similar name and the same signature with virtual method 'Mother::method'. Did you meant to override it?
+
+  virtual Child *creat(int i);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::creat' has a similar name and the same signature with virtual method 'Father::create'. Did you meant to override it?
+
+  virtual Derived &&generat();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'Child::generat' has a similar name and the same signature with virtual method 'Father::generate'. Did you meant to override it?
+
+private:
+  void funk(); // Should not warn: access qualifers don't match.
+};
Index: docs/clang-tidy/checks/misc-virtual-near-miss.rst
===================================================================
--- /dev/null
+++ docs/clang-tidy/checks/misc-virtual-near-miss.rst
@@ -0,0 +1,17 @@
+misc-virtual-near-miss
+======================
+
+Warn if a function is a near miss (ie. the name is very similar and the function signiture is the same) to a virtual function from a base class.
+
+Example:
+
+.. code-block:: c++
+
+  struct Base {
+    virtual void func();
+  };
+
+  struct Derived : Base {
+    virtual funk();
+    // warning: Do you want to override 'func'?
+  };
Index: docs/clang-tidy/checks/list.rst
===================================================================
--- docs/clang-tidy/checks/list.rst
+++ docs/clang-tidy/checks/list.rst
@@ -56,6 +56,7 @@
    misc-unused-alias-decls
    misc-unused-parameters
    misc-unused-raii
+   misc-virtual-near-miss
    modernize-loop-convert
    modernize-make-unique
    modernize-pass-by-value
Index: clang-tidy/misc/VirtualNearMissCheck.h
===================================================================
--- /dev/null
+++ clang-tidy/misc/VirtualNearMissCheck.h
@@ -0,0 +1,65 @@
+//===--- VirtualNearMissCheck.h - clang-tidy---------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H
+
+#include "../ClangTidy.h"
+#include <map>
+#include <string>
+
+namespace clang {
+namespace tidy {
+namespace misc {
+
+/// \brief Checks for near miss of virtual methods.
+///
+/// For a method in a derived class, this check looks for virtual method with a
+/// very similar name and an identical signature defined in a base class.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/misc-virtual-near-miss.html
+class VirtualNearMissCheck : public ClangTidyCheck {
+public:
+  VirtualNearMissCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  /// Check if the given method is possible to be overridden by some other
+  /// method.
+  ///
+  /// It should look up the PossibleMap or update it.
+  bool isPossibleToBeOverridden(const CXXMethodDecl *BaseMD);
+
+  /// Check if the given base method is overridden by some methods in the given
+  /// derived class.
+  ///
+  /// It should look up the OverriddenMap or update it.
+  bool isOverriddenByDerivedClass(const CXXMethodDecl *BaseMD,
+                                  const CXXRecordDecl *DerivedRD);
+
+  /// key: the unique ID of a method;
+  /// value: whether the method is possible to be overridden.
+  std::map<std::string, bool> PossibleMap;
+
+  /// key: <unique ID of base method, name of derived class>
+  /// value: whether the base method is overridden by some method in the derived
+  /// class.
+  std::map<std::pair<std::string, std::string>, bool> OverriddenMap;
+
+  const unsigned EditDistanceThreshold = 1;
+};
+
+} // namespace misc
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H
Index: clang-tidy/misc/VirtualNearMissCheck.cpp
===================================================================
--- /dev/null
+++ clang-tidy/misc/VirtualNearMissCheck.cpp
@@ -0,0 +1,271 @@
+//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "VirtualNearMissCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace misc {
+
+/// Finds out if the given method overrides some method.
+bool isOverrideMethod(const CXXMethodDecl *MD) {
+  return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>();
+}
+
+/// Checks whether the return types are covariant, according to
+/// C++[class.virtual]p7.
+///
+/// Similar with clang::Sema::CheckOverridingFunctionReturnType.
+/// \returns true if the return types of BaseMD and DerivedMD are covariant.
+bool checkOverridingFunctionReturnType(const ASTContext *Context,
+                                       const CXXMethodDecl *BaseMD,
+                                       const CXXMethodDecl *DerivedMD) {
+  QualType BaseReturnTy =
+      BaseMD->getType()->getAs<FunctionType>()->getReturnType();
+  QualType DerivedReturnTy =
+      DerivedMD->getType()->getAs<FunctionType>()->getReturnType();
+
+  if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType())
+    return false;
+
+  // Check if return types are identical
+  if (Context->hasSameType(DerivedReturnTy, BaseReturnTy))
+    return true;
+
+  /// Check if the return types are covariant.
+  /// BTy is the class type in return type of BaseMD. For example,
+  ///    B* Base::md()
+  /// While BRD is the declaration of B.
+  QualType BTy, DTy;
+  const CXXRecordDecl *BRD, *DRD;
+
+  // Both types must be pointers or references to classes.
+  if (const auto *DerivedPT = DerivedReturnTy->getAs<PointerType>()) {
+    if (const auto *BasePT = BaseReturnTy->getAs<PointerType>()) {
+      DTy = DerivedPT->getPointeeType();
+      BTy = BasePT->getPointeeType();
+    }
+  } else if (const auto *DerivedRT = DerivedReturnTy->getAs<ReferenceType>()) {
+    if (const auto *BaseRT = BaseReturnTy->getAs<ReferenceType>()) {
+      DTy = DerivedRT->getPointeeType();
+      BTy = BaseRT->getPointeeType();
+    }
+  }
+
+  // The return types aren't either both pointers or references to a class type.
+  if (DTy.isNull()) {
+    return false;
+  }
+
+  DRD = DTy->getAsCXXRecordDecl();
+  BRD = BTy->getAsCXXRecordDecl();
+  if (DRD == nullptr || BRD == nullptr)
+    return false;
+
+  if (DRD == BRD)
+    return true;
+
+  if (!Context->hasSameUnqualifiedType(DTy, BTy)) {
+    // Begin checking whether the conversion from D to B is valid.
+    CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
+                       /*DetectVirtual=*/false);
+
+    // Check whether D is derived from B, and fill in a CXXBasePaths object.
+    if (!DRD->isDerivedFrom(BRD, Paths))
+      return false;
+
+    // Check ambiguity.
+    if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType()))
+      return false;
+
+    // Check accessibility.
+    // FIXME: We currently only support checking if B is accessible base class
+    // of D, or D is the same class which DerivedMD is in.
+    bool IsItself = DRD == DerivedMD->getParent();
+    bool HasPublicAccess = false;
+    for (const auto &Path : Paths) {
+      if (Path.Access == AS_public) {
+        HasPublicAccess = true;
+      }
+    }
+    if (!HasPublicAccess && !IsItself)
+      return false;
+    // End checking conversion from D to B.
+  }
+
+  // Both pointers or references should have the same cv-qualification.
+  if (DerivedReturnTy.getLocalCVRQualifiers() !=
+      BaseReturnTy.getLocalCVRQualifiers())
+    return false;
+
+  // The class type D should have the same cv-qualification as or less
+  // cv-qualification than the class type B.
+  if (DTy.isMoreQualifiedThan(BTy))
+    return false;
+
+  return true;
+}
+
+/// \returns true if the param types are the same.
+bool checkParamTypes(const CXXMethodDecl *BaseMD,
+                     const CXXMethodDecl *DerivedMD) {
+  unsigned NumParamA = BaseMD->getNumParams();
+  unsigned NumParamB = DerivedMD->getNumParams();
+  if (NumParamA != NumParamB)
+    return false;
+
+  for (unsigned I = 0; I < NumParamA; I++) {
+    if (BaseMD->getParamDecl(I)->getType() !=
+        DerivedMD->getParamDecl(I)->getType())
+      return false;
+  }
+  return true;
+}
+
+/// \returns true if derived method can override base method except for the
+/// name.
+bool checkOverrideWithoutName(const ASTContext *Context,
+                              const CXXMethodDecl *BaseMD,
+                              const CXXMethodDecl *DerivedMD) {
+  if (BaseMD->getTypeQualifiers() != DerivedMD->getTypeQualifiers())
+    return false;
+
+  if (BaseMD->isStatic() != DerivedMD->isStatic())
+    return false;
+
+  if (BaseMD->getAccess() != DerivedMD->getAccess())
+    return false;
+
+  if (BaseMD->getType() == DerivedMD->getType())
+    return true;
+
+  // Now the function types are not identical. Then check if the return types
+  // are covariant and if the param types are the same.
+  if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD))
+    return false;
+  return checkParamTypes(BaseMD, DerivedMD);
+}
+
+/// Check whether BaseMD overrides DerivedMD.
+///
+/// Prerequisite: the class which BaseMD is in should be a base class of that
+/// DerivedMD is in.
+bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD,
+                                  const CXXMethodDecl *DerivedMD) {
+  if (BaseMD->getNameAsString() != DerivedMD->getNameAsString())
+    return false;
+
+  if (!checkParamTypes(BaseMD, DerivedMD))
+    return false;
+
+  return true;
+}
+
+/// Generate unique ID for given MethodDecl.
+///
+/// The Id is used as key for 'PossibleMap'.
+/// Typical Id: "Base::func void (void)"
+std::string generateMethodId(const CXXMethodDecl *MD) {
+  return MD->getQualifiedNameAsString() + " " + MD->getType().getAsString();
+}
+
+bool VirtualNearMissCheck::isPossibleToBeOverridden(
+    const CXXMethodDecl *BaseMD) {
+  std::string Id = generateMethodId(BaseMD);
+  auto Iter = PossibleMap.find(Id);
+  if (Iter != PossibleMap.end()) {
+    return Iter->second;
+  }
+
+  bool IsPossible = !BaseMD->isImplicit() && !isa<CXXConstructorDecl>(BaseMD) &&
+                    BaseMD->isVirtual();
+  PossibleMap[Id] = IsPossible;
+  return IsPossible;
+}
+
+bool VirtualNearMissCheck::isOverriddenByDerivedClass(
+    const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) {
+  auto Key = std::make_pair(generateMethodId(BaseMD),
+                            DerivedRD->getQualifiedNameAsString());
+  auto Iter = OverriddenMap.find(Key);
+  if (Iter != OverriddenMap.end()) {
+    return Iter->second;
+  }
+
+  bool IsOverridden = false;
+  for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) {
+    if (!isOverrideMethod(DerivedMD))
+      continue;
+
+    if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) {
+      IsOverridden = true;
+      break;
+    }
+  }
+  OverriddenMap[Key] = IsOverridden;
+  return IsOverridden;
+}
+
+void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) {
+  if (!getLangOpts().CPlusPlus)
+    return;
+
+  Finder->addMatcher(cxxMethodDecl(unless(anyOf(isOverride(), isImplicit(),
+                                                cxxConstructorDecl())))
+                         .bind("method"),
+                     this);
+}
+
+void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
+  assert(DerivedMD != nullptr);
+
+  if (DerivedMD->isStatic())
+    return;
+
+  const ASTContext *Context = Result.Context;
+
+  const auto *DerivedRD = DerivedMD->getParent();
+
+  for (const auto &BaseSpec : DerivedRD->bases()) {
+    if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) {
+      for (const auto *BaseMD : BaseRD->methods()) {
+        if (!isPossibleToBeOverridden(BaseMD))
+          continue;
+
+        if (isOverriddenByDerivedClass(BaseMD, DerivedRD))
+          continue;
+
+        unsigned EditDistance =
+            BaseMD->getName().edit_distance(DerivedMD->getName());
+        if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) {
+          if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) {
+            // A "virtual near miss" is found.
+            diag(DerivedMD->getLocStart(),
+                 "'%0' has a similar name and the same "
+                 "signature with virtual method '%1'. "
+                 "Did you meant to override it?")
+                << DerivedMD->getQualifiedNameAsString()
+                << BaseMD->getQualifiedNameAsString();
+          }
+        }
+      }
+    }
+  }
+}
+
+} // namespace misc
+} // namespace tidy
+} // namespace clang
Index: clang-tidy/misc/MiscTidyModule.cpp
===================================================================
--- clang-tidy/misc/MiscTidyModule.cpp
+++ clang-tidy/misc/MiscTidyModule.cpp
@@ -33,6 +33,7 @@
 #include "UnusedAliasDeclsCheck.h"
 #include "UnusedParametersCheck.h"
 #include "UnusedRAIICheck.h"
+#include "VirtualNearMissCheck.h"
 
 namespace clang {
 namespace tidy {
@@ -84,6 +85,8 @@
     CheckFactories.registerCheck<UnusedParametersCheck>(
         "misc-unused-parameters");
     CheckFactories.registerCheck<UnusedRAIICheck>("misc-unused-raii");
+    CheckFactories.registerCheck<VirtualNearMissCheck>(
+        "misc-virtual-near-miss");
   }
 };
 
Index: clang-tidy/misc/CMakeLists.txt
===================================================================
--- clang-tidy/misc/CMakeLists.txt
+++ clang-tidy/misc/CMakeLists.txt
@@ -25,6 +25,7 @@
   UnusedParametersCheck.cpp
   UnusedRAIICheck.cpp
   UniqueptrResetReleaseCheck.cpp
+  VirtualNearMissCheck.cpp
 
   LINK_LIBS
   clangAST
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to