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