Skip to content

[clang-tidy] Add bugprone-smartptr-reset-ambiguous-call check #121291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 11, 2025

Conversation

vbvictor
Copy link
Contributor

@vbvictor vbvictor commented Dec 29, 2024

Add new clang-tidy check that finds potentially erroneous calls to reset() method on smart pointers when
the pointee type also has a reset() method.

It's easy to make typo and delete object because the difference between . and -> is really small.

Sometimes IDE's autocomplete will change -> to . automatically. For example, developer wrote ptr->res but after pressing Tab it became ptr.reset().

Small example:

struct Resettable {
  void reset() { /* Own reset logic */ }
};

auto ptr = std::make_unique<Resettable>();

ptr->reset();  // Calls underlying reset method
ptr.reset();   // Makes the pointer null

// After Fix-its
(*ptr).reset();  // Clearly calls underlying reset method
ptr = nullptr;   // Clearly makes the pointer null

Fixes #120908

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Dec 29, 2024

@llvm/pr-subscribers-clang-tidy

@llvm/pr-subscribers-clang-tools-extra

Author: Baranov Victor (vbvictor)

Changes

Add new clang-tidy check that finds potentially erroneous calls to reset() method on smart pointers when
the pointee type also has a reset() method.

It's easy to make typo and delete object because the difference between . and -&gt; is really small.

Sometimes IDE's autocomplete will change -&gt; to . automatically. For example, developer wrote ptr-&gt;res but after Tab it became ptr.reset().

Small example:

struct Resettable {
  void reset() { /* Own reset logic */ }
};

auto ptr = std::make_unique&lt;Resettable&gt;();

ptr-&gt;reset();  // Calls underlying reset method
ptr.reset();   // Makes the pointer null

// After Fix-its
(*ptr).reset();  // Clearly calls underlying reset method
ptr = nullptr;   // Clearly makes the pointer null

Full diff: https://github.com/llvm/llvm-project/pull/121291.diff

8 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+2)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.cpp (+133)
  • (added) clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.h (+34)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+6)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/reset-call.rst (+33)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/reset-call.cpp (+215)
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 33ac65e715ce81..645958e47e22a5 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -57,6 +57,7 @@
 #include "PosixReturnCheck.h"
 #include "RedundantBranchConditionCheck.h"
 #include "ReservedIdentifierCheck.h"
+#include "ResetCallCheck.h"
 #include "ReturnConstRefFromParameterCheck.h"
 #include "SharedPtrArrayMismatchCheck.h"
 #include "SignalHandlerCheck.h"
@@ -144,6 +145,7 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-inaccurate-erase");
     CheckFactories.registerCheck<IncorrectEnableIfCheck>(
         "bugprone-incorrect-enable-if");
+    CheckFactories.registerCheck<ResetCallCheck>("bugprone-reset-call");
     CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
         "bugprone-return-const-ref-from-parameter");
     CheckFactories.registerCheck<SwitchMissingDefaultCaseCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 13adad7c3dadbd..17ab5b27ec5550 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -52,6 +52,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   PosixReturnCheck.cpp
   RedundantBranchConditionCheck.cpp
   ReservedIdentifierCheck.cpp
+  ResetCallCheck.cpp
   ReturnConstRefFromParameterCheck.cpp
   SharedPtrArrayMismatchCheck.cpp
   SignalHandlerCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.cpp
