summaryrefslogtreecommitdiff
path: root/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
blob: 672d222e595224a24fde882c9c4b98d0f3599852 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//===--- ProTypeMemberInitCheck.cpp - clang-tidy---------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "ProTypeMemberInitCheck.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/SmallPtrSet.h"

using namespace clang::ast_matchers;
using llvm::SmallPtrSet;
using llvm::SmallPtrSetImpl;

namespace clang {
namespace tidy {
namespace cppcoreguidelines {

namespace {

AST_MATCHER(CXXConstructorDecl, isUserProvided) {
  return Node.isUserProvided();
}

static void
fieldsRequiringInit(const RecordDecl::field_range &Fields,
                    SmallPtrSetImpl<const FieldDecl *> &FieldsToInit) {
  for (const FieldDecl *F : Fields) {
    QualType Type = F->getType();
    if (Type->isPointerType() || Type->isBuiltinType())
      FieldsToInit.insert(F);
  }
}

void removeFieldsInitializedInBody(
    const Stmt &Stmt, ASTContext &Context,
    SmallPtrSetImpl<const FieldDecl *> &FieldDecls) {
  auto Matches =
      match(findAll(binaryOperator(
                hasOperatorName("="),
                hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))),
            Stmt, Context);
  for (const auto &Match : Matches)
    FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
}

// Creates comma separated list of fields requiring initialization in order of
// declaration.
std::string toCommaSeparatedString(
    const RecordDecl::field_range &FieldRange,
    const SmallPtrSetImpl<const FieldDecl *> &FieldsRequiringInit) {
  std::string List;
  llvm::raw_string_ostream Stream(List);
  size_t AddedFields = 0;
  for (const FieldDecl *Field : FieldRange) {
    if (FieldsRequiringInit.count(Field) > 0) {
      Stream << Field->getName();
      if (++AddedFields < FieldsRequiringInit.size())
        Stream << ", ";
    }
  }
  return Stream.str();
}

// Contains all fields in correct order that need to be inserted at the same
// location for pre C++11.
// There are 3 kinds of insertions:
// 1. The fields are inserted after an existing CXXCtorInitializer stored in
// InitializerBefore. This will be the case whenever there is a written
// initializer before the fields available.
// 2. The fields are inserted before the first existing initializer stored in
// InitializerAfter.
// 3. There are no written initializers and the fields will be inserted before
// the constructor's body creating a new initializer list including the ':'.
struct FieldsInsertion {
  const CXXCtorInitializer *InitializerBefore;
  const CXXCtorInitializer *InitializerAfter;
  SmallVector<const FieldDecl *, 4> Fields;

  SourceLocation getLocation(const ASTContext &Context,
                             const CXXConstructorDecl &Constructor) const {
    if (InitializerBefore != nullptr) {
      return Lexer::getLocForEndOfToken(InitializerBefore->getRParenLoc(), 0,
                                        Context.getSourceManager(),
                                        Context.getLangOpts());
    }
    auto StartLocation = InitializerAfter != nullptr
                             ? InitializerAfter->getSourceRange().getBegin()
                             : Constructor.getBody()->getLocStart();
    auto Token =
        lexer_utils::getPreviousNonCommentToken(Context, StartLocation);
    return Lexer::getLocForEndOfToken(Token.getLocation(), 0,
                                      Context.getSourceManager(),
                                      Context.getLangOpts());
  }

