https://github.com/juanvazquez updated https://github.com/llvm/llvm-project/pull/186903
>From a95cdc8dc0c2c7c4aaa540838a08bae6b82efb93 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..a0e2cc767854d 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
