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