Skip to content
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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/google/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_clang_library(clangTidyGoogleModule
AvoidNSObjectNewCheck.cpp
AvoidThrowingObjCExceptionCheck.cpp
AvoidUnderscoreInGoogletestNameCheck.cpp
CppInitClassMembersCheck.cpp
DefaultArgumentsCheck.cpp
ExplicitConstructorCheck.cpp
ExplicitMakePairCheck.cpp
Expand Down
105 changes: 105 additions & 0 deletions clang-tools-extra/clang-tidy/google/CppInitClassMembersCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//===--- 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 SmallVector<const FieldDecl *, 16> &Fields) {
adriannistor marked this conversation as resolved.
Show resolved Hide resolved
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 field types that should be reported (if additional
// conditions are also met). For example, returns `true` for `int` because an
// uninitialized `int` field can contain uninitialized values.
bool shouldReportThisFieldType(QualType Ty) {
adriannistor marked this conversation as resolved.
Show resolved Hide resolved
if (Ty.isNull())
return false;

// FIXME: For now, this checker focuses on several allowlisted types. We will
adriannistor marked this conversation as resolved.
Show resolved Hide resolved
// 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 (shouldReportThisFieldType(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(),
Copy link
Member

Choose a reason for hiding this comment

The 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....

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. Why is "may opose [sic] them self" a problem? If they sometimes oppose themselves, the condition is false and the matcher is not added. The important thing is to not oppose themselves always (in which case the condition would always be false), but that is not the case here.

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!

Copy link
Author

Choose a reason for hiding this comment

The 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!)

Copy link
Member

Choose a reason for hiding this comment

The 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, hasDefaultConstructor may return true, but unless(has(cxxConstructorDecl())) may return false or true, depend if constructor is used or not. I assume that what you wanted to check is to check if default constructor is implicit. Other thing that instead of using "has" it should be "hasMethod".
And here you could use things like unless(has(cxxConstructorDecl(unless(isImplicit())))).

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
52 changes: 52 additions & 0 deletions clang-tools-extra/clang-tidy/google/CppInitClassMembersCheck.h
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 checker is to eliminate UUM (Use of
/// Uninitialized Memory) bugs caused by uninitialized class members.
///
/// This checker is under active development: the checker authors made a few
/// commits and are actively working on more commits. Users who want a mature
/// and stable checker should not use this checker yet.
///
/// This checker is different from ProTypeMemberInitCheck in that this checker
/// 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 && !LangOpts.ObjC;
adriannistor marked this conversation as resolved.
Show resolved Hide resolved
}
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
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "AvoidNSObjectNewCheck.h"
#include "AvoidThrowingObjCExceptionCheck.h"
#include "AvoidUnderscoreInGoogletestNameCheck.h"
#include "CppInitClassMembersCheck.h"
#include "DefaultArgumentsCheck.h"
#include "ExplicitConstructorCheck.h"
#include "ExplicitMakePairCheck.h"
Expand Down Expand Up @@ -43,6 +44,8 @@ class GoogleModule : public ClangTidyModule {
"google-build-namespaces");
CheckFactories.registerCheck<build::UsingNamespaceDirectiveCheck>(
"google-build-using-namespace");
CheckFactories.registerCheck<CppInitClassMembersCheck>(
"google-cpp-init-class-members");
CheckFactories.registerCheck<DefaultArgumentsCheck>(
"google-default-arguments");
CheckFactories.registerCheck<ExplicitConstructorCheck>(
Expand Down
10 changes: 10 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ New checks
Flags coroutines that suspend while a lock guard is in scope at the
suspension point.

- New :doc:`google-cpp-init-class-members
<clang-tidy/checks/google/cpp-init-class-members>` check.

Checks that class members are initialized in constructors (implicitly or
adriannistor marked this conversation as resolved.
Show resolved Hide resolved
explicitly). This checker aims to completely eliminate UUM (Use of
Uninitialized Memory) bugs caused by uninitialized class members. This checker
is under active development: the checker authors made a few commits and are
actively working on more commits. Users who want a mature and stable checker
should not use this checker yet.

- New :doc:`modernize-use-constraints
<clang-tidy/checks/modernize/use-constraints>` check.

Expand Down
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 checker is to eliminate UUM (Use of
Uninitialized Memory) bugs caused by uninitialized class members.

This checker is under active development: the checker authors made a few commits
and are actively working on more commits. Users who want a mature and stable
checker should not use this checker yet.

This checker is different from ProTypeMemberInitCheck in that this checker
attempts to eliminate UUMs as a bug class, at the expense of false
positives. The authors of this checker will add more documentation about the
differences with ProTypeMemberInitCheck as the checker evolves.

For now, this checker reports `X` in the following two patterns:

.. code-block:: c++
class SomeClass {
public:
SomeClass() = default;

private:
int X;
};

.. code-block:: c++
struct SomeStruct {
int X;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be good idea to add link to relevant Coding Guidelines.

Copy link
Author

Choose a reason for hiding this comment

The 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 (cpp-init-class-members.rst) and the unit tests as a high level and low level specifications, respectively. Full details in the broader discussion below. Thanks!

Copy link
Author

Choose a reason for hiding this comment

The 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!)

1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ Clang-Tidy Checks
:doc:`google-build-explicit-make-pair <google/build-explicit-make-pair>`,
:doc:`google-build-namespaces <google/build-namespaces>`,
:doc:`google-build-using-namespace <google/build-using-namespace>`,
:doc:`google-cpp-init-class-members <google/cpp-init-class-members>`, "Yes"
:doc:`google-default-arguments <google/default-arguments>`,
:doc:`google-explicit-constructor <google/explicit-constructor>`, "Yes"
:doc:`google-global-names-in-headers <google/global-names-in-headers>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// RUN: %check_clang_tidy %s google-cpp-init-class-members %t

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