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