xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/CopyConstructorInitCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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 
registerMatchers(MatchFinder * Finder)18 void CopyConstructorInitCheck::registerMatchers(MatchFinder *Finder) {
19   // In the future this might be extended to move constructors?
20   Finder->addMatcher(
21       cxxConstructorDecl(
22           isCopyConstructor(),
23           hasAnyConstructorInitializer(cxxCtorInitializer(
24               isBaseInitializer(),
25               withInitializer(cxxConstructExpr(hasDeclaration(
26                   cxxConstructorDecl(isDefaultConstructor())))))),
27           unless(isInstantiated()))
28           .bind("ctor"),
29       this);
30 }
31 
check(const MatchFinder::MatchResult & Result)32 void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
33   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("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>(BaseType))
52       ShouldNotDoFixit = ShouldNotDoFixit || TempSpecTy->isTypeAlias();
53     ShouldNotDoFixit = ShouldNotDoFixit || isa<TypedefType>(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>(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             FixItHint::CreateInsertion(CExpr->getEndLoc(), 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(0, 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(FixItLoc, FixItMsg);
111 }
112 
113 } // namespace clang::tidy::bugprone
114