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 
UniqueptrResetReleaseCheck(StringRef Name,ClangTidyContext * Context)19 UniqueptrResetReleaseCheck::UniqueptrResetReleaseCheck(
20     StringRef Name, ClangTidyContext *Context)
21     : ClangTidyCheck(Name, Context),
22       Inserter(Options.getLocalOrGlobal("IncludeStyle",
23                                         utils::IncludeSorter::IS_LLVM),
24                areDiagsSelfContained()) {}
25 
storeOptions(ClangTidyOptions::OptionMap & Opts)26 void UniqueptrResetReleaseCheck::storeOptions(
27     ClangTidyOptions::OptionMap &Opts) {
28   Options.store(Opts, "IncludeStyle", Inserter.getStyle());
29 }
30 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)31 void UniqueptrResetReleaseCheck::registerPPCallbacks(
32     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
33   Inserter.registerPreprocessor(PP);
34 }
35 
registerMatchers(MatchFinder * Finder)36 void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) {
37   Finder->addMatcher(
38       cxxMemberCallExpr(
39           callee(memberExpr(
40                      member(cxxMethodDecl(
41                          hasName("reset"),
42                          ofClass(cxxRecordDecl(hasName("::std::unique_ptr"),
43                                                decl().bind("left_class"))))))
44                      .bind("reset_member")),
45           hasArgument(
46               0, ignoringParenImpCasts(cxxMemberCallExpr(
47                      on(expr().bind("right")),
48                      callee(memberExpr(member(cxxMethodDecl(
49                                            hasName("release"),
50                                            ofClass(cxxRecordDecl(
51                                                hasName("::std::unique_ptr"),
52                                                decl().bind("right_class"))))))
53                                 .bind("release_member"))))))
54           .bind("reset_call"),
55       this);
56 }
57 
58 namespace {
getDeleterForUniquePtr(const MatchFinder::MatchResult & Result,StringRef ID)59 const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result,
60                                    StringRef ID) {
61   const auto *Class =
62       Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID);
63   if (!Class)
64     return nullptr;
65   auto DeleterArgument = Class->getTemplateArgs()[1];
66   if (DeleterArgument.getKind() != TemplateArgument::Type)
67     return nullptr;
68   return DeleterArgument.getAsType().getTypePtr();
69 }
70 
areDeletersCompatible(const MatchFinder::MatchResult & Result)71 bool areDeletersCompatible(const MatchFinder::MatchResult &Result) {
72   const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class");
73   const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class");
74 
75   if (LeftDeleterType->getUnqualifiedDesugaredType() ==
76       RightDeleterType->getUnqualifiedDesugaredType()) {
77     // Same type. We assume they are compatible.
78     // This check handles the case where the deleters are function pointers.
79     return true;
80   }
81 
82   const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl();
83   const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl();
84   if (!LeftDeleter || !RightDeleter)
85     return false;
86 
87   if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) {
88     // Same class. We assume they are compatible.
89     return true;
90   }
91 
92   const auto *LeftAsTemplate =
93       dyn_cast<ClassTemplateSpecializationDecl>(LeftDeleter);
94   const auto *RightAsTemplate =
95       dyn_cast<ClassTemplateSpecializationDecl>(RightDeleter);
96   if (LeftAsTemplate && RightAsTemplate &&
97       LeftAsTemplate->getSpecializedTemplate() ==
98           RightAsTemplate->getSpecializedTemplate()) {
99     // They are different instantiations of the same template. We assume they
100     // are compatible.
101     // This handles things like std::default_delete<Base> vs.
102     // std::default_delete<Derived>.
103     return true;
104   }
105   return false;
106 }
107 
108 } // namespace
109 
check(const MatchFinder::MatchResult & Result)110 void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) {
111   if (!areDeletersCompatible(Result))
112     return;
113 
114   const auto *ResetMember = Result.Nodes.getNodeAs<MemberExpr>("reset_member");
115   const auto *ReleaseMember =
116       Result.Nodes.getNodeAs<MemberExpr>("release_member");
117   const auto *Right = Result.Nodes.getNodeAs<Expr>("right");
118   const auto *ResetCall =
119       Result.Nodes.getNodeAs<CXXMemberCallExpr>("reset_call");
120 
121   StringRef AssignmentText = " = ";
122   StringRef TrailingText = "";
123   bool NeedsUtilityInclude = false;
124   if (ReleaseMember->isArrow()) {
125     AssignmentText = " = std::move(*";
126     TrailingText = ")";
127     NeedsUtilityInclude = true;
128   } else if (!Right->isPRValue()) {
129     AssignmentText = " = std::move(";
130     TrailingText = ")";
131     NeedsUtilityInclude = true;
132   }
133 
134   auto D = diag(ResetMember->getExprLoc(),
135                 "prefer 'unique_ptr<>' assignment over 'release' and 'reset'");
136   if (ResetMember->isArrow())
137     D << FixItHint::CreateInsertion(ResetMember->getBeginLoc(), "*");
138   D << FixItHint::CreateReplacement(
139            CharSourceRange::getCharRange(ResetMember->getOperatorLoc(),
140                                          Right->getBeginLoc()),
141            AssignmentText)
142     << FixItHint::CreateReplacement(
143            CharSourceRange::getTokenRange(ReleaseMember->getOperatorLoc(),
144                                           ResetCall->getEndLoc()),
145            TrailingText);
146   if (NeedsUtilityInclude)
147     D << Inserter.createIncludeInsertion(
148         Result.SourceManager->getFileID(ResetMember->getBeginLoc()),
149         "<utility>");
150 }
151 } // namespace misc
152 } // namespace tidy
153 } // namespace clang
154