1 | //===--- CopyConstructorInitCheck.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 "CopyConstructorInitCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/Lex/Lexer.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | |
16 | namespace clang::tidy::bugprone { |
17 | |
18 | void CopyConstructorInitCheck::registerMatchers(MatchFinder *Finder) { |
19 | // In the future this might be extended to move constructors? |
20 | Finder->addMatcher( |
21 | NodeMatch: cxxConstructorDecl( |
22 | isCopyConstructor(), |
23 | hasAnyConstructorInitializer(InnerMatcher: cxxCtorInitializer( |
24 | isBaseInitializer(), |
25 | withInitializer(InnerMatcher: cxxConstructExpr(hasDeclaration( |
26 | InnerMatcher: cxxConstructorDecl(isDefaultConstructor())))))), |
27 | unless(isInstantiated())) |
28 | .bind(ID: "ctor" ), |
29 | Action: this); |
30 | } |
31 | |
32 | void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { |
33 | const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(ID: "ctor" ); |
34 | std::string ParamName = Ctor->getParamDecl(0)->getNameAsString(); |
35 | |
36 | // We want only one warning (and FixIt) for each ctor. |
37 | std::string FixItInitList; |
38 | bool HasRelevantBaseInit = false; |
39 | bool ShouldNotDoFixit = false; |
40 | bool HasWrittenInitializer = false; |
41 | SmallVector<FixItHint, 2> SafeFixIts; |
42 | for (const auto *Init : Ctor->inits()) { |
43 | bool CtorInitIsWritten = Init->isWritten(); |
44 | HasWrittenInitializer = HasWrittenInitializer || CtorInitIsWritten; |
45 | if (!Init->isBaseInitializer()) |
46 | continue; |
47 | const Type *BaseType = Init->getBaseClass(); |
48 | // Do not do fixits if there is a type alias involved or one of the bases |
49 | // are explicitly initialized. In the latter case we not do fixits to avoid |
50 | // -Wreorder warnings. |
51 | if (const auto *TempSpecTy = dyn_cast<TemplateSpecializationType>(Val: BaseType)) |
52 | ShouldNotDoFixit = ShouldNotDoFixit || TempSpecTy->isTypeAlias(); |
53 | ShouldNotDoFixit = ShouldNotDoFixit || isa<TypedefType>(Val: BaseType); |
54 | ShouldNotDoFixit = ShouldNotDoFixit || CtorInitIsWritten; |
55 | const CXXRecordDecl *BaseClass = |
56 | BaseType->getAsCXXRecordDecl()->getDefinition(); |
57 | if (BaseClass->field_empty() && |
58 | BaseClass->forallBases( |
59 | [](const CXXRecordDecl *Class) { return Class->field_empty(); })) |
60 | continue; |
61 | bool NonCopyableBase = false; |
62 | for (const auto *Ctor : BaseClass->ctors()) { |
63 | if (Ctor->isCopyConstructor() && |
64 | (Ctor->getAccess() == AS_private || Ctor->isDeleted())) { |
65 | NonCopyableBase = true; |
66 | break; |
67 | } |
68 | } |
69 | if (NonCopyableBase) |
70 | continue; |
71 | const auto *CExpr = dyn_cast<CXXConstructExpr>(Val: Init->getInit()); |
72 | if (!CExpr || !CExpr->getConstructor()->isDefaultConstructor()) |
73 | continue; |
74 | HasRelevantBaseInit = true; |
75 | if (CtorInitIsWritten) { |
76 | if (!ParamName.empty()) |
77 | SafeFixIts.push_back( |
78 | Elt: FixItHint::CreateInsertion(InsertionLoc: CExpr->getEndLoc(), Code: ParamName)); |
79 | } else { |
80 | if (Init->getSourceLocation().isMacroID() || |
81 | Ctor->getLocation().isMacroID() || ShouldNotDoFixit) |
82 | break; |
83 | FixItInitList += BaseClass->getNameAsString(); |
84 | FixItInitList += "(" + ParamName + "), " ; |
85 | } |
86 | } |
87 | if (!HasRelevantBaseInit) |
88 | return; |
89 | |
90 | auto Diag = diag(Ctor->getLocation(), |
91 | "calling a base constructor other than the copy constructor" ) |
92 | << SafeFixIts; |
93 | |
94 | if (FixItInitList.empty() || ParamName.empty() || ShouldNotDoFixit) |
95 | return; |
96 | |
97 | std::string FixItMsg{FixItInitList.substr(pos: 0, n: FixItInitList.size() - 2)}; |
98 | SourceLocation FixItLoc; |
99 | // There is no initialization list in this constructor. |
100 | if (!HasWrittenInitializer) { |
101 | FixItLoc = Ctor->getBody()->getBeginLoc(); |
102 | FixItMsg = " : " + FixItMsg; |
103 | } else { |
104 | // We apply the missing ctors at the beginning of the initialization list. |
105 | FixItLoc = (*Ctor->init_begin())->getSourceLocation(); |
106 | FixItMsg += ','; |
107 | } |
108 | FixItMsg += ' '; |
109 | |
110 | Diag << FixItHint::CreateInsertion(InsertionLoc: FixItLoc, Code: FixItMsg); |
111 | } |
112 | |
113 | } // namespace clang::tidy::bugprone |
114 | |