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