new file mode 100644
index 00000000000000..305ac8d51adf3e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.cpp
@@ -0,0 +1,133 @@
+//===--- ResetCallCheck.cpp - clang-tidy ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ResetCallCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+namespace {
+
+AST_MATCHER_P(CallExpr, everyArgumentMatches,
+              ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
+  if (Node.getNumArgs() == 0)
+    return true;
+
+  for (const auto *Arg : Node.arguments()) {
+    if (!InnerMatcher.matches(*Arg, Finder, Builder))
+      return false;
+  }
+  return true;
+}
+
+AST_MATCHER(CXXMethodDecl, hasOnlyDefaultArgs) {
+  if (Node.param_empty())
+    return true;
+
+  for (const auto *Param : Node.parameters()) {
+    if (!Param->hasDefaultArg())
+      return false;
+  }
+  return true;
+}
+
+} // namespace
+
+void ResetCallCheck::registerMatchers(MatchFinder *Finder) {
+  const auto IsSmartptr = hasAnyName("::std::unique_ptr", "::std::shared_ptr");
+
+  const auto ResetMethod =
+      cxxMethodDecl(hasName("reset"), hasOnlyDefaultArgs());
+
+  const auto TypeWithReset =
+      anyOf(cxxRecordDecl(hasMethod(ResetMethod)),
+            classTemplateSpecializationDecl(
+                hasSpecializedTemplate(classTemplateDecl(has(ResetMethod)))));
+
+  const auto SmartptrWithBugproneReset = classTemplateSpecializationDecl(
+      IsSmartptr,
+      hasTemplateArgument(
+          0, templateArgument(refersToType(hasUnqualifiedDesugaredType(
+                 recordType(hasDeclaration(TypeWithReset)))))));
+
+  // Find a.reset() calls
+  Finder->addMatcher(
+      cxxMemberCallExpr(callee(memberExpr(member(hasName("reset")))),
+                        everyArgumentMatches(cxxDefaultArgExpr()),
+                        on(expr(hasType(SmartptrWithBugproneReset))))
+          .bind("smartptrResetCall"),
+      this);
+
+  // Find a->reset() calls
+  Finder->addMatcher(
+      cxxMemberCallExpr(
+          callee(memberExpr(
+              member(ResetMethod),
+              hasObjectExpression(
+                  cxxOperatorCallExpr(
+                      hasOverloadedOperatorName("->"),
+                      hasArgument(
+                          0, expr(hasType(
+                                 classTemplateSpecializationDecl(IsSmartptr)))))
+                      .bind("OpCall")))),
+          everyArgumentMatches(cxxDefaultArgExpr()))
+          .bind("objectResetCall"),
+      this);
+}
+
+void ResetCallCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *SmartptrResetCall =
+      Result.Nodes.getNodeAs<CXXMemberCallExpr>("smartptrResetCall");
+  const auto *ObjectResetCall =
+      Result.Nodes.getNodeAs<CXXMemberCallExpr>("objectResetCall");
+
+  if (SmartptrResetCall) {
+    const auto *Member = cast<MemberExpr>(SmartptrResetCall->getCallee());
+
+    auto DiagBuilder = diag(SmartptrResetCall->getBeginLoc(),
+                            "bugprone 'reset()' call on smart pointer");
+
+    DiagBuilder << FixItHint::CreateReplacement(
+        SourceRange(Member->getOperatorLoc(),
+                    Member->getOperatorLoc().getLocWithOffset(0)),
+        " =");
+
+    DiagBuilder << FixItHint::CreateReplacement(
+        SourceRange(Member->getMemberLoc(), SmartptrResetCall->getEndLoc()),
+        " nullptr");
+  }
+
+  if (ObjectResetCall) {
+    const auto *Arrow = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("OpCall");
+
+    const auto SmartptrSourceRange =
+        Lexer::getAsCharRange(Arrow->getArg(0)->getSourceRange(),
+                              *Result.SourceManager, getLangOpts());
+
+    auto DiagBuilder = diag(ObjectResetCall->getBeginLoc(),
+                            "bugprone 'reset()' call on smart pointer");
+
+    DiagBuilder << FixItHint::CreateInsertion(SmartptrSourceRange.getBegin(),
+                                              "(*");
+    DiagBuilder << FixItHint::CreateInsertion(SmartptrSourceRange.getEnd(),
+                                              ")");
+
+    DiagBuilder << FixItHint::CreateReplacement(
+        CharSourceRange::getCharRange(
+            Arrow->getOperatorLoc(),
+            Arrow->getOperatorLoc().getLocWithOffset(2)),
+        ".");
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.h b/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.h
new file mode 100644
index 00000000000000..18f6ee4f6bed22
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/ResetCallCheck.h
@@ -0,0 +1,34 @@
+//===--- ResetCallCheck.h - clang-tidy --------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_RESETCALLCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_RESETCALLCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds potentially erroneous calls to 'reset()' method on smart pointers when
+/// the pointee type also has a 'reset()' method
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/reset-call.html
+class ResetCallCheck : public ClangTidyCheck {
+public:
+  ResetCallCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_RESETCALLCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index fa3a8e577a33ad..d125afef0bb039 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -148,6 +148,12 @@ New checks
 
   Finds nondeterministic usages of pointers in unordered containers.
 
+- New :doc:`bugprone-reset-call
+  <clang-tidy/checks/bugprone/reset-call>` check.
+
+  Finds potentially erroneous calls to ``reset()`` method on smart pointers when
+  the pointee type also has a ``reset()`` method.
+
 - New :doc:`bugprone-tagged-union-member-count
   <clang-tidy/checks/bugprone/tagged-union-member-count>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/reset-call.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/reset-call.rst
new file mode 100644
index 00000000000000..87cf955b26b648
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/reset-call.rst
@@ -0,0 +1,33 @@
+.. title:: clang-tidy - bugprone-reset-call
+
+bugprone-reset-call
+===================
+
+Finds calls to ``reset()`` method on smart pointers where the pointee type
+also has a ``reset()`` method, which makes it easy to accidentally
+make the pointer null when intending to reset the underlying object.
+
+.. code-block:: c++
+
+  struct Resettable {
+    void reset() { /* Own reset logic */ }
+  };
+
+  auto ptr = std::make_unique<Resettable>();
+
+  ptr->reset();  // Calls underlying reset method
+  ptr.reset();   // Makes the pointer null
+
+Both calls are valid C++ code, but the second one might not be
+what the developer intended, as it destroys the pointed-to object
+rather than resetting its state.
+It's easy to make such typo because the difference between ``.`` and ``->`` is really small.
+
+The recommended approach is to make the intent explicit by using either member access or direct assignment:
+
+.. code-block:: c++
+
+  std::unique_ptr<Resettable> ptr = std::make_unique<Resettable>();
+
+  (*ptr).reset();  // Clearly calls underlying reset method
+  ptr = nullptr;   // Clearly makes the pointer null
\ No newline at end of file
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 4d8853a0f6d86c..9ca866da74666d 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -124,6 +124,7 @@ Clang-Tidy Checks
    :doc:`bugprone-posix-return <bugprone/posix-return>`, "Yes"
    :doc:`bugprone-redundant-branch-condition <bugprone/redundant-branch-condition>`, "Yes"
    :doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
+   :doc:`bugprone-reset-call <bugprone/reset-call>`, "Yes"
    :doc:`bugprone-return-const-ref-from-parameter <bugprone/return-const-ref-from-parameter>`,
    :doc:`bugprone-shared-ptr-array-mismatch <bugprone/shared-ptr-array-mismatch>`, "Yes"
    :doc:`bugprone-signal-handler <bugprone/signal-handler>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/reset-call.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/reset-call.cpp
new file mode 100644
index 00000000000000..d98f4b463840c7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/reset-call.cpp
@@ -0,0 +1,215 @@
+// RUN: %check_clang_tidy %s bugprone-reset-call %t
+
+namespace std {
+
+template <typename T>
+struct unique_ptr {
+  T& operator*() const;
+  T* operator->() const;
+  void reset(T* p = nullptr);
+};
+
+template <typename T>
+struct shared_ptr {
+  T& operator*() const;
+  T* operator->() const;
+  void reset();
+  void reset(T*);
+};
+
+} // namespace std
+
+struct Resettable {
+  void reset();
+  void doSomething();
+};
+
+struct ResettableWithParam {
+  void reset(int a);
+  void doSomething();
+};
+
+struct ResettableWithDefaultParams {
+  void reset(int a = 0, double b = 0.0);
+  void doSomething();
+};
+
+struct NonResettable {
+  void doSomething();
+};
+
+void Positive() {
+  std::unique_ptr<Resettable> u;
+  u.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: u = nullptr;
+  u->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*u).reset();
+
+  std::shared_ptr<Resettable> s;
+  s.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: s = nullptr;
+  s->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*s).reset();
+
+  std::unique_ptr<std::unique_ptr<int>> uu_ptr;
+  uu_ptr.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: uu_ptr = nullptr;
+  uu_ptr->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*uu_ptr).reset();
+
+  std::unique_ptr<std::shared_ptr<int>> su_ptr;
+  su_ptr.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: su_ptr = nullptr;
+  su_ptr->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*su_ptr).reset();
+
+  std::unique_ptr<ResettableWithDefaultParams> rd;
+  rd.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: rd = nullptr;
+  rd->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*rd).reset();
+
+  std::unique_ptr<std::shared_ptr<std::unique_ptr<Resettable>>> nested_ptr;
+  nested_ptr.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: nested_ptr = nullptr;
+  nested_ptr->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*nested_ptr).reset();
+  (*nested_ptr).reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*nested_ptr) = nullptr;
+  (*nested_ptr)->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*(*nested_ptr)).reset();
+  (**nested_ptr).reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (**nested_ptr) = nullptr;
+  (**nested_ptr)->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*(**nested_ptr)).reset();
+}
+
+void Negative() {
+  std::unique_ptr<Resettable> u_ptr;
+  u_ptr.reset(nullptr);
+  u_ptr->doSomething();
+
+  std::shared_ptr<Resettable> s_ptr;
+  s_ptr.reset(nullptr);
+  s_ptr->doSomething();
+  
+  Resettable* raw_ptr;
+  raw_ptr->reset();
+  raw_ptr->doSomething();
+
+  Resettable resettable;
+  resettable.reset();
+  resettable.doSomething();
+  
+  std::unique_ptr<ResettableWithParam> u_ptr_param;
+  u_ptr_param.reset();
+  u_ptr_param.reset(nullptr);
+  u_ptr_param->reset(0);
+
+  std::unique_ptr<NonResettable> u_ptr_no_reset;
+  u_ptr_no_reset.reset();
+
+  std::shared_ptr<ResettableWithParam> s_ptr_param;
+  s_ptr_param.reset();
+  s_ptr_param->reset(0);
+  s_ptr_param->doSomething();
+
+  std::shared_ptr<NonResettable> s_ptr_no_reset;
+  s_ptr_no_reset.reset();
+
+  std::unique_ptr<ResettableWithDefaultParams> u_ptr_default_params;
+  u_ptr_default_params.reset(nullptr);
+  u_ptr_default_params->reset(1);
+  u_ptr_default_params->reset(1, 2.0);
+}
+
+template <typename T>
+void TemplatePositiveTest() {
+  std::unique_ptr<T> u_ptr;
+
+  u_ptr.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: u_ptr = nullptr;
+  u_ptr->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*u_ptr).reset();
+
+  std::shared_ptr<T> s_ptr;
+  s_ptr.reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: s_ptr = nullptr;
+  s_ptr->reset();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bugprone 'reset()' call on smart pointer
+  // CHECK-FIXES: (*s_ptr).reset();
+}
+
+template <typename T>
+void TemplatNegativeTestTypeWithReset() {
+  std::unique_ptr<T> u_ptr;
+  u_ptr.reset();
+  u_ptr->reset(0);
+
+  std::shared_ptr<T> s_ptr;
+  s_ptr.reset();
+  s_ptr->reset(0);
+}
+
+template <typename T>
+void TemplatNegativeTestTypeWithoutReset() {
+  std::unique_ptr<T> u_ptr;
+  u_ptr.reset();
+
+  std::unique_ptr<T> s_ptr;
+  s_ptr.reset();
+}
+
+void instantiate() {
+  TemplatePositiveTest<Resettable>();
+  TemplatePositiveTest<std::unique_ptr<int>>();
+  TemplatePositiveTest<std::shared_ptr<int>>();
+  TemplatNegativeTestTypeWithReset<ResettableWithParam>();
+  TemplatNegativeTestTypeWithoutReset<NonResettable>();
+}
+
+struct S {
+  void foo() {
+    u_ptr.reset();
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: bugprone 'reset()' call on smart pointer
+    // CHECK-FIXES: u_ptr = nullptr;
+    u_ptr->reset();
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: bugprone 'reset()' call on smart pointer
+    // CHECK-FIXES: (*u_ptr).reset();
+    u_ptr.reset(nullptr);
+    u_ptr->doSomething();
+
+    s_ptr.reset();
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: bugprone 'reset()' call on smart pointer
+    // CHECK-FIXES: s_ptr = nullptr;
+    s_ptr->reset();
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: bugprone 'reset()' call on smart pointer
+    // CHECK-FIXES: (*s_ptr).reset();
+    s_ptr.reset(nullptr);
+
+    ptr.reset();
+  }
+
+  std::unique_ptr<Resettable> u_ptr;
+  std::unique_ptr<std::shared_ptr<int>> s_ptr;
+  std::unique_ptr<NonResettable> ptr;
+};

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Consider TK_IgnoreUnlessSpelledInSource, may simplify matchers
  2. Name is too generic, consider:
  • bugprone-smartptr-reset-pointee-reset
  • bugprone-smartptr-reset-call
  • bugprone-smartptr-pointee-reset
  • bugprone-smartptr-reset-ambiguous-call (my prefered)

