//===--- MakeSmartPtrCheck.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 "../utils/TypeTraits.h" #include "MakeSharedCheck.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace modernize { namespace { constexpr char ConstructorCall[] = "constructorCall"; constexpr char ResetCall[] = "resetCall"; constexpr char NewExpression[] = "newExpression"; std::string GetNewExprName(const CXXNewExpr *NewExpr, const SourceManager &SM, const LangOptions &Lang) { StringRef WrittenName = Lexer::getSourceText( CharSourceRange::getTokenRange( NewExpr->getAllocatedTypeSourceInfo()->getTypeLoc().getSourceRange()), SM, Lang); if (NewExpr->isArray()) { return (WrittenName + "[]").str(); } return WrittenName.str(); } } // namespace const char MakeSmartPtrCheck::PointerType[] = "pointerType"; MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context, StringRef MakeSmartPtrFunctionName) : ClangTidyCheck(Name, Context), Inserter(Options.getLocalOrGlobal("IncludeStyle", utils::IncludeSorter::IS_LLVM)), MakeSmartPtrFunctionHeader( Options.get("MakeSmartPtrFunctionHeader", "")), MakeSmartPtrFunctionName( Options.get("MakeSmartPtrFunction", MakeSmartPtrFunctionName)), IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)), IgnoreDefaultInitialization( Options.get("IgnoreDefaultInitialization", true)) {} void MakeSmartPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IncludeStyle", Inserter.getStyle()); Options.store(Opts, "MakeSmartPtrFunctionHeader", MakeSmartPtrFunctionHeader); Options.store(Opts, "MakeSmartPtrFunction", MakeSmartPtrFunctionName); Options.store(Opts, "IgnoreMacros", IgnoreMacros); Options.store(Opts, "IgnoreDefaultInitialization", IgnoreDefaultInitialization); } bool MakeSmartPtrCheck::isLanguageVersionSupported( const LangOptions &LangOpts) const { return LangOpts.CPlusPlus11; } void MakeSmartPtrCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { Inserter.registerPreprocessor(PP); } void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { // Calling make_smart_ptr from within a member function of a type with a // private or protected constructor would be ill-formed. auto CanCallCtor = unless(has(ignoringImpCasts( cxxConstructExpr(hasDeclaration(decl(unless(isPublic()))))))); auto IsPlacement = hasAnyPlacementArg(anything()); Finder->addMatcher( traverse( ast_type_traits::TK_AsIs, cxxBindTemporaryExpr(has(ignoringParenImpCasts( cxxConstructExpr( hasType(getSmartPointerTypeMatcher()), argumentCountIs(1), hasArgument( 0, cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType( equalsBoundNode(PointerType))))), CanCallCtor, unless(IsPlacement)) .bind(NewExpression)), unless(isInTemplateInstantiation())) .bind(ConstructorCall))))), this); Finder->addMatcher( traverse(ast_type_traits::TK_AsIs, cxxMemberCallExpr( thisPointerType(getSmartPointerTypeMatcher()), callee(cxxMethodDecl(hasName("reset"))), hasArgument(0, cxxNewExpr(CanCallCtor, unless(IsPlacement)) .bind(NewExpression)), unless(isInTemplateInstantiation())) .bind(ResetCall)), this); } void MakeSmartPtrCheck::check(const MatchFinder::MatchResult &Result) { // 'smart_ptr' refers to 'std::shared_ptr' or 'std::unique_ptr' or other // pointer, 'make_smart_ptr' refers to 'std::make_shared' or // 'std::make_unique' or other function that creates smart_ptr. SourceManager &SM = *Result.SourceManager; const auto *Construct = Result.Nodes.getNodeAs(ConstructorCall); const auto *Reset = Result.Nodes.getNodeAs(ResetCall); const auto *Type = Result.Nodes.getNodeAs(PointerType); const auto *New = Result.Nodes.getNodeAs(NewExpression); // Skip when this is a new-expression with `auto`, e.g. new auto(1) if (New->getType()->getPointeeType()->getContainedAutoType()) return; // Be conservative for cases where we construct and default initialize. // // For example, // P.reset(new int) // check fix: P = std::make_unique() // P.reset(new int[5]) // check fix: P = std::make_unique(5) // // The fix of the check has side effect, it introduces value initialization // which maybe unexpected and cause performance regression. bool Initializes = New->hasInitializer() || !utils::type_traits::isTriviallyDefaultConstructible( New->getAllocatedType(), *Result.Context); if (!Initializes && IgnoreDefaultInitialization) return; if (Construct) checkConstruct(SM, Result.Context, Construct, Type, New); else if (Reset) checkReset(SM, Result.Context, Reset, New); } void MakeSmartPtrCheck::checkConstruct(SourceManager &SM, ASTContext *Ctx, const CXXConstructExpr *Construct, const QualType *Type, const CXXNewExpr *New) { SourceLocation ConstructCallStart = Construct->getExprLoc(); bool InMacro = ConstructCallStart.isMacroID(); if (InMacro && IgnoreMacros) { return; } bool Invalid = false; StringRef ExprStr = Lexer::getSourceText( CharSourceRange::getCharRange( ConstructCallStart, Construct->getParenOrBraceRange().getBegin()), SM, getLangOpts(), &Invalid); if (Invalid) return; auto Diag = diag(ConstructCallStart, "use %0 instead") << MakeSmartPtrFunctionName; // Disable the fix in macros. if (InMacro) { return; } if (!replaceNew(Diag, New, SM, Ctx)) { return; } // Find the location of the template's left angle. size_t LAngle = ExprStr.find("<"); SourceLocation ConstructCallEnd; if (LAngle == StringRef::npos) { // If the template argument is missing (because it is part of the alias) // we have to add it back. ConstructCallEnd = ConstructCallStart.getLocWithOffset(ExprStr.size()); Diag << FixItHint::CreateInsertion( ConstructCallEnd, "<" + GetNewExprName(New, SM, getLangOpts()) + ">"); } else { ConstructCallEnd = ConstructCallStart.getLocWithOffset(LAngle); } Diag << FixItHint::CreateReplacement( CharSourceRange::getCharRange(ConstructCallStart, ConstructCallEnd), MakeSmartPtrFunctionName); // If the smart_ptr is built with brace enclosed direct initialization, use // parenthesis instead. if (Construct->isListInitialization()) { SourceRange BraceRange = Construct->getParenOrBraceRange(); Diag << FixItHint::CreateReplacement( CharSourceRange::getCharRange( BraceRange.getBegin(), BraceRange.getBegin().getLocWithOffset(1)), "("); Diag << FixItHint::CreateReplacement( CharSourceRange::getCharRange(BraceRange.getEnd(), BraceRange.getEnd().getLocWithOffset(1)), ")"); } insertHeader(Diag, SM.getFileID(ConstructCallStart)); } void MakeSmartPtrCheck::checkReset(SourceManager &SM, ASTContext *Ctx, const CXXMemberCallExpr *Reset, const CXXNewExpr *New) { const auto *Expr = cast(Reset->getCallee()); SourceLocation OperatorLoc = Expr->getOperatorLoc(); SourceLocation ResetCallStart = Reset->getExprLoc(); SourceLocation ExprStart = Expr->getBeginLoc(); SourceLocation ExprEnd = Lexer::getLocForEndOfToken(Expr->getEndLoc(), 0, SM, getLangOpts()); bool InMacro = ExprStart.isMacroID(); if (InMacro && IgnoreMacros) { return; } // There are some cases where we don't have operator ("." or "->") of the // "reset" expression, e.g. call "reset()" method directly in the subclass of // "std::unique_ptr<>". We skip these cases. if (OperatorLoc.isInvalid()) { return; } auto Diag = diag(ResetCallStart, "use %0 instead") << MakeSmartPtrFunctionName; // Disable the fix in macros. if (InMacro) { return; } if (!replaceNew(Diag, New, SM, Ctx)) { return; } Diag << FixItHint::CreateReplacement( CharSourceRange::getCharRange(OperatorLoc, ExprEnd), (llvm::Twine(" = ") + MakeSmartPtrFunctionName + "<" + GetNewExprName(New, SM, getLangOpts()) + ">") .str()); if (Expr->isArrow()) Diag << FixItHint::CreateInsertion(ExprStart, "*"); insertHeader(Diag, SM.getFileID(OperatorLoc)); } bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, const CXXNewExpr *New, SourceManager &SM, ASTContext *Ctx) { auto SkipParensParents = [&](const Expr *E) { TraversalKindScope RAII(*Ctx, ast_type_traits::TK_AsIs); for (const Expr *OldE = nullptr; E != OldE;) { OldE = E; for (const auto &Node : Ctx->getParents(*E)) { if (const Expr *Parent = Node.get()) { E = Parent; break; } } } return E; }; SourceRange NewRange = SkipParensParents(New)->getSourceRange(); SourceLocation NewStart = NewRange.getBegin(); SourceLocation NewEnd = NewRange.getEnd(); // Skip when the source location of the new expression is invalid. if (NewStart.isInvalid() || NewEnd.isInvalid()) return false; std::string ArraySizeExpr; if (const auto* ArraySize = New->getArraySize().getValueOr(nullptr)) { ArraySizeExpr = Lexer::getSourceText(CharSourceRange::getTokenRange( ArraySize->getSourceRange()), SM, getLangOpts()) .str(); } // Returns true if the given constructor expression has any braced-init-list // argument, e.g. // Foo({1, 2}, 1) => true // Foo(Bar{1, 2}) => true // Foo(1) => false // Foo{1} => false auto HasListIntializedArgument = [](const CXXConstructExpr *CE) { for (const auto *Arg : CE->arguments()) { Arg = Arg->IgnoreImplicit(); if (isa(Arg) || isa(Arg)) return true; // Check whether we implicitly construct a class from a // std::initializer_list. if (const auto *CEArg = dyn_cast(Arg)) { // Strip the elidable move constructor, it is present in the AST for // C++11/14, e.g. Foo(Bar{1, 2}), the move constructor is around the // init-list constructor. if (CEArg->isElidable()) { if (const auto *TempExp = CEArg->getArg(0)) { if (const auto *UnwrappedCE = dyn_cast(TempExp->IgnoreImplicit())) CEArg = UnwrappedCE; } } if (CEArg->isStdInitListInitialization()) return true; } } return false; }; switch (New->getInitializationStyle()) { case CXXNewExpr::NoInit: { if (ArraySizeExpr.empty()) { Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd)); } else { // New array expression without written initializer: // smart_ptr(new Foo[5]); Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd), ArraySizeExpr); } break; } case CXXNewExpr::CallInit: { // FIXME: Add fixes for constructors with parameters that can be created // with a C++11 braced-init-list (e.g. std::vector, std::map). // Unlike ordinal cases, braced list can not be deduced in // std::make_smart_ptr, we need to specify the type explicitly in the fixes: // struct S { S(std::initializer_list, int); }; // struct S2 { S2(std::vector); }; // struct S3 { S3(S2, int); }; // smart_ptr(new S({1, 2, 3}, 1)); // C++98 call-style initialization // smart_ptr(new S({}, 1)); // smart_ptr(new S2({1})); // implicit conversion: // // std::initializer_list => std::vector // smart_ptr(new S3({1, 2}, 3)); // The above samples have to be replaced with: // std::make_smart_ptr(std::initializer_list({1, 2, 3}), 1); // std::make_smart_ptr(std::initializer_list({}), 1); // std::make_smart_ptr(std::vector({1})); // std::make_smart_ptr(S2{1, 2}, 3); if (const auto *CE = New->getConstructExpr()) { if (HasListIntializedArgument(CE)) return false; } if (ArraySizeExpr.empty()) { SourceRange InitRange = New->getDirectInitRange(); Diag << FixItHint::CreateRemoval( SourceRange(NewStart, InitRange.getBegin())); Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd)); } else { // New array expression with default/value initialization: // smart_ptr(new int[5]()); // smart_ptr(new Foo[5]()); Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd), ArraySizeExpr); } break; } case CXXNewExpr::ListInit: { // Range of the substring that we do not want to remove. SourceRange InitRange; if (const auto *NewConstruct = New->getConstructExpr()) { if (NewConstruct->isStdInitListInitialization() || HasListIntializedArgument(NewConstruct)) { // FIXME: Add fixes for direct initialization with the initializer-list // constructor. Similar to the above CallInit case, the type has to be // specified explicitly in the fixes. // struct S { S(std::initializer_list); }; // struct S2 { S2(S, int); }; // smart_ptr(new S{1, 2, 3}); // C++11 direct list-initialization // smart_ptr(new S{}); // use initializer-list constructor // smart_ptr()new S2{ {1,2}, 3 }; // have a list-initialized arg // The above cases have to be replaced with: // std::make_smart_ptr(std::initializer_list({1, 2, 3})); // std::make_smart_ptr(std::initializer_list({})); // std::make_smart_ptr(S{1, 2}, 3); return false; } else { // Direct initialization with ordinary constructors. // struct S { S(int x); S(); }; // smart_ptr(new S{5}); // smart_ptr(new S{}); // use default constructor // The arguments in the initialization list are going to be forwarded to // the constructor, so this has to be replaced with: // std::make_smart_ptr(5); // std::make_smart_ptr(); InitRange = SourceRange( NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1), NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1)); } } else { // Aggregate initialization. // smart_ptr(new Pair{first, second}); // Has to be replaced with: // smart_ptr(Pair{first, second}); // // The fix (std::make_unique) needs to see copy/move constructor of // Pair. If we found any invisible or deleted copy/move constructor, we // stop generating fixes -- as the C++ rule is complicated and we are less // certain about the correct fixes. if (const CXXRecordDecl *RD = New->getType()->getPointeeCXXRecordDecl()) { if (llvm::find_if(RD->ctors(), [](const CXXConstructorDecl *Ctor) { return Ctor->isCopyOrMoveConstructor() && (Ctor->isDeleted() || Ctor->getAccess() == AS_private); }) != RD->ctor_end()) { return false; } } InitRange = SourceRange( New->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(), New->getInitializer()->getSourceRange().getEnd()); } Diag << FixItHint::CreateRemoval( CharSourceRange::getCharRange(NewStart, InitRange.getBegin())); Diag << FixItHint::CreateRemoval( SourceRange(InitRange.getEnd().getLocWithOffset(1), NewEnd)); break; } } return true; } void MakeSmartPtrCheck::insertHeader(DiagnosticBuilder &Diag, FileID FD) { if (MakeSmartPtrFunctionHeader.empty()) { return; } Diag << Inserter.createIncludeInsertion(FD, MakeSmartPtrFunctionHeader); } } // namespace modernize } // namespace tidy } // namespace clang