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