https://github.com/juanvazquez updated 
https://github.com/llvm/llvm-project/pull/186903

>From 23b5fd1cda6d17b541c58c7eb5b79a5fe91474e3 Mon Sep 17 00:00:00 2001
From: Juan Vazquez <[email protected]>
Date: Mon, 16 Mar 2026 22:44:32 +0100
Subject: [PATCH] [clang-tidy] use-after-move: Support null_after_move
 annotations

Extend the bugprone-use-after-move check to recognize user defined
smart-pointer-like types that make guarantees on the state of a moved-from
object, leaving it in a valid and specified state that matches the standard
smart pointer's moved-from state (nullptr), where it is safe to use but not
dereference.

Following the RFC discussion:

* Use [[clang::annotate]] to mark the types.
* Use an schema for the [[clang::annotate]] annotation and arguments to help
  avoid conflicts with other users of the attribute.
* The annotation will identify the tool ("clang-tidy") and the arguments the
  plugin ("bugprone-use-after-move"), and the behavior of the type
  ("null_after_move"). E.g.:

[[clang::annotate("clang-tidy", "bugprone-use-after-move", "null_after_move")]]

RFC: 
https://discourse.llvm.org/t/rfc-add-a-class-attribute-clang-null-after-move-for-use-after-move-analysis/89760
---
 .../clang-tidy/bugprone/UseAfterMoveCheck.cpp |  47 +++++++-
 .../checks/bugprone/use-after-move.rst        |   7 +-
 .../checkers/bugprone/use-after-move.cpp      | 101 ++++++++++++++++++
 3 files changed, 149 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
index 8e9f48ee0cd21..91f8bc06bf002 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -8,11 +8,14 @@
 
 #include "UseAfterMoveCheck.h"
 
+#include "clang/AST/Attr.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
+#include "clang/AST/Type.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
 #include "clang/Analysis/CFG.h"
+#include "clang/Basic/LLVM.h"
 #include "clang/Lex/Lexer.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallPtrSet.h"
@@ -323,7 +326,32 @@ void UseAfterMoveFinder::getUsesAndReinits(
   });
 }
 
