-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Conversation
d730e9b
to
99c5739
Compare
nice work. I would like that we add a ticket in trac and point to that in the PR title. I am spontanously thinking that this could possibly be a internal core checker instead (portability). Are there some situations when you think users will consider these warnings to be false positives, assuming they want to fix portability issues? |
This has bitten me before and is definitely a foot gun. GCC 15[1] changed their semantics related to zero initialization of unions; from initializing the complete union (sizeof union) to only zero initializing the first member. If the same first member is not the largest one, the state of the remaining storage is considered undefined and in practice most likely stack garbage. The unionzeroinit addon can detect such scenarios and emit a warning. It does not cover the designated initializers as I would interpret those as being intentional. Example output from one of my projects: ``` x86-decoder.c:294:7: warning: Zero initializing union Evex does not guarantee its complete storage to be zero initialized as its largest member is not declared as the first member. Consider making u32 the first member or favor memset(). [unionzeroinit-unionzeroinit] Evex evex = {0}; ^ ``` [1] https://trofi.github.io/posts/328-c-union-init-and-gcc-15.html
99c5739
to
70bf6db
Compare
Done
Agreed, this more of a proof-of-concept that turned out well IMO.
Say you have I changed the severity from warning to portability. |
addons/unionzeroinit.py
Outdated
|
||
nested_variables = variable.nameToken.valueType.typeScope.varlist | ||
|
||
# Circumvent what seems to be a bug in which only the last bitfield has |
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.
@danmar this smells like a bug. Reproducable using one of the added test cases.
cppcheck --quiet --dump addons/test/unionzeroinit/bitfields.c
Note that only the r3
field has valueType-bits="1"
whereas the remaining bitfields has the same attribute set to zero.
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.
Note that only the r3 field has valueType-bits="1" whereas the remaining bitfields has the same attribute set to zero.
if that is true then yes it is a bug!
would you like to write a ticket about it or should I?
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.
No need, fixed in the following commit (as part of this PR).
yes true but then it would be pretty redundant to use a union I guess.. I have a good feeling about this checker and would like to move this into core cppcheck instead.. could you look into that? that would be used by a lot more cppcheck users. :-) |
On Sun, Sep 28, 2025 at 07:59:23AM -0700, Daniel Marjamäki wrote:
danmar left a comment (danmar/cppcheck#7843)
> Say you have union { uint8_t u8; uint64_t u64 } u = {0} and only operate on u8 would be a false positive.
yes true but then it would be pretty redundant to use a union I guess.. I have a good feeling about this checker and would like to move this into core cppcheck instead.. could you look into that? that would be used by a lot more cppcheck users. :-)
Sure, I will take a stab at this.
|
const Union &u = it->second; | ||
if (!u.isLargestMemberFirst()) { | ||
const UnionMember *largestMember = u.largestMember(); | ||
assert(largestMember != nullptr); |
Check warning
Code scanning / CppCheck
The assert macro shall not be used with a constant-expression Warning
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.
The assert macro shall not be used with a constant-expression
This is a false positive. We will fix it in Cppcheck Premium. The expression is not a constant-expression.
4ff2ed0
to
51d7983
Compare
@danmar migrated to cppcheck core. Let me know what you think. |
51d7983
to
afc3bcd
Compare
!Token::simpleMatch(tok->tokAt(2), "default :")) { | ||
Token *tok1 = typeTok->next(); | ||
if (Token::Match(tok1, "%name% : %num% [;=]")) | ||
if (Token::Match(tok1, "%name% : %num% [;=,]")) |
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.
good.. but I am not sure about doing this in the same PR.
separate ticket+pr+testcase (in testtokenizer.cpp) would be ideal..
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.
See #7865.
{"CheckType::checkLongCast","style"}, | ||
{"CheckType::checkSignConversion","warning"}, | ||
{"CheckType::checkTooBigBitwiseShift","platform"}, | ||
{"CheckUninitVar::check",""}, |
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..
@@ -0,0 +1,209 @@ | |||
/* |
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.
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.
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 don't find that argument compelling and rather keep the logic separated.
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.
we have more than 100 checkers and we have always grouped checkers into classes we did not write each checker in its own class.
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 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..
return Token::simpleMatch(tok, "= { 0 } ;") || | ||
Token::simpleMatch(tok, "= { } ;"); |
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.
return Token::simpleMatch(tok, "= { 0 } ;") || | |
Token::simpleMatch(tok, "= { } ;"); | |
return Token::Match(tok, "= { 0| };"); |
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.
Does not work as intended...
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.
oops I forgot a space
return Token::simpleMatch(tok, "= { 0 } ;") || | |
Token::simpleMatch(tok, "= { } ;"); | |
return Token::Match(tok, "= { 0| } ;"); |
const Union &u = it->second; | ||
if (!u.isLargestMemberFirst()) { | ||
const UnionMember *largestMember = u.largestMember(); | ||
assert(largestMember != nullptr); |
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.
The assert macro shall not be used with a constant-expression
This is a false positive. We will fix it in Cppcheck Premium. The expression is not a constant-expression.
|
oh shit I merged by mistake :-( I revert it with #7868 |
This has bitten me before and is definitely a foot gun. GCC 15[1] changed their semantics related to zero initialization of unions; from initializing the complete union (sizeof union) to only zero initializing the first member. If the same first member is not the largest one, the state of the remaining storage is considered undefined and in practice most likely stack garbage.
The unionzeroinit addon can detect such scenarios and emit a warning. It does not cover the designated initializers as I would interpret those as being intentional.
Example output from one of my projects:
[1] https://trofi.github.io/posts/328-c-union-init-and-gcc-15.html