Skip to content

Commit 42e03bb

Browse files
committed
[clang-tidy] add new check bugprone-reset-ambiguous-call
1 parent 48a66e9 commit 42e03bb

File tree

9 files changed

+552
-0
lines changed

9 files changed

+552
-0
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include "SignedCharMisuseCheck.h"
6565
#include "SizeofContainerCheck.h"
6666
#include "SizeofExpressionCheck.h"
67+
#include "SmartptrResetAmbiguousCallCheck.h"
6768
#include "SpuriouslyWakeUpFunctionsCheck.h"
6869
#include "StandaloneEmptyCheck.h"
6970
#include "StringConstructorCheck.h"
@@ -207,6 +208,8 @@ class BugproneModule : public ClangTidyModule {
207208
"bugprone-sizeof-container");
208209
CheckFactories.registerCheck<SizeofExpressionCheck>(
209210
"bugprone-sizeof-expression");
211+
CheckFactories.registerCheck<SmartptrResetAmbiguousCallCheck>(
212+
"bugprone-smartptr-reset-ambiguous-call");
210213
CheckFactories.registerCheck<SpuriouslyWakeUpFunctionsCheck>(
211214
"bugprone-spuriously-wake-up-functions");
212215
CheckFactories.registerCheck<StandaloneEmptyCheck>(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ add_clang_library(clangTidyBugproneModule STATIC
6565
SizeofContainerCheck.cpp
6666
SizeofExpressionCheck.cpp
6767
SmartPtrArrayMismatchCheck.cpp
68+
SmartptrResetAmbiguousCallCheck.cpp
6869
SpuriouslyWakeUpFunctionsCheck.cpp
6970
StandaloneEmptyCheck.cpp
7071
StringConstructorCheck.cpp
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//===--- SmartptrResetAmbiguousCallCheck.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 "SmartptrResetAmbiguousCallCheck.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::bugprone {
19+
20+
namespace {
21+
22+
AST_MATCHER_P(CallExpr, everyArgumentMatches,
23+
ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
24+
for (const auto *Arg : Node.arguments()) {
25+
if (!InnerMatcher.matches(*Arg, Finder, Builder))
26+
return false;
27+
}
28+
29+
return true;
30+
}
31+
32+
AST_MATCHER(CXXMethodDecl, hasOnlyDefaultParameters) {
33+
for (const auto *Param : Node.parameters()) {
34+
if (!Param->hasDefaultArg())
35+
return false;
36+
}
37+
38+
return true;
39+
}
40+
41+
const auto DefaultSmartPointers = "::std::shared_ptr;::std::unique_ptr";
42+
} // namespace
43+
44+
SmartptrResetAmbiguousCallCheck::SmartptrResetAmbiguousCallCheck(
45+
StringRef Name, ClangTidyContext *Context)
46+
: ClangTidyCheck(Name, Context),
47+
SmartPointers(utils::options::parseStringList(
48+
Options.get("SmartPointers", DefaultSmartPointers))) {}
49+
50+
void SmartptrResetAmbiguousCallCheck::storeOptions(
51+
ClangTidyOptions::OptionMap &Opts) {
52+
Options.store(Opts, "SmartPointers",
53+
utils::options::serializeStringList(SmartPointers));
54+
}
55+
56+
void SmartptrResetAmbiguousCallCheck::registerMatchers(MatchFinder *Finder) {
57+
const auto IsSmartptr = hasAnyName(SmartPointers);
58+
59+
const auto ResetMethod =
60+
cxxMethodDecl(hasName("reset"), hasOnlyDefaultParameters());
61+
62+
const auto TypeWithReset =
63+
anyOf(cxxRecordDecl(hasMethod(ResetMethod)),
64+
classTemplateSpecializationDecl(
65+
hasSpecializedTemplate(classTemplateDecl(has(ResetMethod)))));
66+
67+
const auto SmartptrWithBugproneReset = classTemplateSpecializationDecl(
68+
IsSmartptr,
69+
hasTemplateArgument(
70+
0, templateArgument(refersToType(hasUnqualifiedDesugaredType(
71+
recordType(hasDeclaration(TypeWithReset)))))));
72+
73+
// Find a.reset() calls
74+
Finder->addMatcher(
75+
cxxMemberCallExpr(callee(memberExpr(member(hasName("reset")))),
76+
everyArgumentMatches(cxxDefaultArgExpr()),
77+
on(expr(hasType(SmartptrWithBugproneReset))))
78+
.bind("smartptrResetCall"),
79+
this);
80+
81+
// Find a->reset() calls
82+
Finder->addMatcher(
83+
cxxMemberCallExpr(
84+
callee(memberExpr(
85+
member(ResetMethod),
86+
hasObjectExpression(
87+
cxxOperatorCallExpr(
88+
hasOverloadedOperatorName("->"),
89+
hasArgument(
90+
0, expr(hasType(
91+
classTemplateSpecializationDecl(IsSmartptr)))))
92+
.bind("OpCall")))),
93+
everyArgumentMatches(cxxDefaultArgExpr()))
94+
.bind("objectResetCall"),
95+
this);
96+
}
97+
98+
void SmartptrResetAmbiguousCallCheck::check(
99+
const MatchFinder::MatchResult &Result) {
100+
101+
if (const auto *SmartptrResetCall =
102+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("smartptrResetCall")) {
103+
const auto *Member = cast<MemberExpr>(SmartptrResetCall->getCallee());
104+
105+
diag(SmartptrResetCall->getBeginLoc(),
106+
"be explicit when calling 'reset()' on a smart pointer with a "
107+
"pointee that has a 'reset()' method");
108+
109+
diag(SmartptrResetCall->getBeginLoc(), "assign the pointer to 'nullptr'",
110+
DiagnosticIDs::Note)
111+
<< FixItHint::CreateReplacement(
112+
SourceRange(Member->getOperatorLoc(),
113+
Member->getOperatorLoc().getLocWithOffset(0)),
114+
" =")
115+
<< FixItHint::CreateReplacement(
116+
SourceRange(Member->getMemberLoc(),
117+
SmartptrResetCall->getEndLoc()),
118+
" nullptr");
119+
return;
120+
}
121+
122+
if (const auto *ObjectResetCall =
123+
Result.Nodes.getNodeAs<CXXMemberCallExpr>("objectResetCall")) {
124+
const auto *Arrow = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("OpCall");
125+
126+
const CharSourceRange SmartptrSourceRange =
127+
Lexer::getAsCharRange(Arrow->getArg(0)->getSourceRange(),
128+
*Result.SourceManager, getLangOpts());
129+
130+
diag(ObjectResetCall->getBeginLoc(),
131+
"be explicit when calling 'reset()' on a pointee of a smart pointer");
132+
133+
diag(ObjectResetCall->getBeginLoc(),
134+
"use dereference to call 'reset' method of the pointee",
135+
DiagnosticIDs::Note)
136+
<< FixItHint::CreateInsertion(SmartptrSourceRange.getBegin(), "(*")
137+
<< FixItHint::CreateInsertion(SmartptrSourceRange.getEnd(), ")")
138+
<< FixItHint::CreateReplacement(
139+
CharSourceRange::getCharRange(
140+
Arrow->getOperatorLoc(),
141+
Arrow->getOperatorLoc().getLocWithOffset(2)),
142+
".");
143+
}
144+
}
145+
146+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===--- SmartptrResetAmbiguousCallCheck.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_BUGPRONE_SMARTPTRRESETAMBIGUOUSCALLCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SMARTPTRRESETAMBIGUOUSCALLCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
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/bugprone/smartptr-reset-ambiguous-call.html
21+
class SmartptrResetAmbiguousCallCheck : public ClangTidyCheck {
22+
public:
23+
SmartptrResetAmbiguousCallCheck(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::bugprone
36+
37+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SMARTPTRRESETAMBIGUOUSCALLCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ Improvements to clang-tidy
9191
New checks
9292
^^^^^^^^^^
9393

94+
- New :doc:`bugprone-smartptr-reset-ambiguous-call
95+
<clang-tidy/checks/bugprone/smartptr-reset-ambiguous-call>` check.
96+
97+
Finds potentially erroneous calls to ``reset`` method on smart pointers when
98+
the pointee type also has a ``reset`` method.
99+
94100
New check aliases
95101
^^^^^^^^^^^^^^^^^
96102

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
.. title:: clang-tidy - bugprone-smartptr-reset-ambiguous-call
2+
3+
bugprone-smartptr-reset-ambiguous-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 that are considered are ``std::unique_ptr``,
38+
``std::shared_ptr``. To specify other smart pointers or other classes use the
39+
:option:`SmartPointers` option.
40+
41+
Options
42+
-------
43+
44+
.. option:: SmartPointers
45+
46+
Semicolon-separated list of class names of custom smart pointers.
47+
Default value is `::std::unique_ptr;::std::shared_ptr`.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Clang-Tidy Checks
132132
:doc:`bugprone-signed-char-misuse <bugprone/signed-char-misuse>`,
133133
:doc:`bugprone-sizeof-container <bugprone/sizeof-container>`,
134134
:doc:`bugprone-sizeof-expression <bugprone/sizeof-expression>`,
135+
:doc:`bugprone-smartptr-reset-ambiguous-call <bugprone/smartptr-reset-ambiguous-call>`, "Yes"
135136
:doc:`bugprone-spuriously-wake-up-functions <bugprone/spuriously-wake-up-functions>`,
136137
:doc:`bugprone-standalone-empty <bugprone/standalone-empty>`, "Yes"
137138
:doc:`bugprone-string-constructor <bugprone/string-constructor>`, "Yes"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %check_clang_tidy %s bugprone-smartptr-reset-ambiguous-call %t \
2+
// RUN: -config='{CheckOptions: \
3+
// RUN: {bugprone-smartptr-reset-ambiguous-call.SmartPointers: "::std::unique_ptr;boost::shared_ptr"}}' \
4+
// RUN: --fix-notes --
5+
6+
namespace std {
7+
8+
template <typename T>
9+
struct unique_ptr {
10+
T& operator*() const;
11+
T* operator->() const;
12+
void reset(T* p = nullptr);
13+
};
14+
15+
template <typename T>
16+
struct shared_ptr {
17+
T& operator*() const;
18+
T* operator->() const;
19+
void reset();
20+
void reset(T*);
21+
};
22+
23+
} // namespace std
24+
25+
namespace boost {
26+
27+
template <typename T>
28+
struct shared_ptr {
29+
T& operator*() const;
30+
T* operator->() const;
31+
void reset();
32+
};
33+
34+
} // namespace boost
35+
36+
struct Resettable {
37+
void reset();
38+
void doSomething();
39+
};
40+
41+
void Positive() {
42+
std::unique_ptr<Resettable> u;
43+
u.reset();
44+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: be explicit when calling 'reset()' on a smart pointer with a pointee that has a 'reset()' method
45+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: assign the pointer to 'nullptr'
46+
// CHECK-FIXES: u = nullptr;
47+
u->reset();
48+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: be explicit when calling 'reset()' on a pointee of a smart pointer
49+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: use dereference to call 'reset' method of the pointee
50+
// CHECK-FIXES: (*u).reset();
51+
52+
boost::shared_ptr<Resettable> s;
53+
s.reset();
54+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: be explicit when calling 'reset()' on a smart pointer with a pointee that has a 'reset()' method
55+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: assign the pointer to 'nullptr'
56+
// CHECK-FIXES: s = nullptr;
57+
s->reset();
58+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: be explicit when calling 'reset()' on a pointee of a smart pointer
59+
// CHECK-MESSAGES: :[[@LINE-2]]:3: note: use dereference to call 'reset' method of the pointee
60+
// CHECK-FIXES: (*s).reset();
61+
}
62+
63+
void Negative() {
64+
std::shared_ptr<Resettable> s_ptr;
65+
s_ptr.reset();
66+
s_ptr->reset();
67+
s_ptr->doSomething();
68+
69+
std::unique_ptr<Resettable> u_ptr;
70+
u_ptr.reset(nullptr);
71+
u_ptr->doSomething();
72+
}

0 commit comments

Comments
 (0)