| //===--- UniqueptrResetReleaseCheck.cpp - clang-tidy ----------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "UniqueptrResetReleaseCheck.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Lex/Lexer.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace misc { |
| |
| void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) { |
| // Only register the matchers for C++11; the functionality currently does not |
| // provide any benefit to other languages, despite being benign. |
| if (!getLangOpts().CPlusPlus11) |
| return; |
| |
| Finder->addMatcher( |
| cxxMemberCallExpr( |
| on(expr().bind("left")), callee(memberExpr().bind("reset_member")), |
| callee( |
| cxxMethodDecl(hasName("reset"), |
| ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), |
| decl().bind("left_class"))))), |
| has(ignoringParenImpCasts(cxxMemberCallExpr( |
| on(expr().bind("right")), |
| callee(memberExpr().bind("release_member")), |
| callee(cxxMethodDecl( |
| hasName("release"), |
| ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), |
| decl().bind("right_class"))))))))) |
| .bind("reset_call"), |
| this); |
| } |
| |
| namespace { |
| const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result, |
| StringRef ID) { |
| const auto *Class = |
| Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID); |
| if (!Class) |
| return nullptr; |
| auto DeleterArgument = Class->getTemplateArgs()[1]; |
| if (DeleterArgument.getKind() != TemplateArgument::Type) |
| return nullptr; |
| return DeleterArgument.getAsType().getTypePtr(); |
| } |
| |
| bool areDeletersCompatible(const MatchFinder::MatchResult &Result) { |
| const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class"); |
| const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class"); |
| |
| if (LeftDeleterType->getUnqualifiedDesugaredType() == |
| RightDeleterType->getUnqualifiedDesugaredType()) { |
| // Same type. We assume they are compatible. |
| // This check handles the case where the deleters are function pointers. |
| return true; |
| } |
| |
| const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl(); |
| const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl(); |
| if (!LeftDeleter || !RightDeleter) |
| return false; |
| |
| if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) { |
| // Same class. We assume they are compatible. |
| return true; |
| } |
| |
| const auto *LeftAsTemplate = |
| dyn_cast<ClassTemplateSpecializationDecl>(LeftDeleter); |
| const auto *RightAsTemplate = |
| dyn_cast<ClassTemplateSpecializationDecl>(RightDeleter); |
| if (LeftAsTemplate && RightAsTemplate && |
| LeftAsTemplate->getSpecializedTemplate() == |
| RightAsTemplate->getSpecializedTemplate()) { |
| // They are different instantiations of the same template. We assume they |
| // are compatible. |
| // This handles things like std::default_delete<Base> vs. |
| // std::default_delete<Derived>. |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { |
| if (!areDeletersCompatible(Result)) |
| return; |
| |
| const auto *ResetMember = Result.Nodes.getNodeAs<MemberExpr>("reset_member"); |
| const auto *ReleaseMember = |
| Result.Nodes.getNodeAs<MemberExpr>("release_member"); |
| const auto *Right = Result.Nodes.getNodeAs<Expr>("right"); |
| const auto *Left = Result.Nodes.getNodeAs<Expr>("left"); |
| const auto *ResetCall = |
| Result.Nodes.getNodeAs<CXXMemberCallExpr>("reset_call"); |
| |
| std::string LeftText = clang::Lexer::getSourceText( |
| CharSourceRange::getTokenRange(Left->getSourceRange()), |
| *Result.SourceManager, getLangOpts()); |
| std::string RightText = clang::Lexer::getSourceText( |
| CharSourceRange::getTokenRange(Right->getSourceRange()), |
| *Result.SourceManager, getLangOpts()); |
| |
| if (ResetMember->isArrow()) |
| LeftText = "*" + LeftText; |
| if (ReleaseMember->isArrow()) |
| RightText = "*" + RightText; |
| std::string DiagText; |
| // Even if x was rvalue, *x is not rvalue anymore. |
| if (!Right->isRValue() || ReleaseMember->isArrow()) { |
| RightText = "std::move(" + RightText + ")"; |
| DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())"; |
| } else { |
| DiagText = |
| "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())"; |
| } |
| std::string NewText = LeftText + " = " + RightText; |
| |
| diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement( |
| CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText); |
| } |
| |
| } // namespace misc |
| } // namespace tidy |
| } // namespace clang |