steveire created this revision.
steveire added a reviewer: alexfh.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.
steveire requested review of this revision.

readability-container-size-empty currently modifies source code based on
AST nodes in template instantiations, which means that it makes
transformations based on substituted types.  This can lead to
transforming code to be broken.

Change the matcher implementation to ignore template instantiations
explicitly, and add a matcher to explicitly handle template declatations
instead of instantiations.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D91302

Files:
  clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
  
clang-tools-extra/test/clang-tidy/checkers/readability-container-size-empty.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/readability-container-size-empty.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/readability-container-size-empty.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/readability-container-size-empty.cpp
@@ -442,3 +442,83 @@
   f<double>();
   f<char *>();
 }
+
+template <typename T>
+void neverInstantiatedTemplate() {
+  std::vector<T> v;
+  if (v.size())
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
+  // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (!v.empty()){{$}}
+
+  if (v == std::vector<T>())
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of comparing to an empty object [readability-container-size-empty]
+  // CHECK-FIXES: {{^  }}if (v.empty()){{$}}
+  // CHECK-FIXES-NEXT: ;
+  if (v.size() == 0)
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
+  // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (v.empty()){{$}}
+  if (static_cast<bool>(v.size()))
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
+  // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (static_cast<bool>(!v.empty())){{$}}
+  if (v.size() && false)
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
+  // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (!v.empty() && false){{$}}
+  if (!v.size())
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
+  // CHECK-MESSAGES: :9:8: note: method 'vector'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (v.empty()){{$}}
+
+  TemplatedContainer<T> templated_container;
+  if (templated_container.size())
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+  // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (!templated_container.empty()){{$}}
+  if (templated_container != TemplatedContainer<T>())
+    ;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: the 'empty' method should be used
+  // CHECK-MESSAGES: :44:8: note: method 'TemplatedContainer'::empty() defined here
+  // CHECK-FIXES: {{^  }}if (!templated_container.empty()){{$}}
+  // CHECK-FIXES-NEXT: ;
+}
+
+template <typename TypeRequiresSize>
+void instantiatedTemplateWithSizeCall() {
+  TypeRequiresSize t;
+  // The instantiation of the template with std::vector<int> should not
+  // result in changing the template, because we don't know that
+  // TypeRequiresSize generally has `.empty()`
+  if (t.size())
+    ;
+
+  if (t == TypeRequiresSize{})
+    ;
+
+  if (t != TypeRequiresSize{})
+    ;
+}
+
+class TypeWithSize {
+public:
+  TypeWithSize();
+  bool operator==(const TypeWithSize &other) const;
+  bool operator!=(const TypeWithSize &other) const;
+
+  unsigned size() const { return 0; }
+  // Does not have `.empty()`
+};
+
+void instantiator() {
+  instantiatedTemplateWithSizeCall<TypeWithSize>();
+  instantiatedTemplateWithSizeCall<std::vector<int>>();
+}
Index: clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
+++ clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp
@@ -26,18 +26,27 @@
     : ClangTidyCheck(Name, Context) {}
 
 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
-  const auto ValidContainer = qualType(hasUnqualifiedDesugaredType(
-      recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(
-          namedDecl(
-              has(cxxMethodDecl(
-                      isConst(), parameterCountIs(0), isPublic(),
-                      hasName("size"),
-                      returns(qualType(isInteger(), unless(booleanType()))))
-                      .bind("size")),
-              has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
-                                hasName("empty"), returns(booleanType()))
-                      .bind("empty")))
-              .bind("container")))))));
+  const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
+      namedDecl(
+          has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
+                            hasName("size"),
+                            returns(qualType(isInteger(), unless(booleanType()),
+                                             unless(elaboratedType()))))
+                  .bind("size")),
+          has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
+                            hasName("empty"), returns(booleanType()))
+                  .bind("empty")))
+          .bind("container")));
+
+  const auto ValidContainerNonTemplateType =
+      qualType(hasUnqualifiedDesugaredType(
+          recordType(hasDeclaration(ValidContainerRecord))));
+  const auto ValidContainerTemplateType =
+      qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
+          hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
+
+  const auto ValidContainer = qualType(
+      anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
 
   const auto WrongUse = traverse(
       ast_type_traits::TK_AsIs,
@@ -52,18 +61,37 @@
               anyOf(hasParent(
                         unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
                     anything()))),
-          hasParent(explicitCastExpr(hasDestinationType(booleanType())))));
+          hasParent(explicitCastExpr(hasDestinationType(booleanType()))),
+          hasParent(ifStmt()), hasParent(whileStmt()),
+          hasParent(binaryOperator(hasAnyOperatorName("&&", "||"))),
+          hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize"))));
 
   Finder->addMatcher(
-      cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
+      cxxMemberCallExpr(unless(isInTemplateInstantiation()),
+                        on(expr(anyOf(hasType(ValidContainer),
                                       hasType(pointsTo(ValidContainer)),
-                                      hasType(references(ValidContainer))))),
+                                      hasType(references(ValidContainer))))
+                               .bind("MemberCallObject")),
                         callee(cxxMethodDecl(hasName("size"))), WrongUse,
                         unless(hasAncestor(cxxMethodDecl(
                             ofClass(equalsBoundNode("container"))))))
           .bind("SizeCallExpr"),
       this);
 
