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