Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 91 additions & 3 deletions lib/checkclass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,65 @@ void CheckClassImpl::handleUnionMembers(std::vector<Usage>& usageList)
}
}

static std::vector<const Token *> getInheritedConstructors(const Scope *scope)
{
std::vector<const Token *> result;

if (!scope || !scope->bodyStart || !scope->definedType)
return result;

const auto isInherited = [](const Function &baseCtor, const Scope *scope) {
if (baseCtor.type != FunctionType::eConstructor || baseCtor.argCount() == 0)
return false;
if (baseCtor.isDelete() || baseCtor.access == AccessControl::Private)
return false;
return std::none_of(scope->functionList.cbegin(), scope->functionList.cend(), [&](const Function &func) {
return func.isConstructor() && func.argCount() == baseCtor.argCount() &&
func.argsMatch(baseCtor.nestedIn, baseCtor.argDef, func.argDef, "", 0);
});
};

const std::vector<Type::BaseInfo> &bases = scope->definedType->derivedFrom;
const Token *baseName = nullptr;
const std::list<Function> *baseFuncs = nullptr;
for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "using %name%")) {
const auto base = std::find_if(
bases.cbegin(),
bases.cend(),
[&tok](const Type::BaseInfo &info) {
return info.name == tok->strAt(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

For a template-instantiated base, info.name is B7<int>. Neither using B7::B7; nor using B7<int>::B7; matches it, so struct D : B7<int> { using B7::B7; int x; }; gets no warning. This is a false negative, not a false positive.

}
);
if (base != bases.cend() && base->type && base->type->classScope) {
baseName = tok->next();
baseFuncs = &base->type->classScope->functionList;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

This only looks at constructors that the base declares itself. If the base inherits its constructors with its own using A::A;, its functionList has none, so this is a false negative:

struct A { A(int); };
struct B : A { using A::A; };
struct D : B { using B::B; int x; };   // no warning for D::x

It would be worth adding at least a TODO test for this.

}
continue;
}

if (tok->str() == ";") {
baseName = nullptr;
continue;
}

if (tok->str() == "{") {
baseName = nullptr;
tok = tok->link();
continue;
}

if (baseName && Token::Match(tok, "%name% :: %name% ;") &&
tok->str() == baseName->str() && tok->strAt(2) == baseName->str() &&
std::any_of(baseFuncs->cbegin(), baseFuncs->cend(), [&](const Function &func) {
return isInherited(func, scope);
}))
result.push_back(tok->tokAt(2));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

This only matches the using Base::Base; syntax. It never checks that Base has a constructor that gets inherited and used. Default, copy and move constructors are never inherited, and a base constructor is hidden when the derived class declares one with the same parameters. This produces false positives:

struct Base3 { Base3(int); };
struct D3 : Base3 { using Base3::Base3; D3(int) : Base3(0), x(0) {} int x; };

The PR warns: Member variable 'D3::x' is not initialized in the constructor inherited from 'Base3'. Nothing is inherited here.

The same happens for struct Base1 { Base1(); }; and for struct Base2 {};, which the base build reports nothing for. It also happens when the base constructor is = delete or private.

When the base type is known, it would be better to look at its functionList. Count only constructors that are not default, copy or move constructors and are not deleted. Leave out those whose signature is redeclared in the derived class.

}

return result;
}

//---------------------------------------------------------------------------
// ClassCheck: Check that all class constructors are ok.
//---------------------------------------------------------------------------
Expand Down Expand Up @@ -223,9 +282,11 @@ void CheckClassImpl::constructors()
});
});

const std::vector<const Token *> inheritedCtors = getInheritedConstructors(scope);

// There are no constructors.
std::set<const Variable*> diagVars;
if (scope->numConstructors == 0 && printStyle && !usedInUnion) {
if (scope->numConstructors == 0 && inheritedCtors.empty() && printStyle && !usedInUnion) {
// If there is a private variable, there should be a constructor..
int needInit = 0, haveInit = 0;
std::vector<const Variable*> uninitVars;
Expand Down Expand Up @@ -339,6 +400,30 @@ void CheckClassImpl::constructors()
}
}

if (!inheritedCtors.empty()) {
clearAllVar(usageList);

for (Usage &usage : usageList) {
if (usage.var->hasDefault())
usage.init = true;
}

handleUnionMembers(usageList);

// Inherited constructors don't initialize members of the derived class
for (const Token *baseCtorTok : inheritedCtors) {
for (const Usage &usage : usageList) {
if (isInitialized(usage, FunctionType::eConstructor))
continue;
const Variable& var = *usage.var;
if (var.typeScope() && var.typeScope()->numConstructors > 0)
continue;
uninitVarError(baseCtorTok, false, FunctionType::eConstructor, var.scope()->className, var.name(), scope != var.scope(), false, false, true);
diagVars.emplace(&var);
}
}
}

