-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clang-tidy] The first PR our of many PRs for the "Initialized Class Members" check. #65189
Changes from all commits
c6d70f6
ef9c4b6
a0d2270
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===--- CppInitClassMembersCheck.cpp - clang-tidy ------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include <string> | ||
|
||
#include "CppInitClassMembersCheck.h" | ||
#include "clang/AST/ASTContext.h" | ||
#include "clang/AST/Decl.h" | ||
#include "clang/AST/Type.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
#include "clang/ASTMatchers/ASTMatchers.h" | ||
#include "clang/ASTMatchers/ASTMatchersMacros.h" | ||
#include "clang/Basic/Diagnostic.h" | ||
#include "clang/Basic/LLVM.h" | ||
#include "llvm/ADT/STLExtras.h" | ||
#include "llvm/Support/raw_ostream.h" | ||
|
||
using namespace clang::ast_matchers; | ||
|
||
namespace clang::tidy::google { | ||
|
||
namespace { | ||
|
||
// Matches records that have a default constructor. | ||
AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) { | ||
return Node.hasDefaultConstructor(); | ||
} | ||
|
||
// Returns the names of `Fields` in a comma separated string. | ||
std::string | ||
toCommaSeparatedString(const ArrayRef<const FieldDecl *> &Fields) { | ||
std::string Buffer; | ||
llvm::raw_string_ostream OS(Buffer); | ||
llvm::interleave( | ||
Fields, OS, [&OS](const FieldDecl *Decl) { OS << Decl->getName(); }, | ||
", "); | ||
return Buffer; | ||
} | ||
|
||
// Returns `true` for types that have uninitialized values by default. For | ||
// example, returns `true` for `int` because an uninitialized `int` field or | ||
// local variable can contain uninitialized values. | ||
bool isDefaultValueUninitialized(QualType Ty) { | ||
if (Ty.isNull()) | ||
return false; | ||
|
||
// FIXME: For now, this check focuses on several allowlisted types. We will | ||
// expand coverage in future. | ||
return Ty->isIntegerType() || Ty->isBooleanType(); | ||
} | ||
|
||
} // anonymous namespace | ||
|
||
void CppInitClassMembersCheck::checkMissingMemberInitializer( | ||
ASTContext &Context, const CXXRecordDecl &ClassDecl, | ||
const CXXConstructorDecl *Ctor) { | ||
SmallVector<const FieldDecl *, 16> FieldsToReport; | ||
|
||
for (const FieldDecl *F : ClassDecl.fields()) { | ||
if (isDefaultValueUninitialized(F->getType()) && | ||
!F->hasInClassInitializer()) | ||
FieldsToReport.push_back(F); | ||
} | ||
|
||
if (FieldsToReport.empty()) | ||
return; | ||
|
||
DiagnosticBuilder Diag = | ||
Xazax-hun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
diag(Ctor ? Ctor->getBeginLoc() : ClassDecl.getLocation(), | ||
"%select{these fields should be initialized|constructor should " | ||
"initialize these fields}0: %1") | ||
<< (Ctor != nullptr) << toCommaSeparatedString(FieldsToReport); | ||
|
||
// FIXME: generate fixes. | ||
} | ||
|
||
void CppInitClassMembersCheck::registerMatchers(MatchFinder *Finder) { | ||
Finder->addMatcher(cxxConstructorDecl(isDefinition(), isDefaultConstructor(), | ||
adriannistor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unless(isUserProvided())) | ||
.bind("ctor"), | ||
this); | ||
|
||
Finder->addMatcher(cxxRecordDecl(isDefinition(), hasDefaultConstructor(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hasDefaultConstructor and unless(has(cxxConstructorDecl())) may opose them self, be more specific, like has user specific constructor.... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this. Why is We can refine this condition later. For now, just saying "is default constructor but has no declaration in the AST" is good enough (catches a lot of our cases). We can refine later if we have data (false positives or false negatives). My understanding is that you find this condition too broad? In that case, that is good. Because that will allow us to see false positives (and filter them out). If the condition would be too narrow, we would have false negatives. False negatives are a problem, because we may not be aware that we have them. In short, let's leave this as it is for now and we can refine later based on data. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not closing this because technically I did not change the code. However, if you think the comment above fully addresses this issue, please close this (if the comment does not fully address, please let me know what you think! --- thanks!) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The thing is that sometimes constructor declarations wont be visible in AST until they actually used first time, this may happen for implicit constructors. In such case, |
||
unless(isInstantiated()), | ||
unless(has(cxxConstructorDecl()))) | ||
.bind("record"), | ||
this); | ||
} | ||
|
||
void CppInitClassMembersCheck::check(const MatchFinder::MatchResult &Result) { | ||
if (const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor")) { | ||
checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor); | ||
} else if (const auto *Record = | ||
Result.Nodes.getNodeAs<CXXRecordDecl>("record")) { | ||
// For a record, perform the same action as for a constructor. However, | ||
// emit the diagnostic for the record, not for the constructor. | ||
checkMissingMemberInitializer(*Result.Context, *Record, nullptr); | ||
} | ||
} | ||
|
||
} // namespace clang::tidy::google | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
//===--- CppInitClassMembersCheck.h - clang-tidy ----------------*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_CPPINITCLASSMEMBERSCHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_CPPINITCLASSMEMBERSCHECK_H | ||
|
||
#include "../ClangTidyCheck.h" | ||
|
||
namespace clang::tidy::google { | ||
|
||
/// Checks that class members are initialized in constructors (implicitly or | ||
/// explicitly). Reports constructors or classes where class members are not | ||
/// initialized. The goal of this check is to eliminate UUM (Use of | ||
/// Uninitialized Memory) bugs caused by uninitialized class members. | ||
/// | ||
/// This check is under active development: the check authors made a few | ||
/// commits and are actively working on more commits. Users who want a mature | ||
/// and stable check should not use this check yet. | ||
/// | ||
/// This check is different from ProTypeMemberInitCheck in that this check | ||
/// attempts to eliminate UUMs as a bug class, at the expense of false | ||
/// positives. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/google/cpp-init-class-members.html | ||
class CppInitClassMembersCheck : public ClangTidyCheck { | ||
public: | ||
CppInitClassMembersCheck(StringRef Name, ClangTidyContext *Context) | ||
: ClangTidyCheck(Name, Context) {} | ||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
return LangOpts.CPlusPlus; | ||
} | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
|
||
private: | ||
// Issue a diagnostic for any constructor that does not initialize all member | ||
// variables. If the record does not have a constructor (`Ctor` is `nullptr`), | ||
// the diagnostic is for the record. | ||
void checkMissingMemberInitializer(ASTContext &Context, | ||
const CXXRecordDecl &ClassDecl, | ||
const CXXConstructorDecl *Ctor); | ||
}; | ||
|
||
} // namespace clang::tidy::google | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_CPPINITCLASSMEMBERSCHECK_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.. title:: clang-tidy - google-cpp-init-class-members | ||
|
||
google-cpp-init-class-members | ||
============================= | ||
|
||
Checks that class members are initialized in constructors (implicitly or | ||
adriannistor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
explicitly). Reports constructors or classes where class members are not | ||
initialized. The goal of this check is to eliminate UUM (Use of | ||
Uninitialized Memory) bugs caused by uninitialized class members. | ||
|
||
This check is under active development: the check authors made a few commits | ||
and are actively working on more commits. Users who want a mature and stable | ||
check should not use this check yet. | ||
|
||
This check is different from ProTypeMemberInitCheck in that this check | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ProTypeMemberInitCheck is an implementation detail - please refer to the user-facing check name (cppcoreguidelines-pro-type-member-init) together with a clickable link. Feel free to check out the Release Notes on how we typically refer to checks. |
||
attempts to eliminate UUMs as a bug class, at the expense of false | ||
positives. The authors of this check will add more documentation about the | ||
differences with ProTypeMemberInitCheck as the check evolves. | ||
|
||
For now, this check reports `X` in the following two patterns: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to show what the "fixed" code would look like. |
||
|
||
.. code-block:: c++ | ||
class SomeClass { | ||
public: | ||
SomeClass() = default; | ||
|
||
private: | ||
int X; | ||
}; | ||
|
||
.. code-block:: c++ | ||
struct SomeStruct { | ||
int X; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll be good idea to add link to relevant Coding Guidelines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, fully agree! I added a broader discussion below. To summarize (full context there), we are still working the high level specifications and the low level specifications based on data. Once we have a stable and mature version, we will update the documentation for this checker in a more concrete and actionable way. Until then, we will keep this file ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not closing this because technically I did not change the code. However, if you think the comment above fully addresses this issue, please close this (if the comment does not fully address, please let me know what you think! --- thanks!) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// RUN: %check_clang_tidy %s google-cpp-init-class-members %t | ||
|
||
// TODO: The following issues are not yet handled by the check and are not | ||
// covered by the tests. We may decide that the check needs to report some of | ||
// these cases and should not report some the remaining cases. This exact | ||
// decision is also part of the TODO: | ||
// - Deleted constructor | ||
// - Inheritance | ||
// - Struct with anonymous members | ||
// - Struct with anonymous union members | ||
// - Unnamed structs | ||
// - Nested structs | ||
// - etc | ||
|
||
class PositiveDefaultedDefaultConstructor { | ||
public: | ||
PositiveDefaultedDefaultConstructor() = default; | ||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: constructor should initialize these fields: X | ||
|
||
private: | ||
int X; | ||
}; | ||
|
||
class PositiveDefaultedDefaultConstructorWithInitializedField { | ||
public: | ||
PositiveDefaultedDefaultConstructorWithInitializedField() = default; | ||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: constructor should initialize these fields: X | ||
|
||
private: | ||
int X; | ||
int Y = 4; // no-warning | ||
}; | ||
|
||
class Helper { | ||
public: | ||
Helper(int x) : X(x) {} | ||
|
||
private: | ||
int X; | ||
}; | ||
|
||
class PositiveDefaultedConstructorObjectAndPrimitive { | ||
public: | ||
PositiveDefaultedConstructorObjectAndPrimitive() = default; | ||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: constructor should initialize these fields: Y | ||
|
||
Helper* GetHelper() { return &X; } | ||
|
||
void SetY(bool enabled) { Y = enabled; } | ||
|
||
bool IsY() { return Y; } | ||
|
||
private: | ||
Helper X; | ||
bool Y; | ||
}; | ||
|
||
struct PositiveStruct { | ||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: these fields should be initialized: X, Y | ||
int X; | ||
int Y; | ||
}; | ||
|
||
struct PositiveStructWithInitializedField { | ||
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: these fields should be initialized: Y | ||
int X = 3; // no-warning | ||
int Y; | ||
}; | ||
adriannistor marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if you will need to do more than checking for a couple fo types.
For example, consider the scenario:
Here, the user might have no control over OthersPair, it is coming from a 3rd party library. It does not have a constructor to initialize its members, so we need to add member initializers in
MyStruct
.