struct P { int x; int y; };
int main(void) {
char a[sizeof(struct P { int y; int x; })]; /* declares a new struct P in this block */
struct P v = {1, 2}; /* must name the inner P: v.x == 2, v.y == 1 */
return v.x * 10 + v.y; /* 21 */
}
|
exit code |
CompCert 3.18 (74cbdbf0), -std=c11 -Wall |
12, no warning |
CompCert 3.18, -interp |
12 ("program terminated (exit code = 12)") |
GCC 16.2, -std=c11 -pedantic -Wall |
21 |
Clang 22.1.8, -std=c11 -pedantic -Wall |
21 |
CompCert binds struct P v to the file-scope struct P, which is incorrect.
A similar issue seems to have been fixed for initializers in commit 0375c183, tested in CompCert-small-tests/regression/decl2.c
Here is a more comprehensive suite of similar problems:
/* Declarations inside constant expressions must stay in scope.
C11 lets a type specifier declare a tag, and an enumerator list declare
enumeration constants, anywhere a type name or constant expression may
appear. Their scope is the enclosing block (6.2.1p4, 6.7.2.3p6), whether
or not the expression is evaluated. In every test below such a
declaration hides a same-spelled outer one, so a later use must see the
inner declaration:
- tag tests declare `struct P { int y; int x; }` over the file-scope
`struct P { int x; int y; }`; `struct P v = {1, 2}` then gives
x = 2, y = 1 and the test returns 21 (12 means the outer P was used);
- constant tests declare `enum { K = 5 }` over the file-scope
`enum { K = 1 }`, and return K: 5 (1 means the outer K was used).
Build: cc -std=c11 compcert_scope_bugs.c && ./a.out (gcc, clang, ccomp) */
#include <stdio.h>
struct P { int x; int y; };
enum { K = 1 };
#define USE_P struct P v = {1, 2}; return v.x * 10 + v.y
/* (A) An array bound, in any declarator or type name. */
static int a1_object_bound(void) { char a[sizeof(struct P { int y; int x; })]; (void)a; USE_P; }
static int a2_typedef_bound(void) { typedef char B[sizeof(struct P { int y; int x; })]; USE_P; }
static int a3_member_bound(void) { struct O { char b[sizeof(struct P { int y; int x; })]; }; USE_P; }
static int a4_cast_type_name(void) { (void)(char (*)[sizeof(struct P { int y; int x; })])0; USE_P; }
static int a5_sizeof_type_name(void) { (void)sizeof(char[sizeof(struct P { int y; int x; })]); USE_P; }
static int a6_compound_literal(void) { (void)(char[sizeof(struct P { int y; int x; })]){0}; USE_P; }
static int a7_parameter_bound(char a[sizeof(struct P { int y; int x; })]) { (void)a; USE_P; }
static int a8_bound_constant(void) { char a[sizeof(enum { K = 5 })]; (void)a; return K; }
static int a9_generic_association(void) {
(void)_Generic(0, char (*)[sizeof(struct P { int y; int x; })]: 1, default: 2);
USE_P;
}
static int a10_return_type_bound(void) { char (*g(void))[sizeof(struct P { int y; int x; })]; (void)g; USE_P; }
/* (B) An enumerator's value. */
static int b1_enumerator_tag(void) { enum { J = sizeof(struct P { int y; int x; }) }; (void)J; USE_P; }
static int b2_enumerator_constant(void) { enum { J = sizeof(enum { K = 5 }) }; (void)J; return K; }
/* (C) A static assertion, in a block or in a member list. */
static int c1_static_assert_tag(void) { _Static_assert(sizeof(struct P { int y; int x; }) > 0, ""); USE_P; }
static int c2_member_static_assert(void) {
struct O { int m; _Static_assert(sizeof(struct P { int y; int x; }) > 0, ""); };
USE_P;
}
static int c3_static_assert_constant(void) { _Static_assert(sizeof(enum { K = 5 }) > 0, ""); return K; }
/* (D) An alignment specifier, with a type name or an expression. */
static int d1_alignas_type(void) { _Alignas(struct P { int y; int x; }) char c; (void)c; USE_P; }
static int d2_alignas_expression(void) { _Alignas(sizeof(struct P { int y; int x; })) char c; (void)c; USE_P; }
/* (E) An array designator in an initializer. */
static int e1_designator_tag(void) {
int a[2] = { [sizeof(struct P { int y; int x; }) > 0] = 1 };
(void)a;
USE_P;
}
static int e2_designator_constant(void) { int a[2] = { [sizeof(enum { K = 5 }) > 0] = 1 }; (void)a; return K; }
/* (F) An initializer (fixed in CompCert master by commit 0375c183). */
static int f1_initializer(void) { unsigned long n = sizeof(struct P { int y; int x; }); (void)n; USE_P; }
static int failures = 0;
static void check(const char *name, int value, int expected) {
printf("%-28s %3d %s\n", name, value, value == expected ? "ok" : "WRONG");
if (value != expected)
failures++;
}
int main(void) {
check("A1 object array bound", a1_object_bound(), 21);
check("A2 typedef array bound", a2_typedef_bound(), 21);
check("A3 member array bound", a3_member_bound(), 21);
check("A4 bound in a cast", a4_cast_type_name(), 21);
check("A5 bound in sizeof(type)", a5_sizeof_type_name(), 21);
check("A6 bound in compound literal", a6_compound_literal(), 21);
check("A7 parameter array bound", a7_parameter_bound(0), 21);
check("A8 bound, enum constant", a8_bound_constant(), 5);
check("A9 bound in _Generic type", a9_generic_association(), 21);
check("A10 bound in return type", a10_return_type_bound(), 21);
check("B1 enumerator value, tag", b1_enumerator_tag(), 21);
check("B2 enumerator value, const", b2_enumerator_constant(), 5);
check("C1 _Static_assert, tag", c1_static_assert_tag(), 21);
check("C2 member _Static_assert", c2_member_static_assert(), 21);
check("C3 _Static_assert, const", c3_static_assert_constant(), 5);
check("D1 _Alignas(type)", d1_alignas_type(), 21);
check("D2 _Alignas(expression)", d2_alignas_expression(), 21);
check("E1 designator, tag", e1_designator_tag(), 21);
check("E2 designator, const", e2_designator_constant(), 5);
check("F1 initializer", f1_initializer(), 21);
return failures != 0;
}
74cbdbf0),-std=c11 -Wall-interp-std=c11 -pedantic -Wall-std=c11 -pedantic -WallCompCert binds
struct P vto the file-scopestruct P, which is incorrect.A similar issue seems to have been fixed for initializers in commit 0375c183, tested in
CompCert-small-tests/regression/decl2.cHere is a more comprehensive suite of similar problems: