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