Bootstrapped and regression tested on x86_64 and aarch64.
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.
---
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(-)
create mode 100644 gcc/testsuite/gcc.dg/pr123356-2.c
create mode 100644 gcc/testsuite/gcc.dg/struct-alias-2.c
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 80eebc35908..10b4d77daee 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 167f50e900c..30c602b731f 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 5b6b2b1d6e0..fa8a707b5e6 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 00000000000..1f0b9c99b74
--- /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 00000000000..71bc1edc465
--- /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;
+}
+
--
2.47.3