[clang-tidy] Fix segfault in bugprone-standalone-empty
authorDenis Nikitin <denik@google.com>
Wed, 25 Jan 2023 19:58:38 +0000 (19:58 +0000)
committerChristopher Di Bella <cjdb@google.com>
Wed, 25 Jan 2023 20:06:41 +0000 (20:06 +0000)
The check incorrectly identified empty() method call in the template
class definition as a stand-alone function call.
This led to a crash because the checker did not expect empty() function
calls without arguments.

Fixes: https://github.com/llvm/llvm-project/issues/59487
Reviewed By: cjdb

Differential Revision: https://reviews.llvm.org/D142423

clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp
clang-tools-extra/docs/clang-tidy/checks/bugprone/standalone-empty.rst
clang-tools-extra/test/clang-tidy/checkers/bugprone/standalone-empty.cpp

index 67d8e3693fb568dd6f5031f7f1094d9acc25a80c..a66f838f1c8fa315b3c853f2a6e30614ab9753e2 100644 (file)
@@ -29,10 +29,12 @@ namespace clang::tidy::bugprone {
 using ast_matchers::BoundNodes;
 using ast_matchers::callee;
 using ast_matchers::callExpr;
+using ast_matchers::classTemplateDecl;
 using ast_matchers::cxxMemberCallExpr;
 using ast_matchers::cxxMethodDecl;
 using ast_matchers::expr;
 using ast_matchers::functionDecl;
+using ast_matchers::hasAncestor;
 using ast_matchers::hasName;
 using ast_matchers::hasParent;
 using ast_matchers::ignoringImplicit;
@@ -70,10 +72,13 @@ const Expr *getCondition(const BoundNodes &Nodes, const StringRef NodeId) {
 }
 
 void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
+  // Ignore empty calls in a template definition which fall under callExpr
+  // non-member matcher even if they are methods.
   const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(
       callExpr(
           hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
                         .bind("parent")),
+          unless(hasAncestor(classTemplateDecl())),
           callee(functionDecl(hasName("empty"), unless(returns(voidType())))))
           .bind("empty"))));
   const auto MemberMatcher =
@@ -154,6 +159,8 @@ void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) {
       return;
     if (ParentReturnStmt)
       return;
+    if (NonMemberCall->getNumArgs() != 1)
+      return;
 
     SourceLocation NonMemberLoc = NonMemberCall->getExprLoc();
     SourceLocation NonMemberEndLoc = NonMemberCall->getEndLoc();
index 049698a8b0f7ad85fc10f6456d59a02073b6d485..80805bafddd50aa53b439689196bda78cbe8e70e 100644 (file)
@@ -3,8 +3,8 @@
 bugprone-standalone-empty
 =========================
 
-Warns when `empty()` is used on a range and the result is ignored. Suggests 
-`clear()` if it is an existing member function.
+Warns when ``empty()`` is used on a range and the result is ignored. Suggests
+``clear()`` if it is an existing member function.
 
 The ``empty()`` method on several common ranges returns a Boolean indicating
 whether or not the range is empty, but is often mistakenly interpreted as
@@ -29,3 +29,8 @@ A call to ``clear()`` would appropriately clear the contents of the range:
   std::vector<int> v;
   ...
   v.clear();
+
+Limitations:
+- Doesn't warn if ``empty()`` is defined and used with the ignore result in the
+  class template definition (for example in the library implementation). These
+  error cases can be caught with ``[[nodiscard]]`` attribute.
index d7615fdbbe922f3a7610d25df65aabc3873cccd8..53c651879f84bafddd1d298815b5d0527e9a5460 100644 (file)
@@ -80,6 +80,10 @@ template <class T>
 void empty(T &&);
 } // namespace test
 
+namespace test_no_args {
+bool empty();
+} // namespace test_no_args
+
 namespace base {
 template <typename T>
 struct base_vector {
@@ -306,6 +310,9 @@ bool test_qualified_empty() {
 
     test::empty(v);
     // no-warning
+
+    test_no_args::empty();
+    // no-warning
   }
 
   {
@@ -876,3 +883,24 @@ bool test_clear_with_qualifiers() {
     // no-warning
   }
 }
+
+namespace user_lib {
+template <typename T>
+struct vector {
+  bool empty();
+  bool test_empty_inside_impl() {
+    empty();
+    // no-warning
+    return empty();
+    // no-warning
+  }
+};
+} // namespace user_lib
+
+bool test_template_empty_outside_impl() {
+  user_lib::vector<int> v;
+  v.empty();
+  // CHECK-MESSAGES: :[[#@LINE-1]]:3: warning: ignoring the result of 'empty()' [bugprone-standalone-empty]
+  return v.empty();
+  // no-warning
+}