449 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "../utils/TypeTraits.h"10#include "MakeSharedCheck.h"11#include "clang/Frontend/CompilerInstance.h"12#include "clang/Lex/Lexer.h"13#include "clang/Lex/Preprocessor.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::modernize {18 19static constexpr char ConstructorCall[] = "constructorCall";20static constexpr char DirectVar[] = "directVar";21static constexpr char ResetCall[] = "resetCall";22static constexpr char NewExpression[] = "newExpression";23 24static std::string getNewExprName(const CXXNewExpr *NewExpr,25 const SourceManager &SM,26 const LangOptions &Lang) {27 const StringRef WrittenName = Lexer::getSourceText(28 CharSourceRange::getTokenRange(29 NewExpr->getAllocatedTypeSourceInfo()->getTypeLoc().getSourceRange()),30 SM, Lang);31 if (NewExpr->isArray()) {32 return (WrittenName + "[]").str();33 }34 return WrittenName.str();35}36 37const char MakeSmartPtrCheck::PointerType[] = "pointerType";38 39MakeSmartPtrCheck::MakeSmartPtrCheck(StringRef Name, ClangTidyContext *Context,40 StringRef MakeSmartPtrFunctionName)41 : ClangTidyCheck(Name, Context),42 Inserter(Options.getLocalOrGlobal("IncludeStyle",43 utils::IncludeSorter::IS_LLVM),44 areDiagsSelfContained()),45 MakeSmartPtrFunctionHeader(46 Options.get("MakeSmartPtrFunctionHeader", "<memory>")),47 MakeSmartPtrFunctionName(48 Options.get("MakeSmartPtrFunction", MakeSmartPtrFunctionName)),49 IgnoreMacros(Options.get("IgnoreMacros", true)),50 IgnoreDefaultInitialization(51 Options.get("IgnoreDefaultInitialization", true)) {}52 53void MakeSmartPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {54 Options.store(Opts, "IncludeStyle", Inserter.getStyle());55 Options.store(Opts, "MakeSmartPtrFunctionHeader", MakeSmartPtrFunctionHeader);56 Options.store(Opts, "MakeSmartPtrFunction", MakeSmartPtrFunctionName);57 Options.store(Opts, "IgnoreMacros", IgnoreMacros);58 Options.store(Opts, "IgnoreDefaultInitialization",59 IgnoreDefaultInitialization);60}61 62bool MakeSmartPtrCheck::isLanguageVersionSupported(63 const LangOptions &LangOpts) const {64 return LangOpts.CPlusPlus11;65}66 67void MakeSmartPtrCheck::registerPPCallbacks(const SourceManager &SM,68 Preprocessor *PP,69 Preprocessor *ModuleExpanderPP) {70 Inserter.registerPreprocessor(PP);71}72 73void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {74 // Calling make_smart_ptr from within a member function of a type with a75 // private or protected constructor would be ill-formed.76 auto CanCallCtor = unless(has(ignoringImpCasts(77 cxxConstructExpr(hasDeclaration(decl(unless(isPublic())))))));78 79 auto IsPlacement = hasAnyPlacementArg(anything());80 81 Finder->addMatcher(82 traverse(TK_AsIs,83 cxxConstructExpr(84 anyOf(hasParent(cxxBindTemporaryExpr()),85 hasParent(varDecl().bind(DirectVar))),86 hasType(getSmartPointerTypeMatcher()), argumentCountIs(1),87 hasArgument(88 0, cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType(89 equalsBoundNode(PointerType))))),90 CanCallCtor, unless(IsPlacement))91 .bind(NewExpression)),92 unless(isInTemplateInstantiation()))93 .bind(ConstructorCall)),94 this);95 96 Finder->addMatcher(97 traverse(98 TK_AsIs,99 cxxMemberCallExpr(100 unless(isInTemplateInstantiation()),101 hasArgument(0, cxxNewExpr(CanCallCtor, unless(IsPlacement))102 .bind(NewExpression)),103 callee(cxxMethodDecl(hasName("reset"))),104 anyOf(thisPointerType(getSmartPointerTypeMatcher()),105 on(ignoringImplicit(anyOf(106 hasType(getSmartPointerTypeMatcher()),107 hasType(pointsTo(getSmartPointerTypeMatcher())))))))108 .bind(ResetCall)),109 this);110}111 112void MakeSmartPtrCheck::check(const MatchFinder::MatchResult &Result) {113 // 'smart_ptr' refers to 'std::shared_ptr' or 'std::unique_ptr' or other114 // pointer, 'make_smart_ptr' refers to 'std::make_shared' or115 // 'std::make_unique' or other function that creates smart_ptr.116 117 SourceManager &SM = *Result.SourceManager;118 const auto *Construct =119 Result.Nodes.getNodeAs<CXXConstructExpr>(ConstructorCall);120 const auto *DVar = Result.Nodes.getNodeAs<VarDecl>(DirectVar);121 const auto *Reset = Result.Nodes.getNodeAs<CXXMemberCallExpr>(ResetCall);122 const auto *Type = Result.Nodes.getNodeAs<QualType>(PointerType);123 const auto *New = Result.Nodes.getNodeAs<CXXNewExpr>(NewExpression);124 125 // Skip when this is a new-expression with `auto`, e.g. new auto(1)126 if (New->getType()->getPointeeType()->getContainedAutoType())127 return;128 129 // Be conservative for cases where we construct and default initialize.130 //131 // For example,132 // P.reset(new int) // check fix: P = std::make_unique<int>()133 // P.reset(new int[5]) // check fix: P = std::make_unique<int []>(5)134 //135 // The fix of the check has side effect, it introduces value initialization136 // which maybe unexpected and cause performance regression.137 const bool Initializes = New->hasInitializer() ||138 !utils::type_traits::isTriviallyDefaultConstructible(139 New->getAllocatedType(), *Result.Context);140 if (!Initializes && IgnoreDefaultInitialization)141 return;142 if (Construct)143 checkConstruct(SM, Result.Context, Construct, DVar, Type, New);144 else if (Reset)145 checkReset(SM, Result.Context, Reset, New);146}147 148void MakeSmartPtrCheck::checkConstruct(SourceManager &SM, ASTContext *Ctx,149 const CXXConstructExpr *Construct,150 const VarDecl *DVar,151 const QualType *Type,152 const CXXNewExpr *New) {153 const SourceLocation ConstructCallStart = Construct->getExprLoc();154 const bool InMacro = ConstructCallStart.isMacroID();155 156 if (InMacro && IgnoreMacros) {157 return;158 }159 160 bool Invalid = false;161 const StringRef ExprStr = Lexer::getSourceText(162 CharSourceRange::getCharRange(163 ConstructCallStart, Construct->getParenOrBraceRange().getBegin()),164 SM, getLangOpts(), &Invalid);165 if (Invalid)166 return;167 168 auto Diag = diag(ConstructCallStart, "use %0 instead")169 << MakeSmartPtrFunctionName;170 171 // Disable the fix in macros.172 if (InMacro) {173 return;174 }175 176 if (!replaceNew(Diag, New, SM, Ctx)) {177 return;178 }179 180 // Find the location of the template's left angle.181 const size_t LAngle = ExprStr.find('<');182 SourceLocation ConstructCallEnd;183 if (LAngle == StringRef::npos) {184 // If the template argument is missing (because it is part of the alias)185 // we have to add it back.186 ConstructCallEnd = ConstructCallStart.getLocWithOffset(ExprStr.size());187 Diag << FixItHint::CreateInsertion(188 ConstructCallEnd, "<" + getNewExprName(New, SM, getLangOpts()) + ">");189 } else {190 ConstructCallEnd = ConstructCallStart.getLocWithOffset(LAngle);191 }192 193 std::string FinalMakeSmartPtrFunctionName = MakeSmartPtrFunctionName.str();194 if (DVar)195 FinalMakeSmartPtrFunctionName =196 ExprStr.str() + " = " + MakeSmartPtrFunctionName.str();197 198 Diag << FixItHint::CreateReplacement(199 CharSourceRange::getCharRange(ConstructCallStart, ConstructCallEnd),200 FinalMakeSmartPtrFunctionName);201 202 // If the smart_ptr is built with brace enclosed direct initialization, use203 // parenthesis instead.204 if (Construct->isListInitialization()) {205 const SourceRange BraceRange = Construct->getParenOrBraceRange();206 Diag << FixItHint::CreateReplacement(207 CharSourceRange::getCharRange(208 BraceRange.getBegin(), BraceRange.getBegin().getLocWithOffset(1)),209 "(");210 Diag << FixItHint::CreateReplacement(211 CharSourceRange::getCharRange(BraceRange.getEnd(),212 BraceRange.getEnd().getLocWithOffset(1)),213 ")");214 }215 216 insertHeader(Diag, SM.getFileID(ConstructCallStart));217}218 219void MakeSmartPtrCheck::checkReset(SourceManager &SM, ASTContext *Ctx,220 const CXXMemberCallExpr *Reset,221 const CXXNewExpr *New) {222 const auto *Expr = cast<MemberExpr>(Reset->getCallee());223 const SourceLocation OperatorLoc = Expr->getOperatorLoc();224 const SourceLocation ResetCallStart = Reset->getExprLoc();225 const SourceLocation ExprStart = Expr->getBeginLoc();226 const SourceLocation ExprEnd =227 Lexer::getLocForEndOfToken(Expr->getEndLoc(), 0, SM, getLangOpts());228 229 const bool InMacro = ExprStart.isMacroID();230 231 if (InMacro && IgnoreMacros) {232 return;233 }234 235 // There are some cases where we don't have operator ("." or "->") of the236 // "reset" expression, e.g. call "reset()" method directly in the subclass of237 // "std::unique_ptr<>". We skip these cases.238 if (OperatorLoc.isInvalid()) {239 return;240 }241 242 auto Diag = diag(ResetCallStart, "use %0 instead")243 << MakeSmartPtrFunctionName;244 245 // Disable the fix in macros.246 if (InMacro) {247 return;248 }249 250 if (!replaceNew(Diag, New, SM, Ctx)) {251 return;252 }253 254 Diag << FixItHint::CreateReplacement(255 CharSourceRange::getCharRange(OperatorLoc, ExprEnd),256 (llvm::Twine(" = ") + MakeSmartPtrFunctionName + "<" +257 getNewExprName(New, SM, getLangOpts()) + ">")258 .str());259 260 if (Expr->isArrow())261 Diag << FixItHint::CreateInsertion(ExprStart, "*");262 263 insertHeader(Diag, SM.getFileID(OperatorLoc));264}265 266bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag,267 const CXXNewExpr *New, SourceManager &SM,268 ASTContext *Ctx) {269 auto SkipParensParents = [&](const Expr *E) {270 const TraversalKindScope RAII(*Ctx, TK_AsIs);271 272 for (const Expr *OldE = nullptr; E != OldE;) {273 OldE = E;274 for (const auto &Node : Ctx->getParents(*E)) {275 if (const Expr *Parent = Node.get<ParenExpr>()) {276 E = Parent;277 break;278 }279 }280 }281 return E;282 };283 284 const SourceRange NewRange = SkipParensParents(New)->getSourceRange();285 const SourceLocation NewStart = NewRange.getBegin();286 const SourceLocation NewEnd = NewRange.getEnd();287 288 // Skip when the source location of the new expression is invalid.289 if (NewStart.isInvalid() || NewEnd.isInvalid())290 return false;291 292 std::string ArraySizeExpr;293 if (const auto *ArraySize = New->getArraySize().value_or(nullptr)) {294 ArraySizeExpr = Lexer::getSourceText(CharSourceRange::getTokenRange(295 ArraySize->getSourceRange()),296 SM, getLangOpts())297 .str();298 }299 // Returns true if the given constructor expression has any braced-init-list300 // argument, e.g.301 // Foo({1, 2}, 1) => true302 // Foo(Bar{1, 2}) => true303 // Foo(1) => false304 // Foo{1} => false305 auto HasListIntializedArgument = [](const CXXConstructExpr *CE) {306 for (const auto *Arg : CE->arguments()) {307 Arg = Arg->IgnoreImplicit();308 309 if (isa<CXXStdInitializerListExpr>(Arg) || isa<InitListExpr>(Arg))310 return true;311 // Check whether we implicitly construct a class from a312 // std::initializer_list.313 if (const auto *CEArg = dyn_cast<CXXConstructExpr>(Arg)) {314 // Strip the elidable move constructor, it is present in the AST for315 // C++11/14, e.g. Foo(Bar{1, 2}), the move constructor is around the316 // init-list constructor.317 if (CEArg->isElidable()) {318 if (const auto *TempExp = CEArg->getArg(0)) {319 if (const auto *UnwrappedCE =320 dyn_cast<CXXConstructExpr>(TempExp->IgnoreImplicit()))321 CEArg = UnwrappedCE;322 }323 }324 if (CEArg->isStdInitListInitialization())325 return true;326 }327 }328 return false;329 };330 switch (New->getInitializationStyle()) {331 case CXXNewInitializationStyle::None: {332 if (ArraySizeExpr.empty()) {333 Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd));334 } else {335 // New array expression without written initializer:336 // smart_ptr<Foo[]>(new Foo[5]);337 Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd),338 ArraySizeExpr);339 }340 break;341 }342 case CXXNewInitializationStyle::Parens: {343 // FIXME: Add fixes for constructors with parameters that can be created344 // with a C++11 braced-init-list (e.g. std::vector, std::map).345 // Unlike ordinal cases, braced list can not be deduced in346 // std::make_smart_ptr, we need to specify the type explicitly in the fixes:347 // struct S { S(std::initializer_list<int>, int); };348 // struct S2 { S2(std::vector<int>); };349 // struct S3 { S3(S2, int); };350 // smart_ptr<S>(new S({1, 2, 3}, 1)); // C++98 call-style initialization351 // smart_ptr<S>(new S({}, 1));352 // smart_ptr<S2>(new S2({1})); // implicit conversion:353 // // std::initializer_list => std::vector354 // smart_ptr<S3>(new S3({1, 2}, 3));355 // The above samples have to be replaced with:356 // std::make_smart_ptr<S>(std::initializer_list<int>({1, 2, 3}), 1);357 // std::make_smart_ptr<S>(std::initializer_list<int>({}), 1);358 // std::make_smart_ptr<S2>(std::vector<int>({1}));359 // std::make_smart_ptr<S3>(S2{1, 2}, 3);360 if (const auto *CE = New->getConstructExpr()) {361 if (HasListIntializedArgument(CE))362 return false;363 }364 if (ArraySizeExpr.empty()) {365 const SourceRange InitRange = New->getDirectInitRange();366 Diag << FixItHint::CreateRemoval(367 SourceRange(NewStart, InitRange.getBegin()));368 Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd));369 } else {370 // New array expression with default/value initialization:371 // smart_ptr<Foo[]>(new int[5]());372 // smart_ptr<Foo[]>(new Foo[5]());373 Diag << FixItHint::CreateReplacement(SourceRange(NewStart, NewEnd),374 ArraySizeExpr);375 }376 break;377 }378 case CXXNewInitializationStyle::Braces: {379 // Range of the substring that we do not want to remove.380 SourceRange InitRange;381 if (const auto *NewConstruct = New->getConstructExpr()) {382 if (NewConstruct->isStdInitListInitialization() ||383 HasListIntializedArgument(NewConstruct)) {384 // FIXME: Add fixes for direct initialization with the initializer-list385 // constructor. Similar to the above CallInit case, the type has to be386 // specified explicitly in the fixes.387 // struct S { S(std::initializer_list<int>); };388 // struct S2 { S2(S, int); };389 // smart_ptr<S>(new S{1, 2, 3}); // C++11 direct list-initialization390 // smart_ptr<S>(new S{}); // use initializer-list constructor391 // smart_ptr<S2>()new S2{ {1,2}, 3 }; // have a list-initialized arg392 // The above cases have to be replaced with:393 // std::make_smart_ptr<S>(std::initializer_list<int>({1, 2, 3}));394 // std::make_smart_ptr<S>(std::initializer_list<int>({}));395 // std::make_smart_ptr<S2>(S{1, 2}, 3);396 return false;397 }398 // Direct initialization with ordinary constructors.399 // struct S { S(int x); S(); };400 // smart_ptr<S>(new S{5});401 // smart_ptr<S>(new S{}); // use default constructor402 // The arguments in the initialization list are going to be forwarded to403 // the constructor, so this has to be replaced with:404 // std::make_smart_ptr<S>(5);405 // std::make_smart_ptr<S>();406 InitRange = SourceRange(407 NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1),408 NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1));409 } else {410 // Aggregate initialization.411 // smart_ptr<Pair>(new Pair{first, second});412 // Has to be replaced with:413 // smart_ptr<Pair>(Pair{first, second});414 //415 // The fix (std::make_unique) needs to see copy/move constructor of416 // Pair. If we found any invisible or deleted copy/move constructor, we417 // stop generating fixes -- as the C++ rule is complicated and we are less418 // certain about the correct fixes.419 if (const CXXRecordDecl *RD = New->getType()->getPointeeCXXRecordDecl()) {420 if (llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) {421 return Ctor->isCopyOrMoveConstructor() &&422 (Ctor->isDeleted() || Ctor->getAccess() == AS_private);423 })) {424 return false;425 }426 }427 InitRange = SourceRange(428 New->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(),429 New->getInitializer()->getSourceRange().getEnd());430 }431 Diag << FixItHint::CreateRemoval(432 CharSourceRange::getCharRange(NewStart, InitRange.getBegin()));433 Diag << FixItHint::CreateRemoval(434 SourceRange(InitRange.getEnd().getLocWithOffset(1), NewEnd));435 break;436 }437 }438 return true;439}440 441void MakeSmartPtrCheck::insertHeader(DiagnosticBuilder &Diag, FileID FD) {442 if (MakeSmartPtrFunctionHeader.empty()) {443 return;444 }445 Diag << Inserter.createIncludeInsertion(FD, MakeSmartPtrFunctionHeader);446}447 448} // namespace clang::tidy::modernize449