1 //===--- UniqueptrResetReleaseCheck.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 "UniqueptrResetReleaseCheck.h"
10 #include "clang/ASTMatchers/ASTMatchFinder.h"
11 #include "clang/Lex/Lexer.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace misc {
18 
registerMatchers(MatchFinder * Finder)19 void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) {
20   // Only register the matchers for C++11; the functionality currently does not
21   // provide any benefit to other languages, despite being benign.
22   if (!getLangOpts().CPlusPlus11)
23     return;
24 
25   Finder->addMatcher(
26       cxxMemberCallExpr(
27           on(expr().bind("left")), callee(memberExpr().bind("reset_member")),
28           callee(
29               cxxMethodDecl(hasName("reset"),
30                             ofClass(cxxRecordDecl(hasName("::std::unique_ptr"),
31                                                   decl().bind("left_class"))))),
32           has(ignoringParenImpCasts(cxxMemberCallExpr(
33               on(expr().bind("right")),
34               callee(memberExpr().bind("release_member")),
35               callee(cxxMethodDecl(
36                   hasName("release"),
37                   ofClass(cxxRecordDecl(hasName("::std::unique_ptr"),
38                                         decl().bind("right_class")))))))))
39           .bind("reset_call"),
40       this);
41 }
42 
43 namespace {
getDeleterForUniquePtr(const MatchFinder::MatchResult & Result,StringRef ID)44 const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result,
45                                    StringRef ID) {
46   const auto *Class =
47       Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID);
48   if (!Class)
49     return nullptr;
50   auto DeleterArgument = Class->getTemplateArgs()[1];
51   if (DeleterArgument.getKind() != TemplateArgument::Type)
52     return nullptr;
53   return DeleterArgument.getAsType().getTypePtr();
54 }
55 
areDeletersCompatible(const MatchFinder::MatchResult & Result)56 bool areDeletersCompatible(const MatchFinder::MatchResult &Result) {
57   const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class");
58   const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class");
59 
60   if (LeftDeleterType->getUnqualifiedDesugaredType() ==
61       RightDeleterType->getUnqualifiedDesugaredType()) {
62     // Same type. We assume they are compatible.
63     // This check handles the case where the deleters are function pointers.
64     return true;
65   }
66 
67   const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl();
68   const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl();
69   if (!LeftDeleter || !RightDeleter)
70     return false;
71 
72   if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) {
73     // Same class. We assume they are compatible.
74     return true;
75   }
76 
77   const auto *LeftAsTemplate =
78       dyn_cast<ClassTemplateSpecializationDecl>(LeftDeleter);
79   const auto *RightAsTemplate =
80       dyn_cast<ClassTemplateSpecializationDecl>(RightDeleter);
81   if (LeftAsTemplate && RightAsTemplate &&
82       LeftAsTemplate->getSpecializedTemplate() ==
83           RightAsTemplate->getSpecializedTemplate()) {
84     // They are different instantiations of the same template. We assume they
85     // are compatible.
86     // This handles things like std::default_delete<Base> vs.
87     // std::default_delete<Derived>.
88     return true;
89   }
90   return false;
91 }
92 
93 } // namespace
94 
check(const MatchFinder::MatchResult & Result)95 void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) {
96   if (!areDeletersCompatible(Result))
97     return;
98 
99   const auto *ResetMember = Result.Nodes.getNodeAs<MemberExpr>("reset_member");
100   const auto *ReleaseMember =
101       Result.Nodes.getNodeAs<MemberExpr>("release_member");
102   const auto *Right = Result.Nodes.getNodeAs<Expr>("right");
103   const auto *Left = Result.Nodes.getNodeAs<Expr>("left");
104   const auto *ResetCall =
105       Result.Nodes.getNodeAs<CXXMemberCallExpr>("reset_call");
106 
107   std::string LeftText = clang::Lexer::getSourceText(
108       CharSourceRange::getTokenRange(Left->getSourceRange()),
109       *Result.SourceManager, getLangOpts());
110   std::string RightText = clang::Lexer::getSourceText(
111       CharSourceRange::getTokenRange(Right->getSourceRange()),
112       *Result.SourceManager, getLangOpts());
113 
114   if (ResetMember->isArrow())
115     LeftText = "*" + LeftText;
116   if (ReleaseMember->isArrow())
117     RightText = "*" + RightText;
118   std::string DiagText;
119   // Even if x was rvalue, *x is not rvalue anymore.
120   if (!Right->isRValue() || ReleaseMember->isArrow()) {
121     RightText = "std::move(" + RightText + ")";
122     DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())";
123   } else {
124     DiagText =
125         "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())";
126   }
127   std::string NewText = LeftText + " = " + RightText;
128 
129   diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement(
130       CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText);
131 }
132 
133 } // namespace misc
134 } // namespace tidy
135 } // namespace clang
136