Or any simillar.

@vbvictor vbvictor changed the title [clang-tidy] Add bugprone-reset-call check [clang-tidy] Add bugprone-smartptr-reset-ambiguous-call check Dec 29, 2024
@vbvictor
Copy link
Contributor Author

vbvictor commented Dec 29, 2024

  1. Name is too generic, consider:
  • bugprone-smartptr-reset-pointee-reset
  • bugprone-smartptr-reset-call
  • bugprone-smartptr-pointee-reset
  • bugprone-smartptr-reset-ambiguous-call (my prefered)

Or any simillar.

  1. Renamed check to bugprone-smartptr-reset-ambiguous-call.

Copy link
Contributor

@5chmidti 5chmidti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this check is one that we don't want to emit fixes for, at least not in the main diagnostic, but maybe as a fixit attached to a note. Sort of like:

warning: ...
note: (be more explicit and) use '= nullptr'

The diagnostic message is also too generic and does not say what the problem is. One potential wording could be: be explicit when calling 'reset()' on a smart-pointer with a pointee that has a 'reset()' method

What do others think?

@vbvictor vbvictor closed this Dec 31, 2024
@vbvictor vbvictor reopened this Dec 31, 2024
@vbvictor
Copy link
Contributor Author

vbvictor commented Jan 9, 2025

