-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix #14150 (Add unionzeroinit check) #7843
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
Changes from all commits
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,209 @@ | ||||||||||||||
| /* | ||||||||||||||
|
Owner
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. My feeling is that we could add this in some existing checkers class.. checkother maybe. it is just 1 checker in this class and I doubt we will add more related checkers here later.
Contributor
Author
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 find that argument compelling and rather keep the logic separated.
Owner
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. we have more than 100 checkers and we have always grouped checkers into classes we did not write each checker in its own class.
Owner
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 would find it more difficult to navigate our sourcecode, buildfiles, etc if we had 100's more of checkxyz.cpp , checkxyz.h and testxyz.cpp files.. |
||||||||||||||
| * Cppcheck - A tool for static C/C++ code analysis | ||||||||||||||
| * Copyright (C) 2007-2025 Cppcheck team. | ||||||||||||||
| * | ||||||||||||||
| * This program is free software: you can redistribute it and/or modify | ||||||||||||||
| * it under the terms of the GNU General Public License as published by | ||||||||||||||
| * the Free Software Foundation, either version 3 of the License, or | ||||||||||||||
| * (at your option) any later version. | ||||||||||||||
| * | ||||||||||||||
| * This program is distributed in the hope that it will be useful, | ||||||||||||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||||||||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||||||||||
| * GNU General Public License for more details. | ||||||||||||||
| * | ||||||||||||||
| * You should have received a copy of the GNU General Public License | ||||||||||||||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||||||||||||
| */ | ||||||||||||||
|
|
||||||||||||||
| #include "checkunionzeroinit.h" | ||||||||||||||
|
|
||||||||||||||
| #include <sstream> | ||||||||||||||
| #include <unordered_map> | ||||||||||||||
| #include <vector> | ||||||||||||||
|
|
||||||||||||||
| #include "errortypes.h" | ||||||||||||||
| #include "settings.h" | ||||||||||||||
| #include "symboldatabase.h" | ||||||||||||||
| #include "token.h" | ||||||||||||||
| #include "tokenize.h" | ||||||||||||||
| #include "valueflow.h" | ||||||||||||||
|
|
||||||||||||||
| static const CWE CWEXXX(0U); /* TODO */ | ||||||||||||||
|
|
||||||||||||||
| static const std::string noname; | ||||||||||||||
|
|
||||||||||||||
| // Register this check class (by creating a static instance of it) | ||||||||||||||
| namespace { | ||||||||||||||
| CheckUnionZeroInit instance; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| struct UnionMember { | ||||||||||||||
| UnionMember() | ||||||||||||||
| : name(noname) | ||||||||||||||
| , size(0) {} | ||||||||||||||
|
|
||||||||||||||
| UnionMember(const std::string &name_, size_t size_) | ||||||||||||||
| : name(name_) | ||||||||||||||
| , size(size_) {} | ||||||||||||||
|
|
||||||||||||||
| const std::string &name; | ||||||||||||||
| size_t size; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| struct Union { | ||||||||||||||
| Union(const Scope *scope_, const std::string &name_) | ||||||||||||||
| : scope(scope_) | ||||||||||||||
| , name(name_) {} | ||||||||||||||
|
|
||||||||||||||
| const Scope *scope; | ||||||||||||||
| const std::string &name; | ||||||||||||||
| std::vector<UnionMember> members; | ||||||||||||||
|
|
||||||||||||||
| const UnionMember *largestMember() const { | ||||||||||||||
| const UnionMember *largest = nullptr; | ||||||||||||||
| for (const UnionMember &m : members) { | ||||||||||||||
| if (m.size == 0) | ||||||||||||||
| return nullptr; | ||||||||||||||
| if (largest == nullptr || m.size > largest->size) | ||||||||||||||
| largest = &m; | ||||||||||||||
| } | ||||||||||||||
| return largest; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| bool isLargestMemberFirst() const { | ||||||||||||||
| const UnionMember *largest = largestMember(); | ||||||||||||||
| return largest == nullptr || largest == &members[0]; | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| static UnionMember parseUnionMember(const Variable &var, | ||||||||||||||
| const Settings &settings) | ||||||||||||||
| { | ||||||||||||||
| const Token *nameToken = var.nameToken(); | ||||||||||||||
| if (nameToken == nullptr) | ||||||||||||||
| return UnionMember(); | ||||||||||||||
|
|
||||||||||||||
| const ValueType *vt = nameToken->valueType(); | ||||||||||||||
| size_t size = 0; | ||||||||||||||
| if (var.isArray()) { | ||||||||||||||
| size = var.dimension(0); | ||||||||||||||
| } else if (vt != nullptr) { | ||||||||||||||
| size = ValueFlow::getSizeOf(*vt, settings, | ||||||||||||||
| ValueFlow::Accuracy::ExactOrZero); | ||||||||||||||
| } | ||||||||||||||
| return UnionMember(nameToken->str(), size); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| static std::vector<Union> parseUnions(const SymbolDatabase &symbolDatabase, | ||||||||||||||
| const Settings &settings) | ||||||||||||||
| { | ||||||||||||||
| std::vector<Union> unions; | ||||||||||||||
|
|
||||||||||||||
| for (const Scope &scope : symbolDatabase.scopeList) { | ||||||||||||||
| if (scope.type != ScopeType::eUnion) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| Union u(&scope, scope.className); | ||||||||||||||
| for (const Variable &var : scope.varlist) { | ||||||||||||||
| u.members.push_back(parseUnionMember(var, settings)); | ||||||||||||||
| } | ||||||||||||||
| unions.push_back(u); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return unions; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| static bool isZeroInitializer(const Token *tok) | ||||||||||||||
| { | ||||||||||||||
| return Token::simpleMatch(tok, "= { 0 } ;") || | ||||||||||||||
| Token::simpleMatch(tok, "= { } ;"); | ||||||||||||||
|
Comment on lines
+119
to
+120
Owner
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.
Suggested change
Contributor
Author
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. Does not work as intended...
Owner
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. oops I forgot a space
Suggested change
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| void CheckUnionZeroInit::check() | ||||||||||||||
| { | ||||||||||||||
| if (!mSettings->severity.isEnabled(Severity::portability)) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
| logChecker("CheckUnionZeroInit::check"); // portability | ||||||||||||||
|
|
||||||||||||||
| const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); | ||||||||||||||
|
|
||||||||||||||
| std::unordered_map<const Scope *, Union> unionsByScopeId; | ||||||||||||||
| const std::vector<Union> unions = parseUnions(*symbolDatabase, *mSettings); | ||||||||||||||
| for (const Union &u : unions) { | ||||||||||||||
| unionsByScopeId.insert({u.scope, u}); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { | ||||||||||||||
| if (!tok->isName() || !isZeroInitializer(tok->next())) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| const ValueType *vt = tok->valueType(); | ||||||||||||||
| if (vt == nullptr || vt->typeScope == nullptr) | ||||||||||||||
| continue; | ||||||||||||||
| auto it = unionsByScopeId.find(vt->typeScope); | ||||||||||||||
| if (it == unionsByScopeId.end()) | ||||||||||||||
| continue; | ||||||||||||||
| const Union &u = it->second; | ||||||||||||||
| if (!u.isLargestMemberFirst()) { | ||||||||||||||
| const UnionMember *largestMember = u.largestMember(); | ||||||||||||||
| assert(largestMember != nullptr); | ||||||||||||||
Check warningCode scanning / CppCheck The assert macro shall not be used with a constant-expression Warning
The assert macro shall not be used with a constant-expression
Owner
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.
This is a false positive. We will fix it in Cppcheck Premium. The expression is not a constant-expression. |
||||||||||||||
| unionZeroInitError(tok, *largestMember); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| void CheckUnionZeroInit::runChecks(const Tokenizer &tokenizer, | ||||||||||||||
| ErrorLogger *errorLogger) | ||||||||||||||
| { | ||||||||||||||
| CheckUnionZeroInit(&tokenizer, &tokenizer.getSettings(), errorLogger).check(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| void CheckUnionZeroInit::unionZeroInitError(const Token *tok, | ||||||||||||||
| const UnionMember& largestMember) | ||||||||||||||
| { | ||||||||||||||
| reportError(tok, Severity::portability, "UnionZeroInit", | ||||||||||||||
| "$symbol:" + (tok != nullptr ? tok->str() : "") + "\n" | ||||||||||||||
| "Zero initializing union '$symbol' does not guarantee " + | ||||||||||||||
| "its complete storage to be zero initialized as its largest member " + | ||||||||||||||
| "is not declared as the first member. Consider making " + | ||||||||||||||
| largestMember.name + " the first member or favor memset().", | ||||||||||||||
| CWEXXX, Certainty::normal); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| void CheckUnionZeroInit::getErrorMessages(ErrorLogger *errorLogger, | ||||||||||||||
| const Settings *settings) const | ||||||||||||||
| { | ||||||||||||||
| CheckUnionZeroInit c(nullptr, settings, errorLogger); | ||||||||||||||
| c.unionZeroInitError(nullptr, UnionMember()); | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| std::string CheckUnionZeroInit::generateTestMessage(const Tokenizer &tokenizer, | ||||||||||||||
| const Settings &settings) | ||||||||||||||
| { | ||||||||||||||
| std::stringstream ss; | ||||||||||||||
|
|
||||||||||||||
| const std::vector<Union> unions = parseUnions(*tokenizer.getSymbolDatabase(), | ||||||||||||||
| settings); | ||||||||||||||
| for (const Union &u : unions) { | ||||||||||||||
| ss << "Union{"; | ||||||||||||||
| ss << "name=\"" << u.name << "\", "; | ||||||||||||||
| ss << "scope=" << u.scope << ", "; | ||||||||||||||
| ss << "isLargestMemberFirst=" << u.isLargestMemberFirst(); | ||||||||||||||
| ss << "}" << std::endl; | ||||||||||||||
|
|
||||||||||||||
| const UnionMember *largest = u.largestMember(); | ||||||||||||||
| for (const UnionMember &m : u.members) { | ||||||||||||||
| ss << " Member{"; | ||||||||||||||
| ss << "name=\"" << m.name << "\", "; | ||||||||||||||
| ss << "size=" << m.size; | ||||||||||||||
| ss << "}"; | ||||||||||||||
| if (&m == largest) | ||||||||||||||
| ss << " (largest)"; | ||||||||||||||
| ss << std::endl; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return ss.str(); | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /* -*- C++ -*- | ||
| * Cppcheck - A tool for static C/C++ code analysis | ||
| * Copyright (C) 2007-2025 Cppcheck team. | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
|
|
||
| //--------------------------------------------------------------------------- | ||
| #ifndef checkunionzeroinitH | ||
| #define checkunionzeroinitH | ||
| //--------------------------------------------------------------------------- | ||
|
|
||
| #include "check.h" | ||
| #include "config.h" | ||
|
|
||
| #include <string> | ||
|
|
||
| class ErrorLogger; | ||
| class Settings; | ||
| class Token; | ||
| class Tokenizer; | ||
| struct UnionMember; | ||
|
|
||
| /// @addtogroup Checks | ||
| /// @{ | ||
|
|
||
| /** | ||
| * @brief Check for error-prone zero initialization of unions. | ||
| */ | ||
|
|
||
| class CPPCHECKLIB CheckUnionZeroInit : public Check { | ||
| friend class TestUnionZeroInit; | ||
|
|
||
| public: | ||
| /** This constructor is used when registering the CheckUnionZeroInit */ | ||
| CheckUnionZeroInit() : Check(myName()) {} | ||
|
|
||
| private: | ||
| /** This constructor is used when running checks. */ | ||
| CheckUnionZeroInit(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) | ||
| : Check(myName(), tokenizer, settings, errorLogger) {} | ||
|
|
||
| /** @brief Run checks against the normal token list */ | ||
| void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; | ||
|
|
||
| /** Check for error-prone zero initialization of unions. */ | ||
| void check(); | ||
|
|
||
| void unionZeroInitError(const Token *tok, const UnionMember &largestMember); | ||
|
|
||
| void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; | ||
|
|
||
| static std::string generateTestMessage(const Tokenizer &tokenizer, const Settings &settings); | ||
|
|
||
| static std::string myName() { | ||
| return "CheckUnionZeroInit"; | ||
| } | ||
|
|
||
| std::string classInfo() const override { | ||
| return "Check for error-prone zero initialization of unions.\n"; | ||
| } | ||
| }; | ||
| /// @} | ||
| //--------------------------------------------------------------------------- | ||
| #endif // checkunionzeroinitH |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10043,7 +10043,7 @@ void Tokenizer::simplifyBitfields() | |
| !Token::Match(tok->next(), "case|public|protected|private|class|struct") && | ||
| !Token::simpleMatch(tok->tokAt(2), "default :")) { | ||
| Token *tok1 = typeTok->next(); | ||
| if (Token::Match(tok1, "%name% : %num% [;=]")) | ||
| if (Token::Match(tok1, "%name% : %num% [;=,]")) | ||
|
Owner
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. good.. but I am not sure about doing this in the same PR. separate ticket+pr+testcase (in testtokenizer.cpp) would be ideal..
Contributor
Author
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. See #7865. |
||
| if (!tok1->setBits(MathLib::toBigNumber(tok1->tokAt(2)))) | ||
| tooLargeError(tok1->tokAt(2)); | ||
| if (tok1 && tok1->tokAt(2) && | ||
|
|
||
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.
this does not seem correct but anyway it is auto generated so will be fixed later..