poelmanc created this revision.
poelmanc added reviewers: aaron.ballman, angelgarcia, hokein.
poelmanc added a project: clang-tools-extra.
Herald added a subscriber: yaxunl.
poelmanc requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

As in https://reviews.llvm.org/D95714, `clang-tidy -std=c++20` with 
`modernize-use-nullptr` mistakenly inserts `nullptr` in place of the comparison 
operator if the comparison internally expands in the AST to a rewritten 
spaceship operator. This can be reproduced by running the new 
`modernize-use-nullptr-cxx20.cpp` test without applying the supplied patch to 
UseNullptrCheck.cpp; the current clang-tidy will mistakenly replace:

  result = (a1 < a2);

with

  result = (a1 nullptr a2);

Oops!

The supplied patch fixes this by adding just one comment and one line of code 
to UseNullptrCheck.cpp:

  // Skip implicit casts to 0 generated by operator<=> rewrites.
  unless(hasGrandparent(cxxRewrittenBinaryOperator())),

It also adds the new `hasGrandparent` matcher, which is necessary as opposed to 
`hasAncestor` to properly convert:

  result = (a1 > (ptr == 0 ? a1 : a2));

to

  result = (a1 > (ptr == nullptr ? a1 : a2));

In this case the AST has an "ancestor" that is a rewritten binary operator, but 
not a direct grandparent.

This is an alternative to https://reviews.llvm.org/D95714.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D95726

Files:
  clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
  clang-tools-extra/test/clang-tidy/checkers/modernize-use-nullptr-cxx20.cpp
  clang/include/clang/ASTMatchers/ASTMatchers.h
  clang/include/clang/ASTMatchers/ASTMatchersInternal.h
  clang/lib/ASTMatchers/ASTMatchFinder.cpp

Index: clang/lib/ASTMatchers/ASTMatchFinder.cpp
===================================================================
--- clang/lib/ASTMatchers/ASTMatchFinder.cpp
+++ clang/lib/ASTMatchers/ASTMatchFinder.cpp
@@ -659,6 +659,8 @@
       ResultCache.clear();
     if (MatchMode == AncestorMatchMode::AMM_ParentOnly)
       return matchesParentOf(Node, Matcher, Builder);
+    else if (MatchMode == AncestorMatchMode::AMM_GrandparentOnly)
+      return matchesGrandparentOf(Node, Matcher, Builder);
     return matchesAnyAncestorOf(Node, Ctx, Matcher, Builder);
   }
 
@@ -893,6 +895,18 @@
     return false;
   }
 
+  // Returns whether a direct grandparent of \p Node matches \p Matcher.
+  bool matchesGrandparentOf(const DynTypedNode &Node,
+                                  const DynTypedMatcher &Matcher,
+                                  BoundNodesTreeBuilder *Builder) {
+    for (const auto &Parent : ActiveASTContext->getParents(Node)) {
+      if (matchesParentOf(Parent, Matcher, Builder)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   // Returns whether an ancestor of \p Node matches \p Matcher.
   //
   // The order of matching (which can lead to different nodes being bound in
Index: clang/include/clang/ASTMatchers/ASTMatchersInternal.h
===================================================================
--- clang/include/clang/ASTMatchers/ASTMatchersInternal.h
+++ clang/include/clang/ASTMatchers/ASTMatchersInternal.h
@@ -654,7 +654,10 @@
     AMM_All,
 
     /// Direct parent only.
-    AMM_ParentOnly
+    AMM_ParentOnly,
+
+    /// Direct grandparent only.
+    AMM_GrandparentOnly
   };
 
   virtual ~ASTMatchFinder() = default;
@@ -1654,6 +1657,29 @@
   }
 };
 
+/// Matches nodes of type \c T that have a grandparent node of type
+/// \c GrandparentT for which the given inner matcher matches.
+///
+/// \c GrandparentT must be an AST base type.
+template <typename T, typename GrandparentT>
+class HasGrandparentMatcher : public MatcherInterface<T> {
+  static_assert(IsBaseType<GrandparentT>::value,
+                "has parent only accepts base type matcher");
+
+  const DynTypedMatcher GrandparentMatcher;
+
+public:
+  explicit HasGrandparentMatcher(
+      const Matcher<GrandparentT> &GrandparentMatcher)
+      : GrandparentMatcher(GrandparentMatcher) {}
+
+  bool matches(const T &Node, ASTMatchFinder *Finder,
+               BoundNodesTreeBuilder *Builder) const override {
+    return Finder->matchesAncestorOf(Node, this->GrandparentMatcher, Builder,
+                                     ASTMatchFinder::AMM_GrandparentOnly);
+  }
+};
+
 /// Matches nodes of type \c T that have at least one ancestor node of
 /// type \c AncestorT for which the given inner matcher matches.
 ///
Index: clang/include/clang/ASTMatchers/ASTMatchers.h
===================================================================
--- clang/include/clang/ASTMatchers/ASTMatchers.h
+++ clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -3434,6 +3434,22 @@
     internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc>>
     hasParent;
 
+/// Matches AST nodes that have a grandparent that matches the provided
+/// matcher.
+///
+/// Given
+/// \code
+/// void f() { for (;;) { int x = 42; if (true) { int x = 43; } } }
+/// \endcode
+/// \c compoundStmt(hasGrandparent(forStmt())) matches "{ int x = 43; }".
+///
+/// Usable as: Any Matcher
+extern const internal::ArgumentAdaptingMatcherFunc<
+    internal::HasGrandparentMatcher,
+    internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc>,
+    internal::TypeList<Decl, NestedNameSpecifierLoc, Stmt, TypeLoc>>
+    hasGrandparent;
+
 /// Matches AST nodes that have an ancestor that matches the provided
 /// matcher.
 ///
Index: clang-tools-extra/test/clang-tidy/checkers/modernize-use-nullptr-cxx20.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize-use-nullptr-cxx20.cpp
@@ -0,0 +1,23 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-nullptr %t
+
+#include <compare>
+
+class A {
+public:
+  auto operator<=>(const A &other) const = default;
+};
+
+void test_cxx_rewritten_binary_ops() {
+  A a1, a2;
+  bool result;
+  // should not change next line to (a1 nullptr a2)
+  result = (a1 < a2);
+  // CHECK-FIXES: result = (a1 < a2);
+  // should not change next line to (a1 nullptr a2)
+  result = (a1 >= a2);
+  // CHECK-FIXES: result = (a1 >= a2);
+  int *ptr = 0;
+  // CHECK-FIXES: int *ptr = nullptr;
+  result = (a1 > (ptr == 0 ? a1 : a2));
+  // CHECK-FIXES: result = (a1 > (ptr == nullptr ? a1 : a2));
+}
Index: clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
+++ clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
@@ -45,6 +45,8 @@
       TK_AsIs,
       castExpr(anyOf(ImplicitCastToNull,
                      explicitCastExpr(hasDescendant(ImplicitCastToNull))),
+               // Skip implicit casts to 0 generated by operator<=> rewrites.
+               unless(hasGrandparent(cxxRewrittenBinaryOperator())),
                unless(hasAncestor(explicitCastExpr())))
           .bind(CastSequence));
 }
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to