I think that this check is one that we don't want to emit fixes for, at least not in the main diagnostic, but maybe as a fixit attached to a note.

I think the case when reset is called on smart pointer is trivial for a fixit, so it can be in the main diagnostic.

ptr.reset();
// Converts to
ptr = nullptr;

A reset call on an underlying object can be tricky to fix, so a note with fix is a great idea.

The diagnostic message is also too generic and does not say what the problem is. One potential wording could be: be explicit when calling 'reset()' on a smart-pointer with a pointee that has a 'reset()' method

I agree that my wording is a bit confusing, so I will change it to a better one when fixit format is settled.

@vbvictor
Copy link
Contributor Author

Thank you everyone for the feedback!

@PiotrZSL I tried using TK_IgnoreUnlessSpelledInSource with cxxDependentScopeMemberExpr ast-matcher but failed to write a working matcher case with nested template parameters:

template <typename T>
void TemplatePositiveTest() {
  std::unique_ptr<T> u_ptr;

  u_ptr.reset();
  u_ptr->reset();
}

void instantiate() {
  TemplatePositiveTest<std::unique_ptr<int>>();
}

As for now, I'm thinking about leaving it as is and make an NFC change in the future when I get more familiar with matchers.
Apart from TK_IgnoreUnlessSpelledInSource I fixed your comments.

