//===--- UniqueptrResetReleaseCheck.cpp - clang-tidy ----------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #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) { 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(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(LeftDeleter); const auto *RightAsTemplate = dyn_cast(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 vs. // std::default_delete. return true; } return false; } } // namespace void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { if (!areDeletersCompatible(Result)) return; const auto *ResetMember = Result.Nodes.getNodeAs("reset_member"); const auto *ReleaseMember = Result.Nodes.getNodeAs("release_member"); const auto *Right = Result.Nodes.getNodeAs("right"); const auto *Left = Result.Nodes.getNodeAs("left"); const auto *ResetCall = Result.Nodes.getNodeAs("reset_call"); std::string LeftText = std::string(clang::Lexer::getSourceText( CharSourceRange::getTokenRange(Left->getSourceRange()), *Result.SourceManager, getLangOpts())); std::string RightText = std::string(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