Skip to content

Commit 75f76d4

Browse files
authored
[clang-tidy] Add bugprone-smartptr-reset-ambiguous-call check (#121291)
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()``. Fixes #120908
1 parent bb2e85f commit 75f76d4

File tree

9 files changed

+685
-0
lines changed

9 files changed

+685
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===--- AmbiguousSmartptrResetCallCheck.cpp - clang-tidy -----------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "AmbiguousSmartptrResetCallCheck.h"
10+
#include "../utils/OptionsUtils.h"
11+
#include "clang/AST/ASTContext.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
14+
#include "clang/Lex/Lexer.h"
15+
16+
using namespace clang::ast_matchers;
17+
18+
namespace clang::tidy::readability {
19+
20+
namespace {
21+
22+
AST_MATCHER(CXXMethodDecl, hasOnlyDefaultParameters) {
23+
for (const auto *Param : Node.parameters()) {
24+
if (!Param->hasDefaultArg())
25+
return false;
26+
}
27+
28+
return true;
29+
}
30+
31+
const auto DefaultSmartPointers = "::std::shared_ptr;::std::unique_ptr;"
32+
"::boost::shared_ptr";
33+
} // namespace
34+
35+
AmbiguousSmartptrResetCallCheck::AmbiguousSmartptrResetCallCheck(
36+
StringRef Name, ClangTidyContext *Context)
37+
: ClangTidyCheck(Name, Context),
38+
SmartPointers(utils::options::parseStringList(
39+
Options.get("SmartPointers", DefaultSmartPointers))) {}
40+
41+
void AmbiguousSmartptrResetCallCheck::storeOptions(
42+
ClangTidyOptions::OptionMap &Opts) {
43+
Options.store(Opts, "SmartPointers",
44+
utils::options::serializeStringList(SmartPointers));
45+
}
46+
47+
void AmbiguousSmartptrResetCallCheck::registerMatchers(MatchFinder *Finder) {
48+
const auto IsSmartptr = hasAnyName(SmartPointers);
49+
50+
const auto ResetMethod =
51+
cxxMethodDecl(hasName("reset"), hasOnlyDefaultParameters());
52+
53+
const auto TypeWithReset =
54+
anyOf(cxxRecordDecl(
55+
anyOf(hasMethod(ResetMethod),
56+
isDerivedFrom(cxxRecordDecl(hasMethod(ResetMethod))))),
57+
classTemplateSpecializationDecl(
58+
hasSpecializedTemplate(classTemplateDecl(has(ResetMethod)))));
59+
60+
const auto SmartptrWithReset = expr(hasType(hasUnqualifiedDesugaredType(
61+
recordType(hasDeclaration(classTemplateSpecializationDecl(
62+
IsSmartptr,
63+
hasTemplateArgument(
64+
0, templateArgument(refersToType(hasUnqualifiedDesugaredType(
65+
recordType(hasDeclaration(TypeWithReset))))))))))));
66+
67+
Finder->addMatcher(
68+
cxxMemberCallExpr(
69+
callee(ResetMethod),
70+
unless(hasAnyArgument(expr(unless(cxxDefaultArgExpr())))),
71+
anyOf(on(cxxOperatorCallExpr(hasOverloadedOperatorName("->"),
72+
hasArgument(0, SmartptrWithReset))
73+
.bind("ArrowOp")),
74+
on(SmartptrWithReset)))
75+
.bind("MemberCall"),
76+
this);
77+
}
78+
79+
void AmbiguousSmartptrResetCallCheck::check(
80+
const MatchFinder::MatchResult &Result) {
81+
const auto *MemberCall =
82+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("MemberCall");
83+
assert(MemberCall);
84+
85+
if (const auto *Arrow =
86+
Result.Nodes.getNodeAs<CXXOperatorCallExpr>("ArrowOp")) {
87+
const CharSourceRange SmartptrSourceRange =
88+
Lexer::getAsCharRange(Arrow->getArg(0)->getSourceRange(),
89+
*Result.SourceManager, getLangOpts());
90+
91+
diag(MemberCall->getBeginLoc(),
92+
"ambiguous call to 'reset()' on a pointee of a smart pointer, prefer "
93+
"more explicit approach");
94+
95+
diag(MemberCall->getBeginLoc(),
96+
"consider dereferencing smart pointer to call 'reset' method "
97+
"of the pointee here",
98+
DiagnosticIDs::Note)
99+
<< FixItHint::CreateInsertion(SmartptrSourceRange.getBegin(), "(*")
100+
<< FixItHint::CreateInsertion(SmartptrSourceRange.getEnd(), ")")
101+
<< FixItHint::CreateReplacement(
102+
CharSourceRange::getCharRange(
103+
Arrow->getOperatorLoc(),
104+
Arrow->getOperatorLoc().getLocWithOffset(2)),
105+
".");
106+
} else {
107+
const auto *Member = cast<MemberExpr>(MemberCall->getCallee());
108+
assert(Member);
109+
110+
diag(MemberCall->getBeginLoc(),
111+
"ambiguous call to 'reset()' on a smart pointer with pointee that "
112+
"also has a 'reset()' method, prefer more explicit approach");
113+
114+
diag(MemberCall->getBeginLoc(),
115+
"consider assigning the pointer to 'nullptr' here",
116+
DiagnosticIDs::Note)
117+
<< FixItHint::CreateReplacement(
118+
SourceRange(Member->getOperatorLoc(), Member->getOperatorLoc()),
119+
" =")
120+
<< FixItHint::CreateReplacement(
121+
SourceRange(Member->getMemberLoc(), MemberCall->getEndLoc()),
122+
" nullptr");
123+
}
124+
}
125+
126+
} // namespace clang::tidy::readability
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===--- AmbiguousSmartptrResetCallCheck.h - clang-tidy ---------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AMBIGUOUSSMARTPTRRESETCALLCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AMBIGUOUSSMARTPTRRESETCALLCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::readability {
15+
16+
/// Finds potentially erroneous calls to 'reset' method on smart pointers when
17+
/// the pointee type also has a 'reset' method
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/ambiguous-smartptr-reset-call.html
21+
class AmbiguousSmartptrResetCallCheck : public ClangTidyCheck {
22+
public:
23+
AmbiguousSmartptrResetCallCheck(StringRef Name, ClangTidyContext *Context);
24+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
25+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
26+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus;
29+
}
30+
31+
private:
32+
const std::vector<StringRef> SmartPointers;
33+
};
34+
35+
} // namespace clang::tidy::readability
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AMBIGUOUSSMARTPTRRESETCALLCHECK_H

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
44
)
55

