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