@5chmidti I fixed all your comments and added better warning and note messages:

  s.reset(); // warning: be explicit when calling 'reset()' on a smart pointer with a pointee that has a 'reset()' method
             // note: assign the pointer to 'nullptr'
  s->reset(); // warning: be explicit when calling 'reset()' on a pointee of a smart pointer
              // note: use dereference to call 'reset' method of the pointee

@EugeneZelenko Fixed all pr comments.

@vbvictor vbvictor requested review from 5chmidti and PiotrZSL January 24, 2025 11:13
Copy link
Contributor

@HerrCai0907 HerrCai0907 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@vbvictor
Copy link
Contributor Author

Ping

@EugeneZelenko
Copy link
Contributor

Please rebase from main. Release Notes were cleared after 20 branch creation.

@vbvictor vbvictor force-pushed the bugprone-reset-call branch from 804deb7 to 42e03bb Compare January 31, 2025 16:30
@vbvictor
Copy link
Contributor Author

vbvictor commented Feb 7, 2025

Ping

@vbvictor
Copy link
Contributor Author

@PiotrZSL, @5chmidti Ping

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Overall ok, please work a little bit on an warning message.
  2. Probably better name would be: readability-ambiguous-smartptr-reset-call
  3. Same issue exist on other objects like std::optional, and other methods like std::optionalstd::set and call to emplace. Maybe more checks or more generic check could be constructed where configuration is an pair of "object" and "method".