  std::string codeToInsert() const {
    assert(!Fields.empty() && "No fields to insert");
    std::string Code;
    llvm::raw_string_ostream Stream(Code);
    // Code will be inserted before the first written initializer after ':',
    // append commas.
    if (InitializerAfter != nullptr) {
      for (const auto *Field : Fields)
        Stream << " " << Field->getName() << "(),";
    } else {
      // The full initializer list is created, add extra space after
      // constructor's rparens.
      if (InitializerBefore == nullptr)
        Stream << " ";
      for (const auto *Field : Fields)
        Stream << ", " << Field->getName() << "()";
    }
    Stream.flush();
    // The initializer list is created, replace leading comma with colon.
    if (InitializerBefore == nullptr && InitializerAfter == nullptr)
      Code[1] = ':';
    return Code;
  }
};

SmallVector<FieldsInsertion, 16> computeInsertions(
    const CXXConstructorDecl::init_const_range &Inits,
    const RecordDecl::field_range &Fields,
    const SmallPtrSetImpl<const FieldDecl *> &FieldsRequiringInit) {
  // Find last written non-member initializer or null.
  const CXXCtorInitializer *LastWrittenNonMemberInit = nullptr;
  for (const CXXCtorInitializer *Init : Inits) {
    if (Init->isWritten() && !Init->isMemberInitializer())
      LastWrittenNonMemberInit = Init;
  }
  SmallVector<FieldsInsertion, 16> OrderedFields;
  OrderedFields.push_back({LastWrittenNonMemberInit, nullptr, {}});

  auto CurrentField = Fields.begin();
  for (const CXXCtorInitializer *Init : Inits) {
    if (Init->isWritten() && Init->isMemberInitializer()) {
      const FieldDecl *MemberField = Init->getMember();
      // Add all fields between current field and this member field the previous
      // FieldsInsertion if the field requires initialization.
      for (; CurrentField != Fields.end() && *CurrentField != MemberField;
           ++CurrentField) {
        if (FieldsRequiringInit.count(*CurrentField) > 0)
          OrderedFields.back().Fields.push_back(*CurrentField);
      }
      // If this is the first written member initializer and there was no
      // written non-member initializer set this initializer as
      // InitializerAfter.
      if (OrderedFields.size() == 1 &&
          OrderedFields.back().InitializerBefore == nullptr)
        OrderedFields.back().InitializerAfter = Init;
      OrderedFields.push_back({Init, nullptr, {}});
    }
  }
  // Add remaining fields that require initialization to last FieldsInsertion.
  for (; CurrentField != Fields.end(); ++CurrentField) {
    if (FieldsRequiringInit.count(*CurrentField) > 0)
      OrderedFields.back().Fields.push_back(*CurrentField);
  }
  return OrderedFields;
}

} // namespace

void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(cxxConstructorDecl(isDefinition(), isUserProvided(),
                                        unless(isInstantiated()))
                         .bind("ctor"),
                     this);
}

void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
  const auto &MemberFields = Ctor->getParent()->fields();

  // Skip declarations delayed by late template parsing without a body.
  const Stmt *Body = Ctor->getBody();
  if (!Body)
    return;

  SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
  fieldsRequiringInit(MemberFields, FieldsToInit);
  if (FieldsToInit.empty())
    return;

  for (CXXCtorInitializer *Init : Ctor->inits()) {
    // Return early if this constructor simply delegates to another constructor
    // in the same class.
    if (Init->isDelegatingInitializer())
      return;
    if (!Init->isMemberInitializer())
      continue;
    FieldsToInit.erase(Init->getMember());
  }
  removeFieldsInitializedInBody(*Body, *Result.Context, FieldsToInit);

  if (FieldsToInit.empty())
    return;

  DiagnosticBuilder Diag =
      diag(Ctor->getLocStart(),
           "constructor does not initialize these built-in/pointer fields: %0")
      << toCommaSeparatedString(MemberFields, FieldsToInit);
  // Do not propose fixes in macros since we cannot place them correctly.
  if (Ctor->getLocStart().isMacroID())
    return;
  // For C+11 use in-class initialization which covers all future constructors
  // as well.
  if (Result.Context->getLangOpts().CPlusPlus11) {
    for (const auto *Field : FieldsToInit) {
      Diag << FixItHint::CreateInsertion(
          Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
                                     Result.Context->getSourceManager(),
                                     Result.Context->getLangOpts()),
          "{}");
    }
    return;
  }
  for (const auto &FieldsInsertion :
       computeInsertions(Ctor->inits(), MemberFields, FieldsToInit)) {
    if (!FieldsInsertion.Fields.empty())
      Diag << FixItHint::CreateInsertion(
          FieldsInsertion.getLocation(*Result.Context, *Ctor),
          FieldsInsertion.codeToInsert());
  }
}

} // namespace cppcoreguidelines
} // namespace tidy
} // namespace clang