+  Finder->addMatcher(
+      callExpr(has(cxxDependentScopeMemberExpr(
+                   hasObjectExpression(
+                       expr(anyOf(hasType(ValidContainer),
+                                  hasType(pointsTo(ValidContainer)),
+                                  hasType(references(ValidContainer))))
+                           .bind("MemberCallObject")),
+                   hasMemberName("size"))),
+               WrongUse,
+               unless(hasAncestor(
+                   cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
+          .bind("SizeCallExpr"),
+      this);
+
   // Empty constructor matcher.
   const auto DefaultConstructor = cxxConstructExpr(
           hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
@@ -72,12 +100,11 @@
       ignoringImpCasts(stringLiteral(hasSize(0))),
       ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
       ignoringImplicit(DefaultConstructor),
-      cxxConstructExpr(
-          hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
-          has(expr(ignoringImpCasts(DefaultConstructor)))),
-      cxxConstructExpr(
-          hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
-          has(expr(ignoringImpCasts(DefaultConstructor)))));
+      cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
+                       has(expr(ignoringImpCasts(DefaultConstructor)))),
+      cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
+                       has(expr(ignoringImpCasts(DefaultConstructor)))),
+      cxxUnresolvedConstructExpr(argummentCountIs(0)));
   // Match the object being compared.
   const auto STLArg =
       anyOf(unaryOperator(
@@ -87,6 +114,7 @@
             expr(hasType(ValidContainer)).bind("STLObject"));
   Finder->addMatcher(
       cxxOperatorCallExpr(
+          unless(isInTemplateInstantiation()),
           hasAnyOverloadedOperatorName("==", "!="),
           anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
                 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
@@ -94,24 +122,33 @@
               cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
           .bind("BinCmp"),
       this);
+  Finder->addMatcher(
+      binaryOperator(hasAnyOperatorName("==", "!="),
+                     anyOf(allOf(hasLHS(WrongComparend), hasRHS(STLArg)),
+                           allOf(hasLHS(STLArg), hasRHS(WrongComparend))),
+                     unless(hasAncestor(
+                         cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
+          .bind("BinCmp"),
+      this);
 }
 
 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
-  const auto *MemberCall =
-      Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
+  const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
+  const auto *MemberCallObject =
+      Result.Nodes.getNodeAs<Expr>("MemberCallObject");
   const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
+  const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
   const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
   const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
   const auto *E =
-      MemberCall
-          ? MemberCall->getImplicitObjectArgument()
+      MemberCallObject
+          ? MemberCallObject
           : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
   FixItHint Hint;
   std::string ReplacementText = std::string(
       Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
                            *Result.SourceManager, getLangOpts()));
-  if (BinCmp && IsBinaryOrTernary(E)) {
-    // Not just a DeclRefExpr, so parenthesize to be on the safe side.
+  if (IsBinaryOrTernary(E) || isa<UnaryOperator>(E)) {
     ReplacementText = "(" + ReplacementText + ")";
   }
   if (E->getType()->isPointerType())
@@ -125,7 +162,13 @@
     }
     Hint =
         FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
-  } else if (BinaryOp) {  // Determine the correct transformation.
+  } else if (BinCmpTempl) {
+    if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
+      ReplacementText = "!" + ReplacementText;
+    }
+    Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
+                                        ReplacementText);
+  } else if (BinaryOp) { // Determine the correct transformation.
     bool Negation = false;
     const bool ContainerIsLHS =
         !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
@@ -195,15 +238,17 @@
                                           "!" + ReplacementText);
   }
 
-  if (MemberCall) {
-    diag(MemberCall->getBeginLoc(),
-         "the 'empty' method should be used to check "
-         "for emptiness instead of 'size'")
+  auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
+
+  if (WarnLoc.isValid()) {
+    diag(WarnLoc, "the 'empty' method should be used to check "
+                  "for emptiness instead of 'size'")
         << Hint;
   } else {
-    diag(BinCmp->getBeginLoc(),
-         "the 'empty' method should be used to check "
-         "for emptiness instead of comparing to an empty object")
+    WarnLoc = BinCmpTempl ? BinCmpTempl->getBeginLoc()
+                          : (BinCmp ? BinCmp->getBeginLoc() : SourceLocation{});
+    diag(WarnLoc, "the 'empty' method should be used to check "
+                  "for emptiness instead of comparing to an empty object")
         << Hint;
   }
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to