if (scope->numConstructors == 0) {

// Mark all variables not used
Expand Down Expand Up @@ -1172,12 +1257,15 @@ void CheckClassImpl::noExplicitConstructorError(const Token *tok, const std::str
reportError(tok, Severity::style, "noExplicitConstructor", "$symbol:" + classname + '\n' + message + '\n' + verbose, CWE398, Certainty::normal);
}

void CheckClassImpl::uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor)
void CheckClassImpl::uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor, bool inheritedCtor)
{
std::string message("Member variable '$symbol' ");
if (noCtor)
message += "has no initializer.";
else {
else if (inheritedCtor) {
message += tok ? "is not initialized in the constructor inherited from '" + tok->str() + "'." :
"is not initialized in the inherited constructor.";
} else {
message += "is not initialized in the ";
if (functionType == FunctionType::eCopyConstructor)
message += "copy ";
Expand Down
2 changes: 1 addition & 1 deletion lib/checkclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class CPPCHECKLIB CheckClassImpl : public CheckImpl {
void noCopyConstructorError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive);
void noOperatorEqError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive);
void noDestructorError(const Scope *scope, bool isdefault, const Token *alloc);
void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor = false);
void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor = false, bool inheritedCtor = false);
void uninitVarError(const Token *tok, const std::string &classname, const std::string &varname);
void missingMemberCopyError(const Token *tok, FunctionType functionType, const std::string& classname, const std::string& varname);
void operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive);
Expand Down
140 changes: 140 additions & 0 deletions test/testconstructors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,17 @@ class TestConstructors : public TestFixture {
TEST_CASE(uninitTemplate1); // ticket #7372

TEST_CASE(unknownTemplateType);

TEST_CASE(inheritedCtor1);
TEST_CASE(inheritedCtor2);
TEST_CASE(inheritedCtor3);
TEST_CASE(inheritedCtor4);
TEST_CASE(inheritedCtor5);
TEST_CASE(inheritedCtor6);
TEST_CASE(inheritedCtor7);
TEST_CASE(inheritedCtor8);
TEST_CASE(inheritedCtor9);
TEST_CASE(inheritedCtor10);
}


Expand Down Expand Up @@ -4648,6 +4659,135 @@ class TestConstructors : public TestFixture {
ASSERT_EQUALS("", errout_str());
}

void inheritedCtor1() {
check("struct Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str());
}

void inheritedCtor2() {
check("struct Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x = 0;\n"
" int y{};\n"
"};\n");
ASSERT_EQUALS("", errout_str());
}

void inheritedCtor3() {
check("struct Base { Base(int); };\n"
"struct S { S(); int i; };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" S s;\n"
"};\n");
ASSERT_EQUALS("", errout_str());
}

void inheritedCtor4() {
check("struct Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x = 0;\n"
" int y;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::y' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str());
}

void inheritedCtor5() {
check("struct Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" Derived() : x(0) {}\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str());
}

void inheritedCtor6() {
check("struct A { A(int); };\n"
"struct B { B(double); };\n"
"struct D : A, B {\n"
" using A::A;\n"
" using B::B;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:4:14]: (warning) Member variable 'D::x' is not initialized in the constructor inherited from 'A'. [uninitMemberVar]\n"
"[test.cpp:5:14]: (warning) Member variable 'D::x' is not initialized in the constructor inherited from 'B'. [uninitMemberVar]\n",
errout_str());
}

void inheritedCtor7() {
check("struct Base { void f(); };\n"
"struct Derived : Base {\n"
" using Base::f;\n"
" int x = 0;\n"
" int y;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:5:9]: (warning) Member variable 'Derived::y' has no initializer. [uninitMemberVarNoCtor]\n", errout_str());
}

void inheritedCtor8() {
check("struct Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" Derived(int i) : Base(i), x(0) {}\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());

check("struct Base { Base(int); Base(double); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" Derived(int i) : Base(i), x(0) {}\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str());
}

void inheritedCtor9() {
check("struct Base { Base(); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());

check("struct Base {};\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());

check("struct Base { Base(const Base&); Base(Base&&); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());
}

void inheritedCtor10() {
check("struct Base { Base(int) = delete; };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());

check("class Base { Base(int); };\n"
"struct Derived : Base {\n"
" using Base::Base;\n"
" int x;\n"
"};\n");
ASSERT_EQUALS("", errout_str());
}

};

REGISTER_TEST(TestConstructors)
Loading