66
add_clang_library(clangTidyReadabilityModule STATIC
7+
AmbiguousSmartptrResetCallCheck.cpp
78
AvoidConstParamsInDecls.cpp
89
AvoidNestedConditionalOperatorCheck.cpp
910
AvoidReturnWithVoidValueCheck.cpp

clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "../ClangTidy.h"
1010
#include "../ClangTidyModule.h"
1111
#include "../ClangTidyModuleRegistry.h"
12+
#include "AmbiguousSmartptrResetCallCheck.h"
1213
#include "AvoidConstParamsInDecls.h"
1314
#include "AvoidNestedConditionalOperatorCheck.h"
1415
#include "AvoidReturnWithVoidValueCheck.h"
@@ -68,6 +69,8 @@ namespace readability {
6869
class ReadabilityModule : public ClangTidyModule {
6970
public:
7071
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
72+
CheckFactories.registerCheck<AmbiguousSmartptrResetCallCheck>(
73+
"readability-ambiguous-smartptr-reset-call");
7174
CheckFactories.registerCheck<AvoidConstParamsInDecls>(
7275
"readability-avoid-const-params-in-decls");
7376
CheckFactories.registerCheck<AvoidNestedConditionalOperatorCheck>(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ New checks
100100
Finds unintended character output from ``unsigned char`` and ``signed char`` to an
101101
``ostream``.
102102

103+
- New :doc:`readability-ambiguous-smartptr-reset-call
104+
<clang-tidy/checks/readability/ambiguous-smartptr-reset-call>` check.
105+
106+
Finds potentially erroneous calls to ``reset`` method on smart pointers when
107+
the pointee type also has a ``reset`` method.
108+
103109
New check aliases
104110
^^^^^^^^^^^^^^^^^
105111

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ Clang-Tidy Checks
353353
:doc:`portability-simd-intrinsics <portability/simd-intrinsics>`,
354354
:doc:`portability-std-allocator-const <portability/std-allocator-const>`,
355355
:doc:`portability-template-virtual-member-function <portability/template-virtual-member-function>`,
356+
:doc:`readability-ambiguous-smartptr-reset-call <readability/ambiguous-smartptr-reset-call>`, "Yes"
356357
:doc:`readability-avoid-const-params-in-decls <readability/avoid-const-params-in-decls>`, "Yes"
357358
:doc:`readability-avoid-nested-conditional-operator <readability/avoid-nested-conditional-operator>`,
358359
:doc:`readability-avoid-return-with-void-value <readability/avoid-return-with-void-value>`, "Yes"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
.. title:: clang-tidy - readability-ambiguous-smartptr-reset-call
2+
3+
readability-ambiguous-smartptr-reset-call
4+
=========================================
5+
6+
Finds potentially erroneous calls to ``reset`` method on smart pointers when
7+
the pointee type also has a ``reset`` method. Having a ``reset`` method in
8+
both classes makes it easy to accidentally make the pointer null when
9+
intending to reset the underlying object.
10+
11+
.. code-block:: c++
12+
13+
struct Resettable {
14+
void reset() { /* Own reset logic */ }
15+
};
16+
17+
auto ptr = std::make_unique<Resettable>();
18+
19+
ptr->reset(); // Calls underlying reset method
20+
ptr.reset(); // Makes the pointer null
21+
22+
Both calls are valid C++ code, but the second one might not be what the
23+
developer intended, as it destroys the pointed-to object rather than resetting
24+
its state. It's easy to make such a typo because the difference between
25+
``.`` and ``->`` is really small.
26+
27+
The recommended approach is to make the intent explicit by using either member
28+
access or direct assignment:
29+
30+
.. code-block:: c++
31+
32+
std::unique_ptr<Resettable> ptr = std::make_unique<Resettable>();
33+
34+
(*ptr).reset(); // Clearly calls underlying reset method
35+
ptr = nullptr; // Clearly makes the pointer null
36+
37+
The default smart pointers and classes that are considered are
38+
``std::unique_ptr``, ``std::shared_ptr``, ``boost::shared_ptr``. To specify
39+
other smart pointers or other classes use the :option:`SmartPointers` option.
40+
41+
42+
.. note::
43+
44+
The check may emit invalid fix-its and misleading warning messages when
45+
specifying custom smart pointers or other classes in the
46+
:option:`SmartPointers` option. For example, ``boost::scoped_ptr`` does not
47+
have an ``operator=`` which makes fix-its invalid.
48+
49+
.. note::
50+
51+
Automatic fix-its are enabled only if :program:`clang-tidy` is invoked with
52+
the `--fix-notes` option.
53+
54+
55+
Options
56+
-------
57+
58+
.. option:: SmartPointers
59+
60+
Semicolon-separated list of fully qualified class names of custom smart
61+
pointers. Default value is `::std::unique_ptr;::std::shared_ptr;
62+
::boost::shared_ptr`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: %check_clang_tidy %s readability-ambiguous-smartptr-reset-call %t -- \
2+
// RUN: -config='{CheckOptions: \
3+
// RUN: {readability-ambiguous-smartptr-reset-call.SmartPointers: "::std::unique_ptr;::other_ptr"}}' \
4+
// RUN: --fix-notes -- -I %S/../modernize/Inputs/smart-ptr
5+
6+
#include "unique_ptr.h"
7+
#include "shared_ptr.h"
8+
9+
template <typename T>
10+
struct other_ptr {
11+
T& operator*() const;
12+
T* operator->() const;
13+
void reset();
14+
};
15+
16+
struct Resettable {
17+
void reset();
18+
void doSomething();
19+
};
20+
21+
void Positive() {
22+
std::unique_ptr<Resettable> u;
23+
u.reset();
24+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: ambiguous call to 'reset()' on a smart pointer with pointee that also has a 'reset()' method, prefer more explicit approach
25+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: consider assigning the pointer to 'nullptr' here
26+
// CHECK-FIXES: u = nullptr;
27+
u->reset();
28+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: ambiguous call to 'reset()' on a pointee of a smart pointer, prefer more explicit approach
29+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: consider dereferencing smart pointer to call 'reset' method of the pointee here
30+
// CHECK-FIXES: (*u).reset();
31+
32+
other_ptr<Resettable> s;
33+
s.reset();
34+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: ambiguous call to 'reset()' on a smart pointer with pointee that also has a 'reset()' method, prefer more explicit approach
35+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: consider assigning the pointer to 'nullptr' here
36+
// CHECK-FIXES: s = nullptr;
37+
s->reset();
38+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: ambiguous call to 'reset()' on a pointee of a smart pointer, prefer more explicit approach
39+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: consider dereferencing smart pointer to call 'reset' method of the pointee here
40+
// CHECK-FIXES: (*s).reset();
41+
}
42+
43+
void Negative() {
44+
std::shared_ptr<Resettable> s_ptr;
45+
s_ptr.reset();
46+
s_ptr->reset();
47+
s_ptr->doSomething();
48+
49+
std::unique_ptr<Resettable> u_ptr;
50+
u_ptr.reset(nullptr);
51+
u_ptr->doSomething();
52+
}

0 commit comments

Comments
 (0)