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
registerMatchers(MatchFinder * Finder)19 void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) {
20 // Only register the matchers for C++11; the functionality currently does not
21 // provide any benefit to other languages, despite being benign.
22 if (!getLangOpts().CPlusPlus11)
23 return;
24
25 Finder->addMatcher(
26 cxxMemberCallExpr(
27 on(expr().bind("left")), callee(memberExpr().bind("reset_member")),
28 callee(
29 cxxMethodDecl(hasName("reset"),
30 ofClass(cxxRecordDecl(hasName("::std::unique_ptr"),
31 decl().bind("left_class"))))),
32 has(ignoringParenImpCasts(cxxMemberCallExpr(
33 on(expr().bind("right")),
34 callee(memberExpr().bind("release_member")),
35 callee(cxxMethodDecl(
36 hasName("release"),
37 ofClass(cxxRecordDecl(hasName("::std::unique_ptr"),
38 decl().bind("right_class")))))))))
39 .bind("reset_call"),
40 this);
41 }
42
43 namespace {
getDeleterForUniquePtr(const MatchFinder::MatchResult & Result,StringRef ID)44 const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result,
45 StringRef ID) {
46 const auto *Class =
47 Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID);
48 if (!Class)
49 return nullptr;
50 auto DeleterArgument = Class->getTemplateArgs()[1];
51 if (DeleterArgument.getKind() != TemplateArgument::Type)
52 return nullptr;
53 return DeleterArgument.getAsType().getTypePtr();
54 }
55
areDeletersCompatible(const MatchFinder::MatchResult & Result)56 bool areDeletersCompatible(const MatchFinder::MatchResult &Result) {
57 const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class");
58 const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class");
59
60 if (LeftDeleterType->getUnqualifiedDesugaredType() ==
61 RightDeleterType->getUnqualifiedDesugaredType()) {
62 // Same type. We assume they are compatible.
63 // This check handles the case where the deleters are function pointers.
64 return true;
65 }
66
67 const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl();
68 const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl();
69 if (!LeftDeleter || !RightDeleter)
70 return false;
71
72 if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) {
73 // Same class. We assume they are compatible.
74 return true;
75 }
76
77 const auto *LeftAsTemplate =
78 dyn_cast<ClassTemplateSpecializationDecl>(LeftDeleter);
79 const auto *RightAsTemplate =
80 dyn_cast<ClassTemplateSpecializationDecl>(RightDeleter);
81 if (LeftAsTemplate && RightAsTemplate &&
82 LeftAsTemplate->getSpecializedTemplate() ==
83 RightAsTemplate->getSpecializedTemplate()) {
84 // They are different instantiations of the same template. We assume they
85 // are compatible.
86 // This handles things like std::default_delete<Base> vs.
87 // std::default_delete<Derived>.
88 return true;
89 }
90 return false;
91 }
92
93 } // namespace
94
check(const MatchFinder::MatchResult & Result)95 void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) {
96 if (!areDeletersCompatible(Result))
97 return;
98
99 const auto *ResetMember = Result.Nodes.getNodeAs<MemberExpr>("reset_member");
100 const auto *ReleaseMember =
101 Result.Nodes.getNodeAs<MemberExpr>("release_member");
102 const auto *Right = Result.Nodes.getNodeAs<Expr>("right");
103 const auto *Left = Result.Nodes.getNodeAs<Expr>("left");
104 const auto *ResetCall =
105 Result.Nodes.getNodeAs<CXXMemberCallExpr>("reset_call");
106
107 std::string LeftText = clang::Lexer::getSourceText(
108 CharSourceRange::getTokenRange(Left->getSourceRange()),
109 *Result.SourceManager, getLangOpts());
110 std::string RightText = clang::Lexer::getSourceText(
111 CharSourceRange::getTokenRange(Right->getSourceRange()),
112 *Result.SourceManager, getLangOpts());
113
114 if (ResetMember->isArrow())
115 LeftText = "*" + LeftText;
116 if (ReleaseMember->isArrow())
117 RightText = "*" + RightText;
118 std::string DiagText;
119 // Even if x was rvalue, *x is not rvalue anymore.
120 if (!Right->isRValue() || ReleaseMember->isArrow()) {
121 RightText = "std::move(" + RightText + ")";
122 DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())";
123 } else {
124 DiagText =
125 "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())";
126 }
127 std::string NewText = LeftText + " = " + RightText;
128
129 diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement(
130 CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText);
131 }
132
133 } // namespace misc
134 } // namespace tidy
135 } // namespace clang
136