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