Skip to content

Commit 8206334

Browse files
chaosapetautschnigkiro-agent
committed
ansi-c: support -fms-extensions anonymous tagged struct/union members
A *tagged* struct/union used as an unnamed (anonymous) member -- e.g. the Linux kernel's `struct __filename_head;` embedded in struct filename -- was silently dropped by the C front-end in GCC/Clang mode: it contributed no bytes (so sizeof was wrong, breaking the kernel's static_assert(sizeof(struct filename) % 64 == 0)) and its members were not injected (inaccessible by name). GCC/Clang accept this only with -fms-extensions (the Plan 9 C extension); without it they ignore the member, matching the existing regression/ansi-c/anonymous_member* tests. So: - config: add ansi_c.allow_anonymous_struct_embedding (default false), named to be explicit that only this one MS/Plan 9 extension is implemented, not all of -fms-extensions; - gcc_mode: set it when -fms-extensions is passed (queried as "fms-extensions" -- goto_cc_cmdlinet stores long options without the leading '-'); - c_typecheck_type: in GCC/Clang mode accept an *untagged* struct/union anonymous member always (C11) and a *tagged* one only under -fms-extensions; a non-struct/union unnamed member, or a tagged one without the flag, keeps the previous "ignore" behaviour. With the flag the member now contributes its size and its fields are injected (reachable by name). The new behaviour is documented in the goto-cc section of the CPROVER manual, and exercised from both sides: goto-gcc/ms_extensions_anonymous_member checks exact layout and member access with the flag, and goto-gcc/ms_extensions_anonymous_member_ignored checks the member is ignored without it. The existing anonymous_member* tests confirm the default cbmc behaviour is preserved. Co-authored-by: Michael Tautschnig <tautschn@amazon.com> Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 7483d0d commit 8206334

9 files changed

Lines changed: 149 additions & 24 deletions

File tree

