From 70bf6dbe9473302d9d14d4e0733056c55865ff72 Mon Sep 17 00:00:00 2001 From: Anton Lindqvist Date: Mon, 22 Sep 2025 07:14:00 +0200 Subject: [PATCH 1/3] addons: add unionzeroinit (#14150) 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 --- addons/test/unionzeroinit.py | 87 ++++++++++ addons/test/unionzeroinit/array.c | 3 + addons/test/unionzeroinit/basic.c | 26 +++ addons/test/unionzeroinit/bitfields.c | 15 ++ addons/test/unionzeroinit/long-long.c | 3 + addons/test/unionzeroinit/struct-cyclic.c | 4 + addons/test/unionzeroinit/struct.c | 11 ++ addons/test/unionzeroinit/unknown-type.c | 3 + addons/unionzeroinit.py | 185 ++++++++++++++++++++++ 9 files changed, 337 insertions(+) create mode 100644 addons/test/unionzeroinit.py create mode 100644 addons/test/unionzeroinit/array.c create mode 100644 addons/test/unionzeroinit/basic.c create mode 100644 addons/test/unionzeroinit/bitfields.c create mode 100644 addons/test/unionzeroinit/long-long.c create mode 100644 addons/test/unionzeroinit/struct-cyclic.c create mode 100644 addons/test/unionzeroinit/struct.c create mode 100644 addons/test/unionzeroinit/unknown-type.c create mode 100644 addons/unionzeroinit.py diff --git a/addons/test/unionzeroinit.py b/addons/test/unionzeroinit.py new file mode 100644 index 00000000000..bd19c41c635 --- /dev/null +++ b/addons/test/unionzeroinit.py @@ -0,0 +1,87 @@ +# Running the test with Python 3: +# Command in cppcheck directory: +# PYTHONPATH=./addons python3 -m pytest addons/test/unionzeroinit.py + +import contextlib +import sys + +from addons import cppcheckdata +from addons import unionzeroinit +from . import util + + +@contextlib.contextmanager +def run(path): + sys.argv.append("--cli") + util.dump_create(path) + data = cppcheckdata.CppcheckData(f"{path}.dump") + for cfg in data.iterconfigurations(): + yield cfg, data + sys.argv.remove("--cli") + util.dump_remove(path) + + +def test_basic(capsys): + with run("./addons/test/unionzeroinit/basic.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output["unionzeroinit"]) == 3 + assert json_output["unionzeroinit"][0]["linenr"] == 22 + assert json_output["unionzeroinit"][1]["linenr"] == 23 + assert json_output["unionzeroinit"][2]["linenr"] == 25 + + +def test_array_member(capsys): + with run("./addons/test/unionzeroinit/array.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 + + +def test_struct_member(capsys): + with run("./addons/test/unionzeroinit/struct.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data, True) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 + + +def test_struct_cyclic_member(capsys): + with run("./addons/test/unionzeroinit/struct-cyclic.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data, True) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 + + +def test_unknown_type(capsys): + with run("./addons/test/unionzeroinit/unknown-type.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data, True) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 + + +def test_long_long(capsys): + with run("./addons/test/unionzeroinit/long-long.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data, True) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 + + +def test_bitfields(capsys): + with run("./addons/test/unionzeroinit/bitfields.c") as (cfg, data): + unionzeroinit.union_zero_init(cfg, data, True) + captured = capsys.readouterr() + captured = captured.out.splitlines() + json_output = util.convert_json_output(captured) + assert len(json_output) == 0 diff --git a/addons/test/unionzeroinit/array.c b/addons/test/unionzeroinit/array.c new file mode 100644 index 00000000000..5cfa31b2a4e --- /dev/null +++ b/addons/test/unionzeroinit/array.c @@ -0,0 +1,3 @@ +void foo(void) { + union { int c; char s8[2]; } u = {0}; +} diff --git a/addons/test/unionzeroinit/basic.c b/addons/test/unionzeroinit/basic.c new file mode 100644 index 00000000000..78a7f356762 --- /dev/null +++ b/addons/test/unionzeroinit/basic.c @@ -0,0 +1,26 @@ +#include + +union bad_union_0 { + char c; + int64_t i64; + void *p; +}; + +typedef union { + char c; + int i; +} bad_union_1; + +extern void e(union bad_union_0 *); + +void +foo(void) +{ + union { int i; char c; } good0 = {0}; + union { int i; char c; } good1 = {}; + + union { char c; int i; } bad0 = {0}; + union bad_union_0 bad1 = {0}; + e(&bad1); + bad_union_1 bad2 = {0}; +} diff --git a/addons/test/unionzeroinit/bitfields.c b/addons/test/unionzeroinit/bitfields.c new file mode 100644 index 00000000000..42ac366b1e4 --- /dev/null +++ b/addons/test/unionzeroinit/bitfields.c @@ -0,0 +1,15 @@ +typedef union Evex { + int u32; + struct { + char mmm:3, + b4:1, + r4:1, + b3:1, + x3:1, + r3:1; + } extended; +} Evex; + +void foo(void) { + Evex evex = {0}; +} diff --git a/addons/test/unionzeroinit/long-long.c b/addons/test/unionzeroinit/long-long.c new file mode 100644 index 00000000000..f35190bb983 --- /dev/null +++ b/addons/test/unionzeroinit/long-long.c @@ -0,0 +1,3 @@ +union u { + long long x; +}; diff --git a/addons/test/unionzeroinit/struct-cyclic.c b/addons/test/unionzeroinit/struct-cyclic.c new file mode 100644 index 00000000000..c2aa7bfc531 --- /dev/null +++ b/addons/test/unionzeroinit/struct-cyclic.c @@ -0,0 +1,4 @@ +oid foo(void) { + struct s { struct s *s; } + union { struct s s; } u = {0}; +} diff --git a/addons/test/unionzeroinit/struct.c b/addons/test/unionzeroinit/struct.c new file mode 100644 index 00000000000..81744c66dad --- /dev/null +++ b/addons/test/unionzeroinit/struct.c @@ -0,0 +1,11 @@ +void foo(void) { + union { + int c; + struct { + char x; + struct { + char y; + } s1; + } s0; + } u = {0}; +} diff --git a/addons/test/unionzeroinit/unknown-type.c b/addons/test/unionzeroinit/unknown-type.c new file mode 100644 index 00000000000..0dae0a09332 --- /dev/null +++ b/addons/test/unionzeroinit/unknown-type.c @@ -0,0 +1,3 @@ +union u { + Unknown x; +}; diff --git a/addons/unionzeroinit.py b/addons/unionzeroinit.py new file mode 100644 index 00000000000..ff853446e08 --- /dev/null +++ b/addons/unionzeroinit.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# +# Detect error-prone zero initialization of unions. + +import dataclasses +import cppcheck +import cppcheckdata +from typing import List, Tuple + + +@dataclasses.dataclass +class Member: + name: str + size: int + + +@dataclasses.dataclass +class Union: + name: str + members: List[Member] = dataclasses.field(default_factory=list) + + def largest_member(self): + return sorted(self.members, key=lambda m: -m.size)[0] + + def is_largest_member_first(self): + sizes = [m.size for m in self.members] + + has_unknown_sizes = 0 in sizes + if has_unknown_sizes: + return True + + return sizes[0] == sorted(sizes, key=lambda s: -s)[0] + + +def estimate_size_of_type( + platform: cppcheckdata.Platform, type: str, pointer: bool +) -> int: + bits = 0 + if pointer: + bits = platform.pointer_bit + elif type == "char": + bits = platform.char_bit + elif type == "short": + bits = platform.short_bit + elif type == "int": + bits = platform.int_bit + elif type == "long": + bits = platform.long_bit + elif type == "long_long": + bits = platform.long_long_bit + else: + # Fair estimate... + bits = platform.int_bit + return bits + + +def tokat(token: cppcheckdata.Token, offset) -> cppcheckdata.Token: + at = token.tokAt(offset) + if at: + return at + + empty = {"str": ""} + return cppcheckdata.Token(empty) + + +def parse_array_length(token) -> int: + if not tokat(token, 1).str == "[": + return 1 + + nelements = 0 + try: + nelements = int(tokat(token, 2).str) + except ValueError: + return 1 + + if not tokat(token, 3).str == "]": + return 1 + + return nelements + + +def is_zero_initialized(token): + return ( + tokat(token, 1).str == "=" + and tokat(token, 2).str == "{" + and ( + tokat(token, 3).str == "}" + or (tokat(token, 3).str == "0" and tokat(token, 4).str == "}") + ) + ) + + +def is_pointer(variable: cppcheckdata.Variable) -> bool: + return variable.nameToken.valueType.pointer and not variable.isArray + + +def accumulated_member_size( + data: cppcheckdata.CppcheckData, variable: cppcheckdata.Variable +) -> Tuple[str, int]: + # Note that cppcheck might not be able to observe all types due to + # inaccessible include(s). + if not variable.nameToken.valueType: + return (None, 0) + + if variable.nameToken.valueType.type == "record": + if not variable.nameToken.valueType.typeScope: + return (None, 0) + + nested_variables = variable.nameToken.valueType.typeScope.varlist + + # Circumvent what seems to be a bug in which only the last bitfield has + # its bits properly assigned. + has_bitfields = any([v.nameToken.valueType.bits for v in nested_variables]) + if has_bitfields: + return (variable.nameToken.str, len(nested_variables)) + + total_size = 0 + for nested in nested_variables: + # Avoid potential cyclic members referring to the type currently + # being traversed. + if is_pointer(nested): + total_size += data.platform.pointer_bit + else: + _, size = accumulated_member_size(data, nested.nameToken.variable) + total_size += size + return (variable.nameToken.str, total_size) + + vt = variable.nameToken.valueType + if vt.bits: + size = vt.bits + else: + size = estimate_size_of_type( + data.platform, + variable.nameToken.valueType.type, + is_pointer(variable), + ) + if variable.isArray: + size *= parse_array_length(variable.nameToken) + return (variable.nameToken.str, size) + + +def error_message(u: Union): + return ( + f"Zero initializing union {u.name} does not guarantee its complete " + "storage to be zero initialized as its largest member is not declared " + f"as the first member. Consider making {u.largest_member().name} the " + "first member or favor memset()." + ) + + +@cppcheck.checker +def union_zero_init(cfg, data, debug=False): + unions_by_id = {} + + # Detect union declarations. + for scope in cfg.scopes: + if not scope.type == "Union": + continue + + union = Union(name=scope.className) + for variable in scope.varlist: + name, size = accumulated_member_size(data, variable) + union.members.append(Member(name=name, size=size)) + unions_by_id[scope.Id] = union + + if debug: + for id, u in unions_by_id.items(): + print(id, u, u.is_largest_member_first(), u.largest_member()) + + # Detect problematic union variables. + for token in cfg.tokenlist: + if ( + token.valueType + and token.valueType.typeScopeId in unions_by_id + and token.isName + and is_zero_initialized(token) + ): + id = token.valueType.typeScopeId + if not unions_by_id[id].is_largest_member_first(): + cppcheck.reportError( + token, + "portability", + error_message(unions_by_id[id]), + "unionzeroinit", + ) From 7344d5f1948bef6d28564546eabc952317e02be9 Mon Sep 17 00:00:00 2001 From: Anton Lindqvist Date: Mon, 29 Sep 2025 14:47:46 +0200 Subject: [PATCH 2/3] Bitfields may be followed by comma --- lib/tokenize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 9c6db66a4c6..df4fe6333e5 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -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% [;=,]")) if (!tok1->setBits(MathLib::toBigNumber(tok1->tokAt(2)))) tooLargeError(tok1->tokAt(2)); if (tok1 && tok1->tokAt(2) && From 937add6a48504ab1566f523f7619db7aafbb9a14 Mon Sep 17 00:00:00 2001 From: Anton Lindqvist Date: Sun, 28 Sep 2025 21:45:46 +0200 Subject: [PATCH 3/3] Migrate UnionZeroInit to cppcheck core --- .selfcheck_unused_suppressions | 3 + Makefile | 8 + addons/test/unionzeroinit.py | 87 --------- addons/test/unionzeroinit/array.c | 3 - addons/test/unionzeroinit/basic.c | 26 --- addons/test/unionzeroinit/bitfields.c | 15 -- addons/test/unionzeroinit/long-long.c | 3 - addons/test/unionzeroinit/struct-cyclic.c | 4 - addons/test/unionzeroinit/struct.c | 11 -- addons/test/unionzeroinit/unknown-type.c | 3 - addons/unionzeroinit.py | 185 ------------------- lib/checkers.cpp | 1 + lib/checkunionzeroinit.cpp | 209 ++++++++++++++++++++++ lib/checkunionzeroinit.h | 77 ++++++++ lib/cppcheck.vcxproj | 2 + oss-fuzz/Makefile | 4 + test/testrunner.vcxproj | 1 + test/testunionzeroinit.cpp | 149 +++++++++++++++ 18 files changed, 454 insertions(+), 337 deletions(-) delete mode 100644 addons/test/unionzeroinit.py delete mode 100644 addons/test/unionzeroinit/array.c delete mode 100644 addons/test/unionzeroinit/basic.c delete mode 100644 addons/test/unionzeroinit/bitfields.c delete mode 100644 addons/test/unionzeroinit/long-long.c delete mode 100644 addons/test/unionzeroinit/struct-cyclic.c delete mode 100644 addons/test/unionzeroinit/struct.c delete mode 100644 addons/test/unionzeroinit/unknown-type.c delete mode 100644 addons/unionzeroinit.py create mode 100644 lib/checkunionzeroinit.cpp create mode 100644 lib/checkunionzeroinit.h create mode 100644 test/testunionzeroinit.cpp diff --git a/.selfcheck_unused_suppressions b/.selfcheck_unused_suppressions index ce685d8ab10..66a3c3a57c1 100644 --- a/.selfcheck_unused_suppressions +++ b/.selfcheck_unused_suppressions @@ -6,3 +6,6 @@ unusedFunction:lib/symboldatabase.cpp # Q_OBJECT functions which are not called in our code unusedFunction:cmake.output.notest/gui/cppcheck-gui_autogen/*/moc_aboutdialog.cpp + +# CheckUnionZeroInit::generateTestMessage only used in tests. +unusedFunction:lib/checkunionzeroinit.cpp diff --git a/Makefile b/Makefile index 8dda00a2096..a22405b8da1 100644 --- a/Makefile +++ b/Makefile @@ -224,6 +224,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checkstring.o \ $(libcppdir)/checktype.o \ $(libcppdir)/checkuninitvar.o \ + $(libcppdir)/checkunionzeroinit.o \ $(libcppdir)/checkunusedfunctions.o \ $(libcppdir)/checkunusedvar.o \ $(libcppdir)/checkvaarg.o \ @@ -346,6 +347,7 @@ TESTOBJ = test/fixture.o \ test/testtokenrange.o \ test/testtype.o \ test/testuninitvar.o \ + test/testunionzeroinit.o \ test/testunusedfunctions.o \ test/testunusedprivfunc.o \ test/testunusedvar.o \ @@ -561,6 +563,9 @@ $(libcppdir)/checktype.o: lib/checktype.cpp lib/addoninfo.h lib/astutils.h lib/c $(libcppdir)/checkuninitvar.o: lib/checkuninitvar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checknullpointer.h lib/checkuninitvar.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkuninitvar.cpp +$(libcppdir)/checkunionzeroinit.o: lib/checkunionzeroinit.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkunionzeroinit.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h + $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunionzeroinit.cpp + $(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/astutils.h lib/checkers.h lib/checkunusedfunctions.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp @@ -909,6 +914,9 @@ test/testtype.o: test/testtype.cpp lib/addoninfo.h lib/check.h lib/checkers.h li test/testuninitvar.o: test/testuninitvar.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testuninitvar.cpp +test/testunionzeroinit.o: test/testunionzeroinit.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkunionzeroinit.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h + $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testunionzeroinit.cpp + test/testunusedfunctions.o: test/testunusedfunctions.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testunusedfunctions.cpp diff --git a/addons/test/unionzeroinit.py b/addons/test/unionzeroinit.py deleted file mode 100644 index bd19c41c635..00000000000 --- a/addons/test/unionzeroinit.py +++ /dev/null @@ -1,87 +0,0 @@ -# Running the test with Python 3: -# Command in cppcheck directory: -# PYTHONPATH=./addons python3 -m pytest addons/test/unionzeroinit.py - -import contextlib -import sys - -from addons import cppcheckdata -from addons import unionzeroinit -from . import util - - -@contextlib.contextmanager -def run(path): - sys.argv.append("--cli") - util.dump_create(path) - data = cppcheckdata.CppcheckData(f"{path}.dump") - for cfg in data.iterconfigurations(): - yield cfg, data - sys.argv.remove("--cli") - util.dump_remove(path) - - -def test_basic(capsys): - with run("./addons/test/unionzeroinit/basic.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output["unionzeroinit"]) == 3 - assert json_output["unionzeroinit"][0]["linenr"] == 22 - assert json_output["unionzeroinit"][1]["linenr"] == 23 - assert json_output["unionzeroinit"][2]["linenr"] == 25 - - -def test_array_member(capsys): - with run("./addons/test/unionzeroinit/array.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 - - -def test_struct_member(capsys): - with run("./addons/test/unionzeroinit/struct.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data, True) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 - - -def test_struct_cyclic_member(capsys): - with run("./addons/test/unionzeroinit/struct-cyclic.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data, True) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 - - -def test_unknown_type(capsys): - with run("./addons/test/unionzeroinit/unknown-type.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data, True) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 - - -def test_long_long(capsys): - with run("./addons/test/unionzeroinit/long-long.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data, True) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 - - -def test_bitfields(capsys): - with run("./addons/test/unionzeroinit/bitfields.c") as (cfg, data): - unionzeroinit.union_zero_init(cfg, data, True) - captured = capsys.readouterr() - captured = captured.out.splitlines() - json_output = util.convert_json_output(captured) - assert len(json_output) == 0 diff --git a/addons/test/unionzeroinit/array.c b/addons/test/unionzeroinit/array.c deleted file mode 100644 index 5cfa31b2a4e..00000000000 --- a/addons/test/unionzeroinit/array.c +++ /dev/null @@ -1,3 +0,0 @@ -void foo(void) { - union { int c; char s8[2]; } u = {0}; -} diff --git a/addons/test/unionzeroinit/basic.c b/addons/test/unionzeroinit/basic.c deleted file mode 100644 index 78a7f356762..00000000000 --- a/addons/test/unionzeroinit/basic.c +++ /dev/null @@ -1,26 +0,0 @@ -#include - -union bad_union_0 { - char c; - int64_t i64; - void *p; -}; - -typedef union { - char c; - int i; -} bad_union_1; - -extern void e(union bad_union_0 *); - -void -foo(void) -{ - union { int i; char c; } good0 = {0}; - union { int i; char c; } good1 = {}; - - union { char c; int i; } bad0 = {0}; - union bad_union_0 bad1 = {0}; - e(&bad1); - bad_union_1 bad2 = {0}; -} diff --git a/addons/test/unionzeroinit/bitfields.c b/addons/test/unionzeroinit/bitfields.c deleted file mode 100644 index 42ac366b1e4..00000000000 --- a/addons/test/unionzeroinit/bitfields.c +++ /dev/null @@ -1,15 +0,0 @@ -typedef union Evex { - int u32; - struct { - char mmm:3, - b4:1, - r4:1, - b3:1, - x3:1, - r3:1; - } extended; -} Evex; - -void foo(void) { - Evex evex = {0}; -} diff --git a/addons/test/unionzeroinit/long-long.c b/addons/test/unionzeroinit/long-long.c deleted file mode 100644 index f35190bb983..00000000000 --- a/addons/test/unionzeroinit/long-long.c +++ /dev/null @@ -1,3 +0,0 @@ -union u { - long long x; -}; diff --git a/addons/test/unionzeroinit/struct-cyclic.c b/addons/test/unionzeroinit/struct-cyclic.c deleted file mode 100644 index c2aa7bfc531..00000000000 --- a/addons/test/unionzeroinit/struct-cyclic.c +++ /dev/null @@ -1,4 +0,0 @@ -oid foo(void) { - struct s { struct s *s; } - union { struct s s; } u = {0}; -} diff --git a/addons/test/unionzeroinit/struct.c b/addons/test/unionzeroinit/struct.c deleted file mode 100644 index 81744c66dad..00000000000 --- a/addons/test/unionzeroinit/struct.c +++ /dev/null @@ -1,11 +0,0 @@ -void foo(void) { - union { - int c; - struct { - char x; - struct { - char y; - } s1; - } s0; - } u = {0}; -} diff --git a/addons/test/unionzeroinit/unknown-type.c b/addons/test/unionzeroinit/unknown-type.c deleted file mode 100644 index 0dae0a09332..00000000000 --- a/addons/test/unionzeroinit/unknown-type.c +++ /dev/null @@ -1,3 +0,0 @@ -union u { - Unknown x; -}; diff --git a/addons/unionzeroinit.py b/addons/unionzeroinit.py deleted file mode 100644 index ff853446e08..00000000000 --- a/addons/unionzeroinit.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# -# Detect error-prone zero initialization of unions. - -import dataclasses -import cppcheck -import cppcheckdata -from typing import List, Tuple - - -@dataclasses.dataclass -class Member: - name: str - size: int - - -@dataclasses.dataclass -class Union: - name: str - members: List[Member] = dataclasses.field(default_factory=list) - - def largest_member(self): - return sorted(self.members, key=lambda m: -m.size)[0] - - def is_largest_member_first(self): - sizes = [m.size for m in self.members] - - has_unknown_sizes = 0 in sizes - if has_unknown_sizes: - return True - - return sizes[0] == sorted(sizes, key=lambda s: -s)[0] - - -def estimate_size_of_type( - platform: cppcheckdata.Platform, type: str, pointer: bool -) -> int: - bits = 0 - if pointer: - bits = platform.pointer_bit - elif type == "char": - bits = platform.char_bit - elif type == "short": - bits = platform.short_bit - elif type == "int": - bits = platform.int_bit - elif type == "long": - bits = platform.long_bit - elif type == "long_long": - bits = platform.long_long_bit - else: - # Fair estimate... - bits = platform.int_bit - return bits - - -def tokat(token: cppcheckdata.Token, offset) -> cppcheckdata.Token: - at = token.tokAt(offset) - if at: - return at - - empty = {"str": ""} - return cppcheckdata.Token(empty) - - -def parse_array_length(token) -> int: - if not tokat(token, 1).str == "[": - return 1 - - nelements = 0 - try: - nelements = int(tokat(token, 2).str) - except ValueError: - return 1 - - if not tokat(token, 3).str == "]": - return 1 - - return nelements - - -def is_zero_initialized(token): - return ( - tokat(token, 1).str == "=" - and tokat(token, 2).str == "{" - and ( - tokat(token, 3).str == "}" - or (tokat(token, 3).str == "0" and tokat(token, 4).str == "}") - ) - ) - - -def is_pointer(variable: cppcheckdata.Variable) -> bool: - return variable.nameToken.valueType.pointer and not variable.isArray - - -def accumulated_member_size( - data: cppcheckdata.CppcheckData, variable: cppcheckdata.Variable -) -> Tuple[str, int]: - # Note that cppcheck might not be able to observe all types due to - # inaccessible include(s). - if not variable.nameToken.valueType: - return (None, 0) - - if variable.nameToken.valueType.type == "record": - if not variable.nameToken.valueType.typeScope: - return (None, 0) - - nested_variables = variable.nameToken.valueType.typeScope.varlist - - # Circumvent what seems to be a bug in which only the last bitfield has - # its bits properly assigned. - has_bitfields = any([v.nameToken.valueType.bits for v in nested_variables]) - if has_bitfields: - return (variable.nameToken.str, len(nested_variables)) - - total_size = 0 - for nested in nested_variables: - # Avoid potential cyclic members referring to the type currently - # being traversed. - if is_pointer(nested): - total_size += data.platform.pointer_bit - else: - _, size = accumulated_member_size(data, nested.nameToken.variable) - total_size += size - return (variable.nameToken.str, total_size) - - vt = variable.nameToken.valueType - if vt.bits: - size = vt.bits - else: - size = estimate_size_of_type( - data.platform, - variable.nameToken.valueType.type, - is_pointer(variable), - ) - if variable.isArray: - size *= parse_array_length(variable.nameToken) - return (variable.nameToken.str, size) - - -def error_message(u: Union): - return ( - f"Zero initializing union {u.name} does not guarantee its complete " - "storage to be zero initialized as its largest member is not declared " - f"as the first member. Consider making {u.largest_member().name} the " - "first member or favor memset()." - ) - - -@cppcheck.checker -def union_zero_init(cfg, data, debug=False): - unions_by_id = {} - - # Detect union declarations. - for scope in cfg.scopes: - if not scope.type == "Union": - continue - - union = Union(name=scope.className) - for variable in scope.varlist: - name, size = accumulated_member_size(data, variable) - union.members.append(Member(name=name, size=size)) - unions_by_id[scope.Id] = union - - if debug: - for id, u in unions_by_id.items(): - print(id, u, u.is_largest_member_first(), u.largest_member()) - - # Detect problematic union variables. - for token in cfg.tokenlist: - if ( - token.valueType - and token.valueType.typeScopeId in unions_by_id - and token.isName - and is_zero_initialized(token) - ): - id = token.valueType.typeScopeId - if not unions_by_id[id].is_largest_member_first(): - cppcheck.reportError( - token, - "portability", - error_message(unions_by_id[id]), - "unionzeroinit", - ) diff --git a/lib/checkers.cpp b/lib/checkers.cpp index a5a47b867c2..24a68b23664 100644 --- a/lib/checkers.cpp +++ b/lib/checkers.cpp @@ -200,6 +200,7 @@ namespace checkers { {"CheckType::checkLongCast","style"}, {"CheckType::checkSignConversion","warning"}, {"CheckType::checkTooBigBitwiseShift","platform"}, + {"CheckUninitVar::check",""}, {"CheckUninitVar::analyseWholeProgram",""}, {"CheckUninitVar::check",""}, {"CheckUninitVar::valueFlowUninit",""}, diff --git a/lib/checkunionzeroinit.cpp b/lib/checkunionzeroinit.cpp new file mode 100644 index 00000000000..2c1863129d3 --- /dev/null +++ b/lib/checkunionzeroinit.cpp @@ -0,0 +1,209 @@ +/* + * 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 . + */ + +#include "checkunionzeroinit.h" + +#include +#include +#include + +#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 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 parseUnions(const SymbolDatabase &symbolDatabase, + const Settings &settings) +{ + std::vector 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, "= { } ;"); +} + +void CheckUnionZeroInit::check() +{ + if (!mSettings->severity.isEnabled(Severity::portability)) + return; + + logChecker("CheckUnionZeroInit::check"); // portability + + const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); + + std::unordered_map unionsByScopeId; + const std::vector 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); + 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 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(); +} diff --git a/lib/checkunionzeroinit.h b/lib/checkunionzeroinit.h new file mode 100644 index 00000000000..3b1dd869905 --- /dev/null +++ b/lib/checkunionzeroinit.h @@ -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 . + */ + + +//--------------------------------------------------------------------------- +#ifndef checkunionzeroinitH +#define checkunionzeroinitH +//--------------------------------------------------------------------------- + +#include "check.h" +#include "config.h" + +#include + +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 diff --git a/lib/cppcheck.vcxproj b/lib/cppcheck.vcxproj index 3d830e2894c..f2594a26ca4 100644 --- a/lib/cppcheck.vcxproj +++ b/lib/cppcheck.vcxproj @@ -56,6 +56,7 @@ + @@ -127,6 +128,7 @@ + diff --git a/oss-fuzz/Makefile b/oss-fuzz/Makefile index 5d4658790aa..8d1ea962754 100644 --- a/oss-fuzz/Makefile +++ b/oss-fuzz/Makefile @@ -70,6 +70,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checkstring.o \ $(libcppdir)/checktype.o \ $(libcppdir)/checkuninitvar.o \ + $(libcppdir)/checkunionzeroinit.o \ $(libcppdir)/checkunusedfunctions.o \ $(libcppdir)/checkunusedvar.o \ $(libcppdir)/checkvaarg.o \ @@ -243,6 +244,9 @@ $(libcppdir)/checktype.o: ../lib/checktype.cpp ../lib/addoninfo.h ../lib/astutil $(libcppdir)/checkuninitvar.o: ../lib/checkuninitvar.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checknullpointer.h ../lib/checkuninitvar.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkuninitvar.cpp +$(libcppdir)/checkunionzeroinit.o: ../lib/checkunionzeroinit.cpp ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/checkunionzeroinit.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h + $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunionzeroinit.cpp + $(libcppdir)/checkunusedfunctions.o: ../lib/checkunusedfunctions.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/checkunusedfunctions.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 8e1ce01f07e..997644dd6a0 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -107,6 +107,7 @@ + diff --git a/test/testunionzeroinit.cpp b/test/testunionzeroinit.cpp new file mode 100644 index 00000000000..d41a5edeee8 --- /dev/null +++ b/test/testunionzeroinit.cpp @@ -0,0 +1,149 @@ +/* + * 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 . + */ + +#include "checkunionzeroinit.h" +#include "errortypes.h" +#include "fixture.h" +#include "helpers.h" +#include "settings.h" + +static std::string expectedErrorMessage(int lno, int cno, const std::string &varName, const std::string &largestMemberName) +{ + std::stringstream ss; + ss << "[test.cpp:" << lno << ":" << cno << "]: (portability) "; + ss << "Zero initializing union '" << varName << "' "; + ss << "does not guarantee its complete storage to be zero initialized as its largest member is not declared as the first member. "; + ss << "Consider making " << largestMemberName << " the first member or favor memset(). [UnionZeroInit]"; + ss << std::endl; + return ss.str(); +} + +class TestUnionZeroInit : public TestFixture { +public: + TestUnionZeroInit() : TestFixture("TestUnionZeroInit") {} + +private: + const Settings mSettings = settingsBuilder().severity(Severity::portability).library("std.cfg").build(); + std::string mMessage; + + void run() override { + mNewTemplate = true; + TEST_CASE(basic); + TEST_CASE(arrayMember); + TEST_CASE(structMember); + TEST_CASE(unknownType); + TEST_CASE(bitfields); + } + +#define checkUnionZeroInit(...) checkUnionZeroInit_(__FILE__, __LINE__, __VA_ARGS__) + template + void checkUnionZeroInit_(const char* file, int line, const char (&code)[size], bool cpp = true) { + // Tokenize.. + SimpleTokenizer tokenizer(mSettings, *this, cpp); + ASSERT_LOC(tokenizer.tokenize(code), file, line); + + CheckUnionZeroInit(&tokenizer, &mSettings, this).check(); + + mMessage = CheckUnionZeroInit::generateTestMessage(tokenizer, mSettings); + } + + void basic() { + checkUnionZeroInit( + "union bad_union_0 {\n" + " char c;\n" + " long long i64;\n" + " void *p;\n" + "};\n" + "\n" + "typedef union {\n" + " char c;\n" + " int i;\n" + "} bad_union_1;\n" + "\n" + "extern void e(union bad_union_0 *);\n" + "\n" + "void\n" + "foo(void)\n" + "{\n" + " union { int i; char c; } good0 = {0};\n" + " union { int i; char c; } good1 = {};\n" + "\n" + " union { char c; int i; } bad0 = {0};\n" + " union bad_union_0 bad1 = {0};\n" + " e(&bad1);\n" + " bad_union_1 bad2 = {0};\n" + "}"); + const std::string exp = expectedErrorMessage(20, 28, "bad0", "i") + + expectedErrorMessage(21, 21, "bad1", "i64") + + expectedErrorMessage(23, 15, "bad2", "i"); + ASSERT_EQUALS_MSG(exp, errout_str(), mMessage); + } + + void arrayMember() { + checkUnionZeroInit( + "void foo(void) {\n" + " union { int c; char s8[2]; } u = {0};\n" + "}"); + ASSERT_EQUALS_MSG("", errout_str(), mMessage); + } + + void structMember() { + checkUnionZeroInit( + "void foo(void) {\n" + " union {\n" + " int c;\n" + " struct {\n" + " char x;\n" + " struct {\n" + " char y;\n" + " } s1;\n" + " } s0;\n" + " } u = {0};\n" + "}"); + ASSERT_EQUALS_MSG("", errout_str(), mMessage); + } + + void unknownType() { + checkUnionZeroInit( + "union u {\n" + " Unknown x;\n" + "}"); + ASSERT_EQUALS_MSG("", errout_str(), mMessage); + } + + void bitfields() { + checkUnionZeroInit( + "typedef union Evex {\n" + " int u32;\n" + " struct {\n" + " char mmm:3,\n" + " b4:1,\n" + " r4:1,\n" + " b3:1,\n" + " x3:1,\n" + " r3:1;\n" + " } extended;\n" + "} Evex;\n" + "\n" + "void foo(void) {\n" + " Evex evex = {0};\n" + "}"); + ASSERT_EQUALS_MSG("", errout_str(), mMessage); + } +}; +REGISTER_TEST(TestUnionZeroInit)