https://gcc.gnu.org/g:d5c50c75f05c783e4b2dd8599f6d16f13961e5f1
commit r16-7872-gd5c50c75f05c783e4b2dd8599f6d16f13961e5f1 Author: Martin Uecker <[email protected]> Date: Fri Feb 20 17:19:10 2026 +0100 c: Fix wrong code related to TBAA for components of structure types 2/2 [PR122572] Given the following two types, the C FE assigns the same TYPE_CANONICAL to both struct bar, because it treats pointer to tagged types with the same type as compatible (in this context). struct foo { int y; }; struct bar { struct foo *c; } struct foo { long y; }; struct bar { struct foo *c; } get_alias_set records the components of aggregate types, but only considers the components of the canonical version. To prevent miscompilation, we create a modified canonical type where we change such pointers to void pointers. PR c/122572 gcc/c/ChangeLog: * c-decl.cc (finish_struct): Add distinct canonical type. * c-tree.h (c_type_canonical): Prototype for new function. * c-typeck.cc (c_type_canonical): New function. (ptr_to_tagged_member): New function. gcc/testsuite/ChangeLog: * gcc.dg/pr123356-2.c: New test. * gcc.dg/struct-alias-2.c: New test. Diff: --- gcc/c/c-decl.cc | 4 +- gcc/c/c-tree.h | 1 + gcc/c/c-typeck.cc | 72 +++++++++++++++++- gcc/testsuite/gcc.dg/pr123356-2.c | 69 ++++++++++++++++++ gcc/testsuite/gcc.dg/struct-alias-2.c | 133 ++++++++++++++++++++++++++++++++++ 5 files changed, 275 insertions(+), 4 deletions(-) diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc index 80eebc359084..10b4d77daee4 100644 --- a/gcc/c/c-decl.cc +++ b/gcc/c/c-decl.cc @@ -9995,10 +9995,10 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes, tree *e = c_struct_htab->find_slot_with_hash (t, hash, INSERT); if (*e) - TYPE_CANONICAL (t) = *e; + TYPE_CANONICAL (t) = TYPE_CANONICAL (*e); else { - TYPE_CANONICAL (t) = t; + TYPE_CANONICAL (t) = c_type_canonical (t); *e = t; } c_update_type_canonical (t); diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h index 167f50e900c7..30c602b731f8 100644 --- a/gcc/c/c-tree.h +++ b/gcc/c/c-tree.h @@ -931,6 +931,7 @@ extern tree c_build_function_call_vec (location_t, const vec<location_t>&, vec<tree, va_gc> *); extern tree c_omp_clause_copy_ctor (tree, tree, tree); extern tree c_reconstruct_complex_type (tree, tree); +extern tree c_type_canonical (tree); extern tree c_build_type_attribute_variant (tree ntype, tree attrs); extern tree c_build_pointer_type (tree type); extern tree c_build_array_type (tree type, tree domain); diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index 5b6b2b1d6e08..051773a0cd4d 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -543,8 +543,6 @@ c_reconstruct_complex_type (tree type, tree bottom) gcc_checking_assert (C_TYPE_VARIABLE_SIZE (type) == C_TYPE_VARIABLE_SIZE (outer)); - gcc_checking_assert (C_TYPE_VARIABLY_MODIFIED (outer) - == C_TYPE_VARIABLY_MODIFIED (type)); } else if (TREE_CODE (type) == FUNCTION_TYPE) { @@ -562,6 +560,76 @@ c_reconstruct_complex_type (tree type, tree bottom) TYPE_QUALS (type)); } + +/* Helper function for c_canonical_type. Check whether FIELD + contains a pointer to a structure or union with tag, + possibly nested in other type derivations. */ +static bool +ptr_to_tagged_member (tree field) +{ + gcc_assert (FIELD_DECL == TREE_CODE (field)); + tree type = TREE_TYPE (field); + bool ptr_seen = false; + while (TREE_CODE (type) == ARRAY_TYPE + || TREE_CODE (type) == FUNCTION_TYPE + || TREE_CODE (type) == POINTER_TYPE) + { + if (TREE_CODE (type) == POINTER_TYPE) + ptr_seen = true; + type = TREE_TYPE (type); + } + + if (ptr_seen + && RECORD_OR_UNION_TYPE_P (type) + && NULL_TREE != c_type_tag (type)) + return true; + + return false; +} + +/* For a record or union type, make necessary adaptations so that the + type can be used as TYPE_CANONICAL. + + If the TYPE contains a pointer (possibly nested in other type + derivations) to a structure or union as a member, create a copy + and change such pointers to void pointers. Otherwise, the middle-end + gets confused when recording component aliases in the case where we + have formed equivalency classes that include types for which these + member pointers end up pointing to other structure or unions types + which have the same tag but are not compatible. */ +tree +c_type_canonical (tree type) +{ + gcc_assert (RECORD_OR_UNION_TYPE_P (type)); + + bool needs_mod = false; + for (tree x = TYPE_FIELDS (type); x; x = DECL_CHAIN (x)) + { + if (!ptr_to_tagged_member (x)) + continue; + needs_mod = true; + break; + } + if (!needs_mod) + return type; + + /* Construct new version with such members replaced. */ + tree n = build_distinct_type_copy (type); + tree *fields = &TYPE_FIELDS (n); + for (tree x = TYPE_FIELDS (type); x; x = DECL_CHAIN (x)) + { + tree f = copy_node (x); + if (ptr_to_tagged_member (x)) + TREE_TYPE (f) = c_reconstruct_complex_type (TREE_TYPE (x), + ptr_type_node); + *fields = f; + fields = &DECL_CHAIN (f); + } + TYPE_CANONICAL (n) = n; + return n; +} + + /* If NTYPE is a type of a non-variadic function with a prototype and OTYPE is a type of a function without a prototype and ATTRS contains attribute format, diagnosess and removes it from ATTRS. diff --git a/gcc/testsuite/gcc.dg/pr123356-2.c b/gcc/testsuite/gcc.dg/pr123356-2.c new file mode 100644 index 000000000000..1f0b9c99b74b --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr123356-2.c @@ -0,0 +1,69 @@ +/* { dg-do run } */ +/* { dg-options "-O2" } */ + + +/* Based on the submitted test case but using types with + * tags and moving the second version into another scope. */ + +typedef struct foo +{ + long coeffs; +} +fmpz_poly_struct; + + +void f() +{ + typedef struct foo + { + } n_poly_struct; + typedef struct + { + n_poly_struct * coeffs; + long alloc; + long length; + } n_bpoly_struct; +} + + +typedef struct +{ + fmpz_poly_struct * coeffs; + long alloc; + long length; +} fmpz_bpoly_struct; +typedef fmpz_bpoly_struct fmpz_bpoly_t[]; + +__attribute__((noinline)) +fmpz_poly_struct * fmpz_bpoly_swap_(fmpz_bpoly_t B, fmpz_bpoly_t Q) +{ + fmpz_bpoly_struct t = *B; + *B = *Q; + *Q = t; + return B->coeffs; +} + +__attribute__((noinline,optimize("no-strict-aliasing"))) +fmpz_poly_struct * fmpz_bpoly_swap_2(fmpz_bpoly_t B, fmpz_bpoly_t Q) +{ + fmpz_bpoly_struct t = *B; + *B = *Q; + *Q = t; + return B->coeffs; +} + +int main(){ + fmpz_poly_struct B_coeffs = {0}, Q_coeffs = {0}; + fmpz_bpoly_t B = {0}; + fmpz_bpoly_t Q = {0}; + B->coeffs = &B_coeffs; + Q->coeffs = &Q_coeffs; + if (fmpz_bpoly_swap_(B, Q) != &Q_coeffs) + __builtin_abort(); + B->coeffs = &B_coeffs; + Q->coeffs = &Q_coeffs; + if (fmpz_bpoly_swap_2(B, Q) != &Q_coeffs) + __builtin_abort(); + return 0; +} + diff --git a/gcc/testsuite/gcc.dg/struct-alias-2.c b/gcc/testsuite/gcc.dg/struct-alias-2.c new file mode 100644 index 000000000000..71bc1edc4653 --- /dev/null +++ b/gcc/testsuite/gcc.dg/struct-alias-2.c @@ -0,0 +1,133 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -std=c23" } */ + + +/* Based on the submitted test case for PR123356 but using types with + * tags and moving the second version into another scope. */ + +struct foo { long x; }; + +void f() +{ + struct foo { }; + struct bar { struct foo *c; }; + union baz { struct foo *c; }; + struct arr { struct foo *c[1]; }; + struct fun { struct foo (*c)(); }; +} + + +void f1() +{ + struct foo { }; + struct bar { struct foo *c; }; + union baz { struct foo *c; }; + struct arr { struct foo *c[1]; }; + struct fun { struct foo (*c)(); }; +} + +struct bar { struct foo *c; }; +union baz { struct foo *c; }; +struct arr { struct foo *c[1]; }; +struct fun { struct foo (*c)(); }; + +void f2() +{ + struct foo { int y; }; + struct bar { struct foo *c; }; + union baz { struct foo *c; }; + struct arr { struct foo *c[1]; }; + struct fun { struct foo (*c)(); }; +} + +__attribute__((noinline)) +struct foo * g1(struct bar *B, struct bar *Q) +{ + struct bar t = *B; + *B = *Q; + *Q = t; + return B->c; +} + +__attribute__((noinline)) +struct foo * g2(union baz *B, union baz *Q) +{ + union baz t = *B; + *B = *Q; + *Q = t; + return B->c; +} + +__attribute__((noinline)) +struct foo { long x; } * + g3(struct bar { struct foo { long x; } *c; } *B, + struct bar { struct foo { long x; } *c; } *Q) +{ + struct bar t = *B; + *B = *Q; + *Q = t; + return B->c; +} + +__attribute__((noinline)) +struct foo * g4(struct arr *B, + struct arr *Q) +{ + struct arr t = *B; + *B = *Q; + *Q = t; + return B->c[0]; +} + +__attribute__((noinline)) +struct foo (*g5(struct fun *B, + struct fun *Q))() +{ + struct fun t = *B; + *B = *Q; + *Q = t; + return B->c; +} + +struct foo Bd() { }; +struct foo Qd() { }; + +int main() +{ + struct foo Bc = { }; + struct foo Qc = { }; + + struct bar B = { &Bc }; + struct bar Q = { &Qc }; + + if (g1(&B, &Q) != &Qc) + __builtin_abort(); + + union baz Bu = { &Bc }; + union baz Qu = { &Qc }; + + if (g2(&Bu, &Qu) != &Qc) + __builtin_abort(); + + struct bar B2 = { &Bc }; + struct bar Q2 = { &Qc }; + + if (g3(&B2, &Q2) != &Qc) + __builtin_abort(); + + struct arr Ba = { &Bc }; + struct arr Qa = { &Qc }; + + if (g4(&Ba, &Qa) != &Qc) + __builtin_abort(); +#if 0 + // PR114959 + struct fun Bf = { &Bd }; + struct fun Qf = { &Qd }; + + if (g5(&Bf, &Qf) != &Qd) + __builtin_abort(); +#endif + return 0; +} +
