| //===--- MakeSmartPtrCheck.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 "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 StdMemoryHeader[] = "memory"; |
| |
| 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"; |
| const char MakeSmartPtrCheck::ConstructorCall[] = "constructorCall"; |
| const char MakeSmartPtrCheck::ResetCall[] = "resetCall"; |
| const char MakeSmartPtrCheck::NewExpression[] = "newExpression"; |
| |
| MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, |
| ClangTidyContext* Context, |
| StringRef MakeSmartPtrFunctionName) |
| : ClangTidyCheck(Name, Context), |
| IncludeStyle(utils::IncludeSorter::parseIncludeStyle( |
| Options.getLocalOrGlobal("IncludeStyle", "llvm"))), |
| MakeSmartPtrFunctionHeader( |
| Options.get("MakeSmartPtrFunctionHeader", StdMemoryHeader)), |
| MakeSmartPtrFunctionName( |
| Options.get("MakeSmartPtrFunction", MakeSmartPtrFunctionName)), |
| IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} |
| |
| void MakeSmartPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "IncludeStyle", IncludeStyle); |
| Options.store(Opts, "MakeSmartPtrFunctionHeader", MakeSmartPtrFunctionHeader); |
| Options.store(Opts, "MakeSmartPtrFunction", MakeSmartPtrFunctionName); |
| Options.store(Opts, "IgnoreMacros", IgnoreMacros); |
| } |
| |
| bool MakeSmartPtrCheck::isLanguageVersionSupported( |
| const LangOptions &LangOpts) const { |
| return LangOpts.CPlusPlus11; |
| } |
| |
| void MakeSmartPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) { |
| if (isLanguageVersionSupported(getLangOpts())) { |
| Inserter.reset(new utils::IncludeInserter( |
| Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); |
| Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); |
| } |
| } |
| |
| void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { |
| if (!isLanguageVersionSupported(getLangOpts())) |
| return; |
| |
| // 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()))))))); |
| |
| Finder->addMatcher( |
| cxxBindTemporaryExpr(has(ignoringParenImpCasts( |
| cxxConstructExpr( |
| hasType(getSmartPointerTypeMatcher()), argumentCountIs(1), |
| hasArgument(0, |
| cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType( |
| equalsBoundNode(PointerType))))), |
| CanCallCtor) |
| .bind(NewExpression)), |
| unless(isInTemplateInstantiation())) |
| .bind(ConstructorCall)))), |
| this); |
| |
| Finder->addMatcher( |
| cxxMemberCallExpr( |
| thisPointerType(getSmartPointerTypeMatcher()), |
| callee(cxxMethodDecl(hasName("reset"))), |
| hasArgument(0, cxxNewExpr(CanCallCtor).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<CXXConstructExpr>(ConstructorCall); |
| const auto *Reset = Result.Nodes.getNodeAs<CXXMemberCallExpr>(ResetCall); |
| const auto *Type = Result.Nodes.getNodeAs<QualType>(PointerType); |
| const auto *New = Result.Nodes.getNodeAs<CXXNewExpr>(NewExpression); |
| |
| if (New->getNumPlacementArgs() != 0) |
| return; |
| |
| if (Construct) |
| checkConstruct(SM, Construct, Type, New); |
| else if (Reset) |
| checkReset(SM, Reset, New); |
| } |
| |
| void MakeSmartPtrCheck::checkConstruct(SourceManager &SM, |
| 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)) { |
| 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, |
| const CXXMemberCallExpr *Reset, |
| const CXXNewExpr *New) { |
| const auto *Expr = cast<MemberExpr>(Reset->getCallee()); |
| SourceLocation OperatorLoc = Expr->getOperatorLoc(); |
| SourceLocation ResetCallStart = Reset->getExprLoc(); |
| SourceLocation ExprStart = Expr->getLocStart(); |
| SourceLocation ExprEnd = |
| Lexer::getLocForEndOfToken(Expr->getLocEnd(), 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)) { |
| 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) { |
| SourceLocation NewStart = New->getSourceRange().getBegin(); |
| SourceLocation NewEnd = New->getSourceRange().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()) { |
| ArraySizeExpr = Lexer::getSourceText(CharSourceRange::getTokenRange( |
| ArraySize->getSourceRange()), |
| SM, getLangOpts()) |
| .str(); |
| } |
| |
| switch (New->getInitializationStyle()) { |
| case CXXNewExpr::NoInit: { |
| if (ArraySizeExpr.empty()) { |
| Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd)); |
| } else { |
| // New array expression without written initializer: |
| // smart_ptr<Foo[]>(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>, int); }; |
| // struct S2 { S2(std::vector<int>); }; |
| // smart_ptr<S>(new S({1, 2, 3}, 1)); // C++98 call-style initialization |
| // smart_ptr<S>(new S({}, 1)); |
| // smart_ptr<S2>(new S2({1})); // implicit conversion: |
| // // std::initializer_list => std::vector |
| // The above samples have to be replaced with: |
| // std::make_smart_ptr<S>(std::initializer_list<int>({1, 2, 3}), 1); |
| // std::make_smart_ptr<S>(std::initializer_list<int>({}), 1); |
| // std::make_smart_ptr<S2>(std::vector<int>({1})); |
| if (const auto *CE = New->getConstructExpr()) { |
| for (const auto *Arg : CE->arguments()) { |
| if (isa<CXXStdInitializerListExpr>(Arg)) { |
| return false; |
| } |
| // Check whether we construct a class from a std::initializer_list. |
| // If so, we won't generate the fixes. |
| auto IsStdInitListInitConstructExpr = [](const Expr* E) { |
| assert(E); |
| if (const auto *ImplicitCE = dyn_cast<CXXConstructExpr>(E)) { |
| if (ImplicitCE->isStdInitListInitialization()) |
| return true; |
| } |
| return false; |
| }; |
| if (IsStdInitListInitConstructExpr(Arg->IgnoreImplicit())) |
| 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<Foo[]>(new int[5]()); |
| // smart_ptr<Foo[]>(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()) { |
| // 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<int>); }; |
| // smart_ptr<S>(new S{1, 2, 3}); // C++11 direct list-initialization |
| // smart_ptr<S>(new S{}); // use initializer-list consturctor |
| // The above cases have to be replaced with: |
| // std::make_smart_ptr<S>(std::initializer_list<int>({1, 2, 3})); |
| // std::make_smart_ptr<S>(std::initializer_list<int>({})); |
| return false; |
| } else { |
| // Direct initialization with ordinary constructors. |
| // struct S { S(int x); S(); }; |
| // smart_ptr<S>(new S{5}); |
| // smart_ptr<S>(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<S>(5); |
| // std::make_smart_ptr<S>(); |
| InitRange = SourceRange( |
| NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1), |
| NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1)); |
| } |
| } else { |
| // Aggregate initialization. |
| // smart_ptr<Pair>(new Pair{first, second}); |
| // Has to be replaced with: |
| // smart_ptr<Pair>(Pair{first, second}); |
| InitRange = SourceRange( |
| New->getAllocatedTypeSourceInfo()->getTypeLoc().getLocStart(), |
| 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; |
| } |
| if (auto IncludeFixit = Inserter->CreateIncludeInsertion( |
| FD, MakeSmartPtrFunctionHeader, |
| /*IsAngled=*/MakeSmartPtrFunctionHeader == StdMemoryHeader)) { |
| Diag << *IncludeFixit; |
| } |
| } |
| |
| } // namespace modernize |
| } // namespace tidy |
| } // namespace clang |