Extend the check to detect patterns like 'ptr.get() == nullptr'

Summary:
Extend the check to detect patterns like 'ptr.get() == nullptr'
It detects == and != when any argument is a ptr.get() and the other is a
nullptr.
Only supports standard smart pointer types std::unique_ptr and std::shared_ptr.
Does not support the case 'ptr.get() == other.get()' yet.

Reviewers: djasper

CC: cfe-commits, alexfh

Differential Revision: http://llvm-reviews.chandlerc.com/D3294

llvm-svn: 205854
This commit is contained in:
Samuel Benzaquen 2014-04-09 14:17:23 +00:00
parent b55248278f
commit 110f3cc45a
5 changed files with 102 additions and 42 deletions

View File

@ -16,8 +16,19 @@ using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
void RedundantSmartptrGet::registerMatchers(MatchFinder *Finder) {
namespace {
internal::Matcher<Expr> callToGet(internal::Matcher<Decl> OnClass) {
return memberCallExpr(
on(expr(anyOf(hasType(OnClass),
hasType(qualType(pointsTo(decl(OnClass).bind(
"ptr_to_ptr")))))).bind("smart_pointer")),
callee(methodDecl(hasName("get")))).bind("redundant_get");
}
void registerMatchersForGetArrowStart(MatchFinder *Finder,
MatchFinder::MatchCallback *Callback) {
const auto QuacksLikeASmartptr = recordDecl(
recordDecl().bind("duck_typing"),
has(methodDecl(hasName("operator->"),
returns(qualType(pointsTo(type().bind("op->Type")))))),
has(methodDecl(hasName("operator*"),
@ -25,35 +36,50 @@ void RedundantSmartptrGet::registerMatchers(MatchFinder *Finder) {
has(methodDecl(hasName("get"),
returns(qualType(pointsTo(type().bind("getType")))))));
const auto CallToGet =
memberCallExpr(on(expr(hasType(recordDecl(QuacksLikeASmartptr)))
.bind("smart_pointer")),
callee(methodDecl(hasName("get")))).bind("redundant_get");
const auto ArrowCallToGet =
memberCallExpr(
on(expr(hasType(qualType(pointsTo(recordDecl(QuacksLikeASmartptr)))))
.bind("smart_pointer")),
callee(methodDecl(hasName("get")))).bind("redundant_get");
// Catch 'ptr.get()->Foo()'
Finder->addMatcher(
memberExpr(isArrow(), hasObjectExpression(ignoringImpCasts(CallToGet))),
this);
Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(),
hasObjectExpression(ignoringImpCasts(
callToGet(QuacksLikeASmartptr)))),
Callback);
// Catch '*ptr.get()'
// Catch '*ptr.get()' or '*ptr->get()'
Finder->addMatcher(
unaryOperator(hasOperatorName("*"), hasUnaryOperand(CallToGet)), this);
unaryOperator(hasOperatorName("*"),
hasUnaryOperand(callToGet(QuacksLikeASmartptr))),
Callback);
}
// Catch '*ptr->get()'
void registerMatchersForGetEquals(MatchFinder *Finder,
MatchFinder::MatchCallback *Callback) {
// This one is harder to do with duck typing.
// The operator==/!= that we are looking for might be member or non-member,
// might be on global namespace or found by ADL, might be a template, etc.
// For now, lets keep a list of known standard types.
const auto IsAKnownSmartptr = recordDecl(
anyOf(hasName("::std::unique_ptr"), hasName("::std::shared_ptr")));
// Matches against nullptr.
Finder->addMatcher(
unaryOperator(hasOperatorName("*"), hasUnaryOperand(ArrowCallToGet))
.bind("ptr_to_ptr"),
this);
binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
hasEitherOperand(ignoringImpCasts(nullPtrLiteralExpr())),
hasEitherOperand(callToGet(IsAKnownSmartptr))),
Callback);
// TODO: Catch ptr.get() == other_ptr.get()
}
} // namespace
void RedundantSmartptrGet::registerMatchers(MatchFinder *Finder) {
registerMatchersForGetArrowStart(Finder, this);
registerMatchersForGetEquals(Finder, this);
}
namespace {
bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) {
if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr)
return true;
// Verify that the types match.
// We can't do this on the matcher because the type nodes can be different,
// even though they represent the same type. This difference comes from how
@ -71,14 +97,20 @@ bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) {
void RedundantSmartptrGet::check(const MatchFinder::MatchResult &Result) {
if (!allReturnTypesMatch(Result)) return;
bool IsPtrToPtr = Result.Nodes.getNodeAs<Expr>("ptr_to_ptr") != nullptr;
bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr;
bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr;
const Expr *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get");
const Expr *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer");
if (IsPtrToPtr && IsMemberExpr) {
// Ignore this case (eg. Foo->get()->DoSomething());
return;
}
StringRef SmartptrText = Lexer::getSourceText(
CharSourceRange::getTokenRange(Smartptr->getSourceRange()),
*Result.SourceManager, LangOptions());
// Replace *foo->get() with **foo, and foo.get() with foo.
// Replace foo->get() with *foo, and foo.get() with foo.
std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str();
diag(GetCall->getLocStart(), "Redundant get() call on smart pointer.")
<< FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement);

View File

@ -0,0 +1,12 @@
#!/bin/sh
#
# Run clang-tidy in fix mode and verify the result.
INPUT_FILE=$1
CHECK_TO_RUN=$2
TEMPORARY_FILE=$3.cpp
grep -Ev "// *[A-Z-]+:" ${INPUT_FILE} > ${TEMPORARY_FILE}
clang-tidy ${TEMPORARY_FILE} -fix --checks=${CHECK_TO_RUN} \
--disable-checks="" -- --std=c++11
FileCheck -input-file=${TEMPORARY_FILE} ${INPUT_FILE}

View File

@ -0,0 +1,10 @@
#!/bin/sh
#
# Run clang-tidy and verify its command line output.
INPUT_FILE=$1
CHECK_TO_RUN=$2
clang-tidy --checks=${CHECK_TO_RUN} --disable-checks="" ${INPUT_FILE} \
-- --std=c++11 \
| FileCheck ${INPUT_FILE}

View File

@ -1,6 +1,6 @@
// RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp
// RUN: clang-tidy %t.cpp -fix -checks=misc-redundant-smartptr-get --
// RUN: FileCheck -input-file=%t.cpp %s
// RUN: $(dirname %s)/check_clang_tidy_fix.sh %s misc-redundant-smartptr-get %t
#include <memory>
struct Bar {
void Do();
@ -39,4 +39,11 @@ void Positive() {
int_ptr ip;
int i = *ip.get();
// CHECK: int i = *ip;
std::unique_ptr<int> uu;
std::shared_ptr<double> *ss;
bool bb = uu.get() == nullptr;
// CHECK: bool bb = uu == nullptr;
bb = nullptr != ss->get();
// CHECK: bb = nullptr != *ss;
}

View File

@ -1,22 +1,8 @@
// RUN: clang-tidy --checks=misc-redundant-smartptr-get %s -- | FileCheck %s
// RUN: $(dirname %s)/check_clang_tidy_output.sh %s misc-redundant-smartptr-get
// CHECK-NOT: warning
namespace std {
template <typename T>
struct MakeRef {
typedef T& type;
};
template <typename T>
struct unique_ptr {
T* get();
T* operator->();
// This simulates libstdc++'s implementation of unique_ptr.
typename MakeRef<T>::type operator*();
};
} // namespace std
#include <memory>
struct int_ptr {
int* get();
@ -65,6 +51,14 @@ void Positive() {
int i = *ip.get();
// CHECK: :[[@LINE-1]]:12: warning: Redundant get() call on smart pointer.
// CHECK: int i = *ip.get();
bool bb = u.get() == nullptr;
// CHECK: :[[@LINE-1]]:13: warning: Redundant get() call on smart pointer.
// CHECK: u.get() == nullptr;
std::shared_ptr<double> *sp;
bb = nullptr != sp->get();
// CHECK: :[[@LINE-1]]:19: warning: Redundant get() call on smart pointer.
// CHECK: nullptr != sp->get();
}
// CHECK-NOT: warning
@ -77,4 +71,9 @@ void Negative() {
Fail2().get()->Do();
const Bar& b = *Fail1().get();
(*Fail2().get()).Do();
int_ptr ip;
bool bb = std::unique_ptr<int>().get() == NULL;
bb = ip.get() == nullptr;
bb = u->get() == NULL;
}