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