-static bool isStandardSmartPointer(const ValueDecl *VD) {
+static std::optional<StringRef> getStringLiteral(const Expr *E) {
+  if (!E)
+    return std::nullopt;
+  if (const auto *SL = dyn_cast<StringLiteral>(E->IgnoreParenImpCasts()))
+    return SL->getString();
+  return std::nullopt;
+}
+
+// User defined types can use [[clang::annotate]] to mark smart-pointer-like
+// types with a specified move from state that matches the standard smart
+// pointer's moved-from state (nullptr).
+static bool isNullAfterMoveAnnotate(const AnnotateAttr *Attr) {
+  if (Attr->getAnnotation() != "clang-tidy")
+    return false;
+
+  if (Attr->args_size() != 2)
+    return false;
+
+  std::optional<StringRef> Plugin = getStringLiteral(Attr->args_begin()[0]);
+  std::optional<StringRef> Annotation = 
getStringLiteral(Attr->args_begin()[1]);
+
+  return Plugin && Annotation && *Plugin == "bugprone-use-after-move" &&
+         *Annotation == "null_after_move";
+}
+
+static bool isSpecifiedAfterMove(const ValueDecl *VD) {
   const Type *TheType = VD->getType().getNonReferenceType().getTypePtrOrNull();
   if (!TheType)
     return false;
@@ -332,6 +360,16 @@ static bool isStandardSmartPointer(const ValueDecl *VD) {
   if (!RecordDecl)
     return false;
 
+  // Use the definition for the declaration, as it is the expected place to add
+  // the annotations.
+  const CXXRecordDecl *DefinitionDecl = RecordDecl->getDefinition();
+  if (DefinitionDecl != nullptr) {
+    for (const auto *Attr : DefinitionDecl->specific_attrs<AnnotateAttr>())
+      if (isNullAfterMoveAnnotate(Attr))
+        return true;
+  }
+
+  // Standard smart pointers have a well-specified moved-from state (nullptr).
   const IdentifierInfo *ID = RecordDecl->getIdentifier();
   if (!ID)
     return false;
@@ -358,9 +396,10 @@ void UseAfterMoveFinder::getDeclRefs(
         const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
         const auto *Operator = 
Match.getNodeAs<CXXOperatorCallExpr>("operator");
         if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) {
-          // Ignore uses of a standard smart pointer that don't dereference the
-          // pointer.
-          if (Operator || !isStandardSmartPointer(DeclRef->getDecl()))
+          // Ignore uses of a standard smart pointer or classes annotated as
+          // "null_after_move" (smart-pointer-like behavior) that don't
+          // dereference the pointer.
+          if (Operator || !isSpecifiedAfterMove(DeclRef->getDecl()))
             DeclRefs->insert(DeclRef);
         }
       }
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst
index 3218b32ce2c58..787b3a98026e7 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst
@@ -195,8 +195,6 @@ Use
 Any occurrence of the moved variable that is not a reinitialization (see below)
 is considered to be a use.
 
-An exception to this are objects of type ``std::unique_ptr``,
-``std::shared_ptr``, ``std::weak_ptr``, ``std::optional``, and ``std::any``.
 An exception to this are objects of type ``std::unique_ptr``,
 ``std::shared_ptr``, ``std::weak_ptr``, ``std::optional``, and ``std::any``,
 which can be reinitialized via ``reset``. For smart pointers specifically, the
@@ -204,6 +202,11 @@ moved-from objects have a well-defined state of being 
``nullptr``s, and only
 ``operator*``, ``operator->`` and ``operator[]`` are considered bad accesses as
 they would be dereferencing a ``nullptr``.
 
+User-defined types can be annotated as having the same semantics as standard
+smart pointers with ``[[clang::annotate("clang-tidy", 
"bugprone-use-after-move",
+"null_after_move")]]``. This expresses that a moved-from object of this type is
+a null pointer.
+
 If multiple uses occur after a move, only the first of these is flagged.
 
 Reinitialization
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
index 5d95c44fc318f..6a7b4a5614b88 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
@@ -1023,6 +1023,107 @@ void reinitAnnotation() {
   }
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Tests for annotations on smart-pointer-like types
+
+namespace null_after_move {
+
+template <typename T>
+class [[clang::annotate("clang-tidy",
+                        "bugprone-use-after-move",
+                        "null_after_move")]] SmartPtrAlike {
+public:
+  SmartPtrAlike();
+  ~SmartPtrAlike();
+  SmartPtrAlike(const SmartPtrAlike&) = delete;
+  SmartPtrAlike &operator=(const SmartPtrAlike&) = delete;
+  SmartPtrAlike(SmartPtrAlike&& other);
+  SmartPtrAlike &operator=(SmartPtrAlike&& other);
+  T* get() const;
+  T& operator*() const;
+};
+
+// Don't flag uses of smart-pointer-like types correctly annotated, unless it's
+// a dereference.
+void smartPointerLikeTypeUseAfterMove() {
+  SmartPtrAlike<int> ptr;
+  ptr.get();
+  SmartPtrAlike<int> other_ptr = std::move(ptr);
+  ptr.get();
+  int inv_value = *ptr;
+  // CHECK-NOTES: [[@LINE-1]]:20: warning: 'ptr' used after it was moved
+  // CHECK-NOTES: [[@LINE-4]]:34: note: move occurred here
+}
+
+class [[clang::annotate("other"),
+      clang::annotate("clang-tidy",
+                      "bugprone-use-after-move",
+                      "null_after_move")]] MultipleAnnotationsType {
+public:
+  MultipleAnnotationsType();
+  ~MultipleAnnotationsType();
+  MultipleAnnotationsType(const MultipleAnnotationsType&) = delete;
+  MultipleAnnotationsType &operator=(const MultipleAnnotationsType&) = delete;
+  MultipleAnnotationsType(MultipleAnnotationsType&& other);
+  MultipleAnnotationsType &operator=(MultipleAnnotationsType&& other);
+  int* get() const;
+  int& operator*() const;
+};
+
+// Handle smart-pointer-like types correctly annotated, even in the case of
+// multiple annotations.
+void typeWithMultipleAnnotations() {
+  MultipleAnnotationsType ptr;
+  ptr.get();
+  MultipleAnnotationsType other_ptr = std::move(ptr);
+  ptr.get();
+  int inv_value = *ptr;
+  // CHECK-NOTES: [[@LINE-1]]:20: warning: 'ptr' used after it was moved
+  // CHECK-NOTES: [[@LINE-4]]:39: note: move occurred here
+}
+
+class [[clang::annotate("null_after_move")]] BadAnnotation {
+public:
+  BadAnnotation();
+  ~BadAnnotation();
+  BadAnnotation(const BadAnnotation&) = delete;
+  BadAnnotation &operator=(const BadAnnotation&) = delete;
+  BadAnnotation(BadAnnotation&& other);
+  BadAnnotation &operator=(BadAnnotation&& other);
+  int* get() const;
+};
+
+// Flag uses of smart-pointer-like types with incorrect annotate.
+void badUseAfterMoveAnnotationIgnored() {
+  BadAnnotation ptr;
+  BadAnnotation other_ptr = std::move(ptr);
+  ptr.get();
+  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'ptr' used after it was moved
+  // CHECK-NOTES: [[@LINE-3]]:29: note: move occurred here
+}
+
+class [[clang::annotate("clang-tidy", "null_after_move")]] BadAnnotationArgs {
+public:
+  BadAnnotationArgs();
+  ~BadAnnotationArgs();
+  BadAnnotationArgs(const BadAnnotationArgs&) = delete;
+  BadAnnotationArgs &operator=(const BadAnnotationArgs&) = delete;
+  BadAnnotationArgs(BadAnnotationArgs&& other);
+  BadAnnotationArgs &operator=(BadAnnotationArgs&& other);
+  int* get() const;
+};
+
+// Flag uses of smart-pointer-like types with wrong annotate arguments.
+void UseAfterMoveAnnotationWithBadArgsIgnored() {
+  BadAnnotationArgs ptr;
+  BadAnnotationArgs other_ptr = std::move(ptr);
+  ptr.get();
+  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'ptr' used after it was moved
+  // CHECK-NOTES: [[@LINE-3]]:33: note: move occurred here
+}
+
+} // namespace null_after_move
+
 
////////////////////////////////////////////////////////////////////////////////
 // Tests related to order of evaluation within expressions
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to