Consider points 1 & 2. And after that it could be delivered.

Comment on lines 74 to 82
Finder->addMatcher(
cxxMemberCallExpr(callee(memberExpr(member(hasName("reset")))),
everyArgumentMatches(cxxDefaultArgExpr()),
on(expr(hasType(SmartptrWithBugproneReset))))
.bind("smartptrResetCall"),
this);

// Find a->reset() calls
Finder->addMatcher(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could merge both addMatcher by using "anyOf".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I Could not find a good way to use "anyOf" in this context.
If I write like this, it gives CE.

Finder->addMatcher(anyOf(...));

If I write:

Finder->addMatcher(
    cxxMemberCallExpr(
       anyOf(...))
    .bind("MemberCall"));

I get only one bind-name for cxxMemberCallExpr which complicates futher processing.
If there aren't any performance issues, I'd like to leave as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe something like this:

Finder->addMatcher(
      cxxMemberCallExpr(
      	   callee(ResetMethod),
           unless(hasAnyArgument(expr(unless(cxxDefaultArgExpr())))),
           anyOf(
                 thisPointerType(SmartptrWithBugproneReset),
                 on(cxxOperatorCallExpr(
                      hasOverloadedOperatorName("->"),
                      hasArgument(
                          0, expr(hasType(classTemplateSpecializationDecl(IsSmartptr)))))
                   )
           )
       ), this);

Copy link
Contributor Author

@vbvictor vbvictor Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With some modifications made this work,
changed thisPointerType() to on(expr()) because it failed to match some cases with nested ptr's like
std::unique_ptr<std::shared_ptr<std::unique_ptr<Resettable>>> nested_ptr;

@vbvictor
Copy link
Contributor Author

@PiotrZSL, thank you for pointing out better naming pattern for diag-messages, would use it in the future.

  1. Created new warning and fix-it messages.
  2. Renamed check.
  3. As for now, my check can support any type that will have reset method, this include std::optional. Maybe a more general approach would be to detect overlapping methods with classes that are returned via operator* and operator->. However, if some method is called on main object and there is no equivalent of = nullptr I don't know how user can fix such warning.

Comment on lines 74 to 82
Finder->addMatcher(
cxxMemberCallExpr(callee(memberExpr(member(hasName("reset")))),
everyArgumentMatches(cxxDefaultArgExpr()),
on(expr(hasType(SmartptrWithBugproneReset))))
.bind("smartptrResetCall"),
this);

// Find a->reset() calls
Finder->addMatcher(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe something like this:

Finder->addMatcher(
      cxxMemberCallExpr(
      	   callee(ResetMethod),
           unless(hasAnyArgument(expr(unless(cxxDefaultArgExpr())))),
           anyOf(
                 thisPointerType(SmartptrWithBugproneReset),
                 on(cxxOperatorCallExpr(
                      hasOverloadedOperatorName("->"),
                      hasArgument(
                          0, expr(hasType(classTemplateSpecializationDecl(IsSmartptr)))))
                   )
           )
       ), this);

@vbvictor vbvictor requested a review from PiotrZSL February 18, 2025 16:53
@vbvictor
Copy link
Contributor Author

@PiotrZSL, @denzor200
I think we should provide 100% valid fix-its for default SmartPointers. That will be std::shared_ptr, std::unique_ptr, boost::shared_ptr. I created a note in docs that warns users about inconvenience that can happen when non-default smart pointers are specified.
May you please review one more time.
If everything is good i'd need help in merging this pr since i don't have write access to main

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general looks fine.
Few nits.

@@ -0,0 +1,68 @@
// RUN: %check_clang_tidy %s readability-ambiguous-smartptr-reset-call %t \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could merge this file into main test, just have 2 RUN commands there and specify custom suffix for this one (you may need to include current default values also)

Copy link
Contributor Author

@vbvictor vbvictor Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to leave two separate files, they are easier to read and maintain IMHO.

@vbvictor vbvictor force-pushed the bugprone-reset-call branch from 81dd8b0 to 099aacc Compare March 1, 2025 15:07
@vbvictor
Copy link
Contributor Author

vbvictor commented Mar 5, 2025

Since all approves are made, can we land this PR in main?
@HerrCai0907

@vbvictor
Copy link
Contributor Author

vbvictor commented Mar 9, 2025

@HerrCai0907, could you please address #121291 (comment). Thank you!
Can I ask someone to grant me write access to repository?
Since you basically the only one who merges PRs of other people, this could lower amount of work you need to do.
Also, I'd be happy to help new contributors in merging their PRs.

@EugeneZelenko
Copy link
Contributor

@HerrCai0907, could you please address #121291 (comment). Thank you! Can I ask someone to grant me write access to repository? Since you basically the only one who merges PRs of other people, this could lower amount of work you need to do. Also, I'd be happy to help new contributors in merging their PRs.

If you have at least three merged pull requests, you could request commit access using other ones as example.

@PiotrZSL
Copy link
Member

PiotrZSL commented Mar 10, 2025

@vbvictor If you don't want this to be merged as '70346889+vbvictor@users.noreply.github.com', please change your email privacy settings.

@vbvictor
Copy link
Contributor Author

@vbvictor If you don't want this to be merged as '70346889+vbvictor@users.noreply.github.com', please change your email privacy settings.

Thank you for noticing!
I changed my email to public visibility.

@PiotrZSL PiotrZSL merged commit 75f76d4 into llvm:main Mar 11, 2025
12 checks passed
Copy link

@vbvictor Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang-tidy] Check request: don't use reset for a smart pointer to Resetable
7 participants