doc/cprover-manual/goto-cc.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ for i in `find . -name Makefile`; do
9696
done
9797
```
9898

99+
goto-cc honours the `-fms-extensions` flag: when given, it accepts an
100+
anonymous member that is a *tagged* struct or union (a Microsoft/GCC
101+
extension used, for instance, throughout the Linux kernel), injecting that
102+
member's fields into the enclosing struct and accounting for its size.
103+
Without the flag such a member is ignored, matching the default gcc and
104+
Clang behaviour.
105+
99106
Here are additional examples on how to use goto-cc:
100107

101108
- \ref man_goto-cc-linux "Linux Kernel"
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// -fms-extensions: a *tagged* struct/union used as an unnamed (anonymous)
2+
// member. Its members are injected into the enclosing struct and it
3+
// contributes its size. Used pervasively in the Linux kernel (e.g.
4+
// struct __filename_head embedded in struct filename, whose static_asserts
5+
// require the size to be exact). The _Static_asserts here are checked at
6+
// conversion time, so this fails to compile unless the anonymous tagged
7+
// member is laid out correctly. Member types are deliberately fixed-width
8+
// (no pointers, no long) so the expected sizes are identical on 32-bit and
9+
// 64-bit targets.
10+
struct head
11+
{
12+
int name;
13+
int refcnt;
14+
int aname;
15+
};
16+
17+
struct filename
18+
{
19+
struct head; // anonymous tagged-struct member
20+
char iname[20];
21+
};
22+
23+
_Static_assert(sizeof(struct head) == 12, "head size");
24+
_Static_assert(sizeof(struct filename) == 32, "filename size");
25+
_Static_assert(
26+
__builtin_offsetof(struct filename, iname) == 12,
27+
"iname offset");
28+
29+
// a tagged union as an anonymous member, too
30+
struct u_outer
31+
{
32+
union inner
33+
{
34+
int i;
35+
char c[4];
36+
};
37+
int tail;
38+
};
39+
_Static_assert(sizeof(struct u_outer) == 8, "union-anon size");
40+
41+
int main(void)
42+
{
43+
struct filename f;
44+
f.refcnt = 7; // inner field reachable through the anonymous member
45+
f.name = 0;
46+
47+
struct u_outer u;
48+
u.i = 3; // inner union field reachable
49+
50+
return f.refcnt + u.i;
51+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
main.c
3+
-fms-extensions -c -o main.gb
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
^warning: ignoring
8+
^CONVERSION ERROR$
9+
--
10+
With -fms-extensions, goto-cc must accept a *tagged* struct/union as an
11+
anonymous member: it contributes its size and injects its members. The
12+
_Static_asserts (checked at conversion) require exact layout, and the
13+
field accesses require member injection. This is the pattern used by the
14+
Linux kernel's struct filename (struct __filename_head). Member sizes are
15+
fixed-width so the layout asserts hold on both 32-bit and 64-bit targets.
16+
Without -fms-extensions the member is ignored (standard GCC), which is
17+
covered by regression/ansi-c/anonymous_member*.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Companion to ms_extensions_anonymous_member, compiled WITHOUT
2+
// -fms-extensions. Without that flag goto-cc follows standard gcc/Clang
3+
// behaviour: a *tagged* struct/union used as an anonymous member declares
4+
// nothing -- it injects no members and contributes no size. The
5+
// _Static_assert (checked at conversion time) therefore requires the
6+
// enclosing struct to be sized as if the tagged member were absent.
7+
// Member types are fixed-width so the expected size holds on 32-bit and
8+
// 64-bit targets.
9+
struct head
10+
{
11+
int name;
12+
int refcnt;
13+
int aname;
14+
};
15+
16+
struct filename
17+
{
18+
struct
19+
head; // anonymous tagged-struct member: ignored without -fms-extensions
20+
char iname[20];
21+
};
22+
23+
// The tagged member contributes no size, so the struct is just char[20].
24+
_Static_assert(sizeof(struct filename) == 20, "tagged anon member ignored");
25+
26+
int main(void)
27+
{
28+
struct filename f;
29+
(void)f;
30+
return 0;
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
main.c
3+
-c -o main.gb
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
declaration does not declare anything
7+
--
8+
^CONVERSION ERROR$
9+
--
10+
Without -fms-extensions, goto-cc ignores a tagged struct/union used as an
11+
anonymous member (standard gcc/Clang behaviour): it injects no members and
12+
contributes no size. The _Static_assert requires sizeof(struct filename)
13+
to exclude the tagged member; were the member accepted (as with
14+
-fms-extensions) the size would differ and conversion would fail. This is
15+
the no-flag counterpart of ms_extensions_anonymous_member.

src/ansi-c/c_typecheck_type.cpp

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -986,32 +986,25 @@ void c_typecheck_baset::typecheck_compound_body(
986986
}
987987
else
988988
{
989-
// GCC and Clang ignore anything other than an untagged struct or
990-
// union; we could print a warning, but there isn't any ambiguity in
991-
// semantics here. Printing a warning could elevate this to an error
992-
// when compiling code with goto-cc with -Werror.
993-
// Note that our type checking always creates a struct_tag/union_tag
994-
// type, but only named struct/union types have an ID_tag member.
989+
// C11 allows an *untagged* struct/union as an anonymous member.
990+
// -fms-extensions (and MSVC) additionally allow a *tagged*
991+
// struct/union as an anonymous member, injecting its members
992+
// and contributing its size -- used throughout the Linux kernel
993+
// (e.g. `struct __filename_head;` embedded in struct filename).
994+
// Anything else -- a non-struct/union unnamed member (e.g. a bare
995+
// `int;`), or a tagged struct/union without -fms-extensions -- is
996+
// ignored (the GCC/Clang behaviour we preserve).
997+
const typet &member_type = new_component.type();
995998
if(
996-
new_component.type().id() == ID_struct_tag &&
997-
follow_tag(to_struct_tag_type(new_component.type()))
999+
member_type.id() != ID_struct_tag &&
1000+
member_type.id() != ID_union_tag)
1001+
continue; // not a struct/union: ignore
1002+
const bool is_untagged =
1003+
follow_tag(to_struct_or_union_tag_type(member_type))
9981004
.find(ID_tag)
999-
.is_nil())
1000-
{
1001-
// ok, anonymous struct
1002-
}
1003-
else if(
1004-
new_component.type().id() == ID_union_tag &&
1005-
follow_tag(to_union_tag_type(new_component.type()))
1006-
.find(ID_tag)
1007-
.is_nil())
1008-
{
1009-
// ok, anonymous union
1010-
}
1011-
else
1012-
{
1013-
continue;
1014-
}
1005+
.is_nil();
1006+
if(!is_untagged && !config.ansi_c.allow_anonymous_struct_embedding)
1007+
continue; // tagged anonymous member needs -fms-extensions
10151008
}
10161009
}
10171010

src/goto-cc/gcc_mode.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,15 @@ int gcc_modet::doit()
579579
config.ansi_c.wchar_t_is_unsigned=true;
580580
}
581581

582+
// -fms-extensions enables MS/GCC extensions such as anonymous members
583+
// that are a *tagged* struct/union type (used throughout the Linux
584+
// kernel, e.g. struct __filename_head embedded in struct filename).
585+
// Only anonymous struct/union embedding is implemented here.
586+
// Note: goto_cc_cmdlinet stores long options without the leading '-',
587+
// so we query "fms-extensions" (cf. the "fshort-double" check below).
588+
if(cmdline.isset("fms-extensions"))
589+
config.ansi_c.allow_anonymous_struct_embedding = true;
590+
582591
// -fsingle-precision-constant makes floating-point constants "float"
583592
// instead of double
584593
if(cmdline.isset("-fsingle-precision-constant"))

src/util/config.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,7 @@ bool configt::set(const cmdlinet &cmdline)
867867
cpp.cpp_standard=cppt::default_cpp_standard();
868868

869869
ansi_c.single_precision_constant=false;
870+
ansi_c.allow_anonymous_struct_embedding = false;
870871
ansi_c.for_has_scope=true; // C99 or later
871872
ansi_c.ts_18661_3_Floatn_types=false;
872873
ansi_c.__float128_is_keyword = false;

src/util/config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class configt
168168
bool bf16_type; // __bf16 (Clang >= 15, GCC >= 13)
169169
bool fp16_type; // __fp16 (GCC >= 4.5 on ARM, Clang >= 6)
170170
bool single_precision_constant;
171+
bool allow_anonymous_struct_embedding; // -fms-extensions (partial)
171172
enum class c_standardt
172173
{
173174
C89,

0 commit comments

Comments
 (0)