summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clang-tidy')
-rw-r--r--clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp450
-rw-r--r--clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h43
-rw-r--r--clang-tools-extra/clang-tidy/utils/Matchers.h5
-rw-r--r--clang-tools-extra/clang-tidy/utils/TypeTraits.cpp75
-rw-r--r--clang-tools-extra/clang-tidy/utils/TypeTraits.h7
5 files changed, 442 insertions, 138 deletions
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
index 672d222e595..645e0291ebc 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp
@@ -9,12 +9,15 @@
#include "ProTypeMemberInitCheck.h"
#include "../utils/LexerUtils.h"
+#include "../utils/Matchers.h"
+#include "../utils/TypeTraits.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 namespace clang::tidy::matchers;
using llvm::SmallPtrSet;
using llvm::SmallPtrSetImpl;
@@ -24,16 +27,13 @@ namespace cppcoreguidelines {
namespace {
-AST_MATCHER(CXXConstructorDecl, isUserProvided) {
- return Node.isUserProvided();
-}
-
-static void
-fieldsRequiringInit(const RecordDecl::field_range &Fields,
- SmallPtrSetImpl<const FieldDecl *> &FieldsToInit) {
+void fieldsRequiringInit(const RecordDecl::field_range &Fields,
+ ASTContext &Context,
+ SmallPtrSetImpl<const FieldDecl *> &FieldsToInit) {
for (const FieldDecl *F : Fields) {
QualType Type = F->getType();
- if (Type->isPointerType() || Type->isBuiltinType())
+ if (!F->hasInClassInitializer() &&
+ type_traits::isTriviallyDefaultConstructible(Type, Context))
FieldsToInit.insert(F);
}
}
@@ -50,134 +50,259 @@ void removeFieldsInitializedInBody(
FieldDecls.erase(Match.getNodeAs<FieldDecl>("fieldDecl"));
}
-// Creates comma separated list of fields requiring initialization in order of
+StringRef getName(const FieldDecl *Field) { return Field->getName(); }
+
+StringRef getName(const RecordDecl *Record) {
+ // Get the typedef name if this is a C-style anonymous struct and typedef.
+ if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl())
+ return Typedef->getName();
+ return Record->getName();
+}
+
+// Creates comma separated list of decls 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 << ", ";
- }
+template <typename R, typename T>
+std::string
+toCommaSeparatedString(const R &OrderedDecls,
+ const SmallPtrSetImpl<const T *> &DeclsToInit) {
+ SmallVector<StringRef, 16> Names;
+ for (const T *Decl : OrderedDecls) {
+ if (DeclsToInit.count(Decl))
+ Names.emplace_back(getName(Decl));
}
- return Stream.str();
+ return llvm::join(Names.begin(), Names.end(), ", ");
}
-// 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 getLocationForEndOfToken(const ASTContext &Context,
+ SourceLocation Location) {
+ return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(),
+ Context.getLangOpts());
+};
+
+// There are 3 kinds of insertion placements:
+enum class InitializerPlacement {
+ // 1. The fields are inserted after an existing CXXCtorInitializer stored in
+ // Where. This will be the case whenever there is a written initializer before
+ // the fields available.
+ After,
+
+ // 2. The fields are inserted before the first existing initializer stored in
+ // Where.
+ Before,
+
+ // 3. There are no written initializers and the fields will be inserted before
+ // the constructor's body creating a new initializer list including the ':'.
+ New
+};
+
+// An InitializerInsertion contains a list of fields and/or base classes to
+// insert into the initializer list of a constructor. We use this to ensure
+// proper absolute ordering according to the class declaration relative to the
+// (perhaps improper) ordering in the existing initializer list, if any.
+struct IntializerInsertion {
+ IntializerInsertion(InitializerPlacement Placement,
+ const CXXCtorInitializer *Where)
+ : Placement(Placement), Where(Where) {}
SourceLocation getLocation(const ASTContext &Context,
const CXXConstructorDecl &Constructor) const {
- if (InitializerBefore != nullptr) {
- return Lexer::getLocForEndOfToken(InitializerBefore->getRParenLoc(), 0,
- Context.getSourceManager(),
- Context.getLangOpts());
+ assert((Where != nullptr || Placement == InitializerPlacement::New) &&
+ "Location should be relative to an existing initializer or this "
+ "insertion represents a new initializer list.");
+ SourceLocation Location;
+ switch (Placement) {
+ case InitializerPlacement::New:
+ Location = lexer_utils::getPreviousNonCommentToken(
+ Context, Constructor.getBody()->getLocStart())
+ .getLocation();
+ break;
+ case InitializerPlacement::Before:
+ Location = lexer_utils::getPreviousNonCommentToken(
+ Context, Where->getSourceRange().getBegin())
+ .getLocation();
+ break;
+ case InitializerPlacement::After:
+ Location = Where->getRParenLoc();
+ break;
}
- 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());
+ return getLocationForEndOfToken(Context, Location);
}
std::string codeToInsert() const {
- assert(!Fields.empty() && "No fields to insert");
+ assert(!Initializers.empty() && "No initializers 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() << "()";
+ std::string joined =
+ llvm::join(Initializers.begin(), Initializers.end(), "(), ");
+ switch (Placement) {
+ case InitializerPlacement::New:
+ Stream << " : " << joined << "()";
+ break;
+ case InitializerPlacement::Before:
+ Stream << " " << joined << "(),";
+ break;
+ case InitializerPlacement::After:
+ Stream << ", " << joined << "()";
+ break;
}
- Stream.flush();
- // The initializer list is created, replace leading comma with colon.
- if (InitializerBefore == nullptr && InitializerAfter == nullptr)
- Code[1] = ':';
return Code;
}
+
+ InitializerPlacement Placement;
+ const CXXCtorInitializer *Where;
+ SmallVector<std::string, 4> Initializers;
};
-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, {}});
+// Convenience utility to get a RecordDecl from a QualType.
+const RecordDecl *getCanonicalRecordDecl(const QualType &Type) {
+ if (const auto *RT = Type.getCanonicalType()->getAs<RecordType>())
+ return RT->getDecl();
+ return nullptr;
+}
+
+template <typename R, typename T>
+SmallVector<IntializerInsertion, 16>
+computeInsertions(const CXXConstructorDecl::init_const_range &Inits,
+ const R &OrderedDecls,
+ const SmallPtrSetImpl<const T *> &DeclsToInit) {
+ SmallVector<IntializerInsertion, 16> Insertions;
+ Insertions.emplace_back(InitializerPlacement::New, nullptr);
- auto CurrentField = Fields.begin();
+ typename R::const_iterator Decl = std::begin(OrderedDecls);
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 (Init->isWritten()) {
+ if (Insertions.size() == 1)
+ Insertions.emplace_back(InitializerPlacement::Before, Init);
+
+ // Gets either the field or base class being initialized by the provided
+ // initializer.
+ const auto *InitDecl =
+ Init->isMemberInitializer()
+ ? static_cast<const NamedDecl *>(Init->getMember())
+ : Init->getBaseClass()->getAs<RecordType>()->getDecl();
+
+ // Add all fields between current field up until the next intializer.
+ for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) {
+ if (const T *D = dyn_cast<T>(*Decl)) {
+ if (DeclsToInit.count(D) > 0)
+ Insertions.back().Initializers.emplace_back(getName(D));
+ }
}
- // 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, {}});
+
+ Insertions.emplace_back(InitializerPlacement::After, Init);
}
}
- // 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);
+
+ // Add remaining decls that require initialization.
+ for (; Decl != std::end(OrderedDecls); ++Decl) {
+ if (const T *D = dyn_cast<T>(*Decl)) {
+ if (DeclsToInit.count(D) > 0)
+ Insertions.back().Initializers.emplace_back(getName(D));
+ }
+ }
+ return Insertions;
+}
+
+// Gets the list of bases and members that could possibly be initialized, in
+// order as they appear in the class declaration.
+void getInitializationsInOrder(const CXXRecordDecl *ClassDecl,
+ SmallVectorImpl<const NamedDecl *> &Decls) {
+ Decls.clear();
+ for (const auto &Base : ClassDecl->bases())
+ Decls.emplace_back(getCanonicalRecordDecl(Base.getType()));
+ Decls.append(ClassDecl->fields().begin(), ClassDecl->fields().end());
+}
+
+template <typename T>
+void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag,
+ const CXXConstructorDecl *Ctor,
+ const SmallPtrSetImpl<const T *> &DeclsToInit) {
+ // Do not propose fixes in macros since we cannot place them correctly.
+ if (Ctor->getLocStart().isMacroID())
+ return;
+
+ SmallVector<const NamedDecl *, 16> OrderedDecls;
+ getInitializationsInOrder(Ctor->getParent(), OrderedDecls);
+
+ for (const auto &Insertion :
+ computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) {
+ if (!Insertion.Initializers.empty())
+ Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor),
+ Insertion.codeToInsert());
+ }
+}
+
+template <typename T, typename Func>
+void forEachField(const RecordDecl *Record, const T &Fields,
+ bool OneFieldPerUnion, Func &&Fn) {
+ for (const FieldDecl *F : Fields) {
+ if (F->isAnonymousStructOrUnion()) {
+ if (const RecordDecl *R = getCanonicalRecordDecl(F->getType()))
+ forEachField(R, R->fields(), OneFieldPerUnion, Fn);
+ } else {
+ Fn(F);
+ }
+
+ if (OneFieldPerUnion && Record->isUnion())
+ break;
}
- return OrderedFields;
}
} // namespace
+ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ IgnoreArrays(Options.get("IgnoreArrays", false)) {}
+
void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) {
- Finder->addMatcher(cxxConstructorDecl(isDefinition(), isUserProvided(),
- unless(isInstantiated()))
- .bind("ctor"),
- this);
+ if (!getLangOpts().CPlusPlus)
+ return;
+
+ auto IsUserProvidedNonDelegatingConstructor =
+ allOf(isUserProvided(),
+ unless(anyOf(isInstantiated(), isDelegatingConstructor())));
+ auto IsNonTrivialDefaultConstructor = allOf(
+ isDefaultConstructor(), unless(isUserProvided()),
+ hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible()))));
+ Finder->addMatcher(
+ cxxConstructorDecl(isDefinition(),
+ anyOf(IsUserProvidedNonDelegatingConstructor,
+ IsNonTrivialDefaultConstructor))
+ .bind("ctor"),
+ this);
+ auto HasDefaultConstructor = hasInitializer(
+ cxxConstructExpr(unless(requiresZeroInitialization()),
+ hasDeclaration(cxxConstructorDecl(
+ isDefaultConstructor(), unless(isUserProvided())))));
+ Finder->addMatcher(
+ varDecl(isDefinition(), HasDefaultConstructor,
+ hasType(recordDecl(has(fieldDecl()),
+ isTriviallyDefaultConstructible())))
+ .bind("var"),
+ this);
}
void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
- const auto &MemberFields = Ctor->getParent()->fields();
+ if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) {
+ checkMissingMemberInitializer(*Result.Context, Ctor);
+ checkMissingBaseClassInitializer(*Result.Context, Ctor);
+ } else if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var")) {
+ checkUninitializedTrivialType(*Result.Context, Var);
+ }
+}
+
+void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IgnoreArrays", IgnoreArrays);
+}
+
+void ProTypeMemberInitCheck::checkMissingMemberInitializer(
+ ASTContext &Context, const CXXConstructorDecl *Ctor) {
+ const CXXRecordDecl *ClassDecl = Ctor->getParent();
+ bool IsUnion = ClassDecl->isUnion();
+
+ if (IsUnion && ClassDecl->hasInClassInitializer())
+ return;
// Skip declarations delayed by late template parsing without a body.
const Stmt *Body = Ctor->getBody();
@@ -185,50 +310,113 @@ void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
return;
SmallPtrSet<const FieldDecl *, 16> FieldsToInit;
- fieldsRequiringInit(MemberFields, FieldsToInit);
+ fieldsRequiringInit(ClassDecl->fields(), Context, 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());
+ for (const CXXCtorInitializer *Init : Ctor->inits()) {
+ // Remove any fields that were explicitly written in the initializer list
+ // or in-class.
+ if (Init->isAnyMemberInitializer() && Init->isWritten()) {
+ if (IsUnion)
+ return; // We can only initialize one member of a union.
+ FieldsToInit.erase(Init->getMember());
+ }
}
- removeFieldsInitializedInBody(*Body, *Result.Context, FieldsToInit);
+ removeFieldsInitializedInBody(*Body, Context, FieldsToInit);
- if (FieldsToInit.empty())
+ // Collect all fields in order, both direct fields and indirect fields from
+ // anonmyous record types.
+ SmallVector<const FieldDecl *, 16> OrderedFields;
+ forEachField(ClassDecl, ClassDecl->fields(), false,
+ [&](const FieldDecl *F) { OrderedFields.push_back(F); });
+
+ // Collect all the fields we need to initialize, including indirect fields.
+ SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
+ forEachField(ClassDecl, FieldsToInit, false,
+ [&](const FieldDecl *F) { AllFieldsToInit.insert(F); });
+ if (AllFieldsToInit.empty())
return;
DiagnosticBuilder Diag =
diag(Ctor->getLocStart(),
- "constructor does not initialize these built-in/pointer fields: %0")
- << toCommaSeparatedString(MemberFields, FieldsToInit);
+ IsUnion
+ ? "union constructor should initialize one of these fields: %0"
+ : "constructor does not initialize these fields: %0")
+ << toCommaSeparatedString(OrderedFields, AllFieldsToInit);
+
// 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) {
+
+ // Collect all fields but only suggest a fix for the first member of unions,
+ // as initializing more than one union member is an error.
+ SmallPtrSet<const FieldDecl *, 16> FieldsToFix;
+ forEachField(ClassDecl, FieldsToInit, true, [&](const FieldDecl *F) {
+ // Don't suggest fixes for enums because we don't know a good default.
+ if (!F->getType()->isEnumeralType())
+ FieldsToFix.insert(F);
+ });
+ if (FieldsToFix.empty())
+ return;
+
+ // Use in-class initialization if possible.
+ if (Context.getLangOpts().CPlusPlus11) {
+ for (const FieldDecl *Field : FieldsToFix) {
Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
- Result.Context->getSourceManager(),
- Result.Context->getLangOpts()),
+ getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()),
"{}");
}
- return;
+ } else {
+ // Otherwise, rewrite the constructor's initializer list.
+ fixInitializerList(Context, Diag, Ctor, FieldsToFix);
}
- for (const auto &FieldsInsertion :
- computeInsertions(Ctor->inits(), MemberFields, FieldsToInit)) {
- if (!FieldsInsertion.Fields.empty())
- Diag << FixItHint::CreateInsertion(
- FieldsInsertion.getLocation(*Result.Context, *Ctor),
- FieldsInsertion.codeToInsert());
+}
+
+void ProTypeMemberInitCheck::checkMissingBaseClassInitializer(
+ const ASTContext &Context, const CXXConstructorDecl *Ctor) {
+ const CXXRecordDecl *ClassDecl = Ctor->getParent();
+
+ // Gather any base classes that need to be initialized.
+ SmallVector<const RecordDecl *, 4> AllBases;
+ SmallPtrSet<const RecordDecl *, 4> BasesToInit;
+ for (const CXXBaseSpecifier &Base : ClassDecl->bases()) {
+ if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) {
+ AllBases.emplace_back(BaseClassDecl);
+ if (!BaseClassDecl->field_empty() &&
+ type_traits::isTriviallyDefaultConstructible(Base.getType(), Context))
+ BasesToInit.insert(BaseClassDecl);
+ }
}
+
+ if (BasesToInit.empty())
+ return;
+
+ // Remove any bases that were explicitly written in the initializer list.
+ for (const CXXCtorInitializer *Init : Ctor->inits()) {
+ if (Init->isBaseInitializer() && Init->isWritten())
+ BasesToInit.erase(Init->getBaseClass()->getAs<RecordType>()->getDecl());
+ }
+
+ if (BasesToInit.empty())
+ return;
+
+ DiagnosticBuilder Diag =
+ diag(Ctor->getLocStart(),
+ "constructor does not initialize these bases: %0")
+ << toCommaSeparatedString(AllBases, BasesToInit);
+
+ fixInitializerList(Context, Diag, Ctor, BasesToInit);
+}
+
+void ProTypeMemberInitCheck::checkUninitializedTrivialType(
+ const ASTContext &Context, const VarDecl *Var) {
+ DiagnosticBuilder Diag =
+ diag(Var->getLocStart(), "uninitialized record type: %0") << Var;
+
+ Diag << FixItHint::CreateInsertion(
+ getLocationForEndOfToken(Context, Var->getSourceRange().getEnd()),
+ Context.getLangOpts().CPlusPlus11 ? "{}" : " = {}");
}
} // namespace cppcoreguidelines
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h
index 60765b82750..401fe0378f2 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h
@@ -16,8 +16,12 @@ namespace clang {
namespace tidy {
namespace cppcoreguidelines {
-/// \brief Checks that builtin or pointer fields are initialized by
-/// user-defined constructors.
+/// \brief Implements C++ Core Guidelines Type.6.
+///
+/// Checks that every user-provided constructor value-initializes all class
+/// members and base classes that would have undefined behavior otherwise. Also
+/// check that any record types without user-provided default constructors are
+/// value-initialized where used.
///
/// Members initialized through function calls in the body of the constructor
/// will result in false positives.
@@ -25,15 +29,40 @@ namespace cppcoreguidelines {
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.html
/// TODO: See if 'fixes' for false positives are optimized away by the compiler.
-/// TODO: "Issue a diagnostic when constructing an object of a trivially
-/// constructible type without () or {} to initialize its members. To fix: Add
-/// () or {}."
+/// TODO: For classes with multiple constructors, make sure that we don't offer
+/// multiple in-class initializer fixits for the same member.
class ProTypeMemberInitCheck : public ClangTidyCheck {
public:
- ProTypeMemberInitCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ ProTypeMemberInitCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+ // Checks Type.6 part 1:
+ // Issue a diagnostic for any constructor of a non-trivially-constructible
+ // type that does not initialize all member variables.
+ //
+ // To fix: Write a data member initializer, or mention it in the member
+ // initializer list.
+ void checkMissingMemberInitializer(ASTContext &Context,
+ const CXXConstructorDecl *Ctor);
+
+ // A subtle side effect of Type.6 part 2:
+ // Make sure to initialize trivially constructible base classes.
+ void checkMissingBaseClassInitializer(const ASTContext &Context,
+ const CXXConstructorDecl *Ctor);
+
+ // Checks Type.6 part 2:
+ // Issue a diagnostic when constructing an object of a trivially constructible
+ // type without () or {} to initialize its members.
+ //
+ // To fix: Add () or {}.
+ void checkUninitializedTrivialType(const ASTContext &Context,
+ const VarDecl *Var);
+
+ // Whether arrays need to be initialized or not. Default is false.
+ bool IgnoreArrays;
};
} // namespace cppcoreguidelines
diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h
index 834c06b2861..94ed53a5089 100644
--- a/clang-tools-extra/clang-tidy/utils/Matchers.h
+++ b/clang-tools-extra/clang-tidy/utils/Matchers.h
@@ -23,6 +23,11 @@ AST_MATCHER(QualType, isExpensiveToCopy) {
return IsExpensive && *IsExpensive;
}
+AST_MATCHER(RecordDecl, isTriviallyDefaultConstructible) {
+ return type_traits::recordIsTriviallyDefaultConstructible(
+ Node, Finder->getASTContext());
+}
+
} // namespace matchers
} // namespace tidy
} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp
index 6c17ee356a0..8e63f0a22d0 100644
--- a/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp
+++ b/clang-tools-extra/clang-tidy/utils/TypeTraits.cpp
@@ -31,6 +31,81 @@ llvm::Optional<bool> isExpensiveToCopy(QualType Type, ASTContext &Context) {
!classHasTrivialCopyAndDestroy(Type);
}
+bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl,
+ const ASTContext &Context) {
+ const auto *ClassDecl = dyn_cast<CXXRecordDecl>(&RecordDecl);
+ // Non-C++ records are always trivially constructible.
+ if (!ClassDecl)
+ return true;
+ // A class with a user-provided default constructor is not trivially
+ // constructible.
+ if (ClassDecl->hasUserProvidedDefaultConstructor())
+ return false;
+ // A class is trivially constructible if it has a trivial default constructor.
+ if (ClassDecl->hasTrivialDefaultConstructor())
+ return true;
+
+ // If all its fields are trivially constructible.
+ for (const FieldDecl *Field : ClassDecl->fields()) {
+ if (!isTriviallyDefaultConstructible(Field->getType(), Context))
+ return false;
+ }
+ // If all its direct bases are trivially constructible.
+ for (const CXXBaseSpecifier &Base : ClassDecl->bases()) {
+ if (!isTriviallyDefaultConstructible(Base.getType(), Context))
+ return false;
+ }
+
+ return true;
+}
+
+// Based on QualType::isTrivial.
+bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context) {
+ if (Type.isNull())
+ return false;
+
+ if (Type->isArrayType())
+ return isTriviallyDefaultConstructible(Context.getBaseElementType(Type),
+ Context);
+
+ // Return false for incomplete types after skipping any incomplete array
+ // types which are expressly allowed by the standard and thus our API.
+ if (Type->isIncompleteType())
+ return false;
+
+ if (Context.getLangOpts().ObjCAutoRefCount) {
+ switch (Type.getObjCLifetime()) {
+ case Qualifiers::OCL_ExplicitNone:
+ return true;
+
+ case Qualifiers::OCL_Strong:
+ case Qualifiers::OCL_Weak:
+ case Qualifiers::OCL_Autoreleasing:
+ return false;
+
+ case Qualifiers::OCL_None:
+ if (Type->isObjCLifetimeType())
+ return false;
+ break;
+ }
+ }
+
+ QualType CanonicalType = Type.getCanonicalType();
+ if (CanonicalType->isDependentType())
+ return false;
+
+ // As an extension, Clang treats vector types as Scalar types.
+ if (CanonicalType->isScalarType() || CanonicalType->isVectorType())
+ return true;
+
+ if (const auto *RT = CanonicalType->getAs<RecordType>()) {
+ return recordIsTriviallyDefaultConstructible(*RT->getDecl(), Context);
+ }
+
+ // No other types can match.
+ return false;
+}
+
} // type_traits
} // namespace tidy
} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/utils/TypeTraits.h b/clang-tools-extra/clang-tidy/utils/TypeTraits.h
index 573e61f2f5e..c612e5613d5 100644
--- a/clang-tools-extra/clang-tidy/utils/TypeTraits.h
+++ b/clang-tools-extra/clang-tidy/utils/TypeTraits.h
@@ -20,6 +20,13 @@ namespace type_traits {
// \brief Returns true If \c Type is expensive to copy.
llvm::Optional<bool> isExpensiveToCopy(QualType Type, ASTContext &Context);
+// \brief Returns true If \c Type is trivially default constructible.
+bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context);
+
+// \brief Returns true If \c RecordDecl is trivially default constructible.
+bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl,
+ const ASTContext &Context);
+
} // type_traits
} // namespace tidy
} // namespace clang
OpenPOWER on IntegriCloud