C23: fix aliasing for structures/unions with incomplete types
When incomplete structure/union types are completed later, compatibility of struct types that contain pointers to such types changes. When forming equivalence classes for TYPE_CANONICAL, we therefor need to be conservative and treat all structs with the same tag which are pointer targets as equivalent for purposed of determining equivalency of structure/union types which contain such types as member. This avoids having to update TYPE_CANONICAL of such structure/unions recursively. The pointer types themselves are updated in c_update_type_canonical. gcc/c/ * c-typeck.cc (comptypes_internal): Add flag to track whether a struct is the target of a pointer. (tagged_types_tu_compatible): When forming equivalence classes, treat nested pointed-to structs as equivalent. gcc/testsuite/ * gcc.dg/c23-tag-incomplete-alias-1.c: New test.
This commit is contained in:
parent
915440eed2
commit
86b98d9399
2 changed files with 76 additions and 3 deletions
|
@ -1172,6 +1172,7 @@ struct comptypes_data {
|
|||
bool different_types_p;
|
||||
bool warning_needed;
|
||||
bool anon_field;
|
||||
bool pointedto;
|
||||
bool equiv;
|
||||
|
||||
const struct tagged_tu_seen_cache* cache;
|
||||
|
@ -1235,8 +1236,36 @@ comptypes_check_different_types (tree type1, tree type2,
|
|||
}
|
||||
|
||||
|
||||
/* Like comptypes, but if it returns nonzero for struct and union
|
||||
types considered equivalent for aliasing purposes. */
|
||||
/* Like comptypes, but if it returns true for struct and union types
|
||||
considered equivalent for aliasing purposes, i.e. for setting
|
||||
TYPE_CANONICAL after completing a struct or union.
|
||||
|
||||
This function must return false only for types which are not
|
||||
compatible according to C language semantics (cf. comptypes),
|
||||
otherwise the middle-end would make incorrect aliasing decisions.
|
||||
It may return true for some similar types that are not compatible
|
||||
according to those stricter rules.
|
||||
|
||||
In particular, we ignore size expression in arrays so that the
|
||||
following structs are in the same equivalence class:
|
||||
|
||||
struct foo { char (*buf)[]; };
|
||||
struct foo { char (*buf)[3]; };
|
||||
struct foo { char (*buf)[4]; };
|
||||
|
||||
We also treat unions / structs with members which are pointers to
|
||||
structures or unions with the same tag as equivalent (if they are not
|
||||
incompatible for other reasons). Although incomplete structure
|
||||
or union types are not compatible to any other type, they may become
|
||||
compatible to different types when completed. To avoid having to update
|
||||
TYPE_CANONICAL at this point, we only consider the tag when forming
|
||||
the equivalence classes. For example, the following types with tag
|
||||
'foo' are all considered equivalent:
|
||||
|
||||
struct bar;
|
||||
struct foo { struct bar *x };
|
||||
struct foo { struct bar { int a; } *x };
|
||||
struct foo { struct bar { char b; } *x }; */
|
||||
|
||||
bool
|
||||
comptypes_equiv_p (tree type1, tree type2)
|
||||
|
@ -1357,6 +1386,7 @@ comptypes_internal (const_tree type1, const_tree type2,
|
|||
/* Do not remove mode information. */
|
||||
if (TYPE_MODE (t1) != TYPE_MODE (t2))
|
||||
return false;
|
||||
data->pointedto = true;
|
||||
return comptypes_internal (TREE_TYPE (t1), TREE_TYPE (t2), data);
|
||||
|
||||
case FUNCTION_TYPE:
|
||||
|
@ -1375,7 +1405,7 @@ comptypes_internal (const_tree type1, const_tree type2,
|
|||
|
||||
if ((d1 == NULL_TREE) != (d2 == NULL_TREE))
|
||||
data->different_types_p = true;
|
||||
/* Ignore size mismatches. */
|
||||
/* Ignore size mismatches when forming equivalence classes. */
|
||||
if (data->equiv)
|
||||
return true;
|
||||
/* Sizes must match unless one is missing or variable. */
|
||||
|
@ -1515,6 +1545,12 @@ tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
|
|||
if (TYPE_NAME (t1) != TYPE_NAME (t2))
|
||||
return false;
|
||||
|
||||
/* When forming equivalence classes for TYPE_CANONICAL in C23, we treat
|
||||
structs with the same tag as equivalent, but only when they are targets
|
||||
of pointers inside other structs. */
|
||||
if (data->equiv && data->pointedto)
|
||||
return true;
|
||||
|
||||
if (!data->anon_field && NULL_TREE == TYPE_NAME (t1))
|
||||
return false;
|
||||
|
||||
|
@ -1610,6 +1646,7 @@ tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
|
|||
return false;
|
||||
|
||||
data->anon_field = !DECL_NAME (s1);
|
||||
data->pointedto = false;
|
||||
|
||||
data->cache = &entry;
|
||||
if (!comptypes_internal (TREE_TYPE (s1), TREE_TYPE (s2), data))
|
||||
|
|
36
gcc/testsuite/gcc.dg/c23-tag-incomplete-alias-1.c
Normal file
36
gcc/testsuite/gcc.dg/c23-tag-incomplete-alias-1.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* { dg-do run }
|
||||
* { dg-options "-std=c23 -O2" } */
|
||||
|
||||
[[gnu::noinline]]
|
||||
void *alias(void *ap, void *bp, void *x, void *y)
|
||||
{
|
||||
struct foo { struct bar *f; } *a = ap;
|
||||
struct bar { long x; };
|
||||
|
||||
a->f = x;
|
||||
|
||||
{
|
||||
struct bar;
|
||||
struct foo { struct bar *f; } *b = bp;
|
||||
struct bar { long x; };
|
||||
|
||||
// after completing bar, the two struct foo should be compatible
|
||||
|
||||
b->f = y;
|
||||
}
|
||||
|
||||
|
||||
return a->f;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
struct bar { long x; };
|
||||
struct foo { struct bar *f; } a;
|
||||
struct bar x, y;
|
||||
if (&y != alias(&a, &a, &x, &y))
|
||||
__builtin_abort();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue