Skip to content

Conversation

@kees
Copy link
Contributor

@kees kees commented Dec 12, 2025

The 'counted_by' attribute was previously rejected on flexible array members (FAMs) and pointers that were inside any union. This change allows them when the FAM/pointer is in an anonymous union and the count field is in the parent struct.

This pattern is useful for representing tagged unions where different array types share the same count:

struct buffer { int count; union { char *bytes __counted_by(count); int *ints __counted_by(count); }; }; 

The union cases are handled as such:

Allowed (this patch):

  • FAM/pointer in anonymous union, count in parent struct: the count field is in stable storage outside the union.

Rejected (unchanged):

  • FAM/pointer in named union: named unions are not part of the parent struct's namespace.

  • Both FAM/pointer and count in same anonymous union: they share storage, making the count unreliable.

  • Count in anonymous union: the count field itself cannot be in a union (shared storage).

Provides parity with GCC, which has just been fixed:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122495
https://gcc.gnu.org/pipermail/gcc-patches/2025-December/703480.html

@kees kees requested a review from bwendling December 12, 2025 11:43
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 12, 2025

@llvm/pr-subscribers-clang

Author: Kees Cook (kees)

Changes

The 'counted_by' attribute was previously rejected on flexible array members (FAMs) and pointers that were inside any union. This change allows them when the FAM/pointer is in an anonymous union and the count field is in the parent struct.

This pattern is useful for representing tagged unions where different array types share the same count:

struct buffer { int count; union { char *bytes __counted_by(count); int *ints __counted_by(count); }; }; 

The union cases are handled as such:

Allowed (this patch):

  • FAM/pointer in anonymous union, count in parent struct: the count field is in stable storage outside the union.

Rejected (unchanged):

  • FAM/pointer in named union: named unions are not part of the parent struct's namespace.

  • Both FAM/pointer and count in same anonymous union: they share storage, making the count unreliable.

  • Count in anonymous union: the count field itself cannot be in a union (shared storage).

Provides parity with GCC, which has just been fixed:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122495
https://gcc.gnu.org/pipermail/gcc-patches/2025-December/703480.html


Full diff: https://github.com/llvm/llvm-project/pull/171996.diff

4 Files Affected:

  • (modified) clang/lib/Sema/SemaBoundsSafety.cpp (+22-5)
  • (modified) clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c (+36)
  • (modified) clang/test/Sema/attr-counted-by-struct-ptrs.c (+36)
  • (modified) clang/test/Sema/attr-counted-by-vla.c (+46)
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index de9adf8ef5a1b..dda3e27ea78ca 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -26,14 +26,20 @@ getCountAttrKind(bool CountInBytes, bool OrNull) { : CountAttributedType::CountedBy; } +// Check if a RecordDecl is anonymous or will be anonymous once processing +// completes. The anonymous flag may not be set yet during parsing. +static bool IsAnonymousOrWillBeAnonymous(const RecordDecl *RD) { + return RD->isAnonymousStructOrUnion() || + (!RD->isCompleteDefinition() && RD->getName().empty()); +} + static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) { const auto *RD = FD->getParent(); // An unnamed struct is anonymous struct only if it's not instantiated. // However, the struct may not be fully processed yet to determine // whether it's anonymous or not. In that case, this function treats it as // an anonymous struct and tries to find a named parent. - while (RD && (RD->isAnonymousStructOrUnion() || - (!RD->isCompleteDefinition() && RD->getName().empty()))) { + while (RD && IsAnonymousOrWillBeAnonymous(RD)) { const auto *Parent = dyn_cast<RecordDecl>(RD->getParent()); if (!Parent) break; @@ -56,7 +62,11 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, unsigned Kind = getCountAttrKind(CountInBytes, OrNull); - if (FD->getParent()->isUnion()) { + // Reject if the field is directly in a non-anonymous union. + // Anonymous unions are allowed because their members are promoted to the + // enclosing scope, so the count field can be in the parent struct. + if (FD->getParent()->isUnion() && + !IsAnonymousOrWillBeAnonymous(FD->getParent())) { Diag(FD->getBeginLoc(), diag::err_count_attr_in_union) << Kind << FD->getSourceRange(); return true; @@ -207,10 +217,17 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, return true; } + // Reject if both FD and CountFD are in the same union - they share storage. + if (FD->getParent() == CountFD->getParent() && FD->getParent()->isUnion()) { + Diag(FD->getBeginLoc(), diag::err_count_attr_in_union) + << Kind << FD->getSourceRange(); + return true; + } + if (FD->getParent() != CountFD->getParent()) { if (CountFD->getParent()->isUnion()) { - Diag(CountFD->getBeginLoc(), diag::err_count_attr_refer_to_union) - << Kind << CountFD->getSourceRange(); + Diag(FD->getBeginLoc(), diag::err_count_attr_refer_to_union) + << Kind << FD->getSourceRange(); return true; } // Whether CountRD is an anonymous struct is not determined at this diff --git a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c index 0fd739ca7d4c3..19f88d12e6252 100644 --- a/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c +++ b/clang/test/Sema/attr-counted-by-or-null-struct-ptrs.c @@ -222,3 +222,39 @@ struct on_void_ty { // expected-error@+1{{field has incomplete type 'void'}} void wrong_ty __counted_by_or_null(count); }; + +//============================================================================== +// __counted_by_or_null on pointer members in unions +//============================================================================== + +// Pointer in anonymous union with count in parent struct - OK +struct ptr_in_anon_union_count_in_parent { + int count; + union { + int a; + struct size_known *buf __counted_by_or_null(count); + }; +}; + +// Pointer in named union - ERROR +union ptr_in_named_union { + int count; + struct size_known *buf __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a union member}} +}; + +// Both pointer and count in same anonymous union - ERROR (they share storage) +struct ptr_and_count_in_same_anon_union { + union { + int count; + struct size_known *buf __counted_by_or_null(count); // expected-error {{'counted_by_or_null' cannot be applied to a union member}} + }; +}; + +// Count in anonymous union, pointer in parent struct - ERROR (count in union) +struct count_in_anon_union_ptr_in_parent { + union { + int count; + int x; + }; + struct size_known *buf __counted_by_or_null(count); // expected-error {{'counted_by_or_null' argument cannot refer to a union member}} +}; diff --git a/clang/test/Sema/attr-counted-by-struct-ptrs.c b/clang/test/Sema/attr-counted-by-struct-ptrs.c index a42f3895695a3..b522da78ca9f0 100644 --- a/clang/test/Sema/attr-counted-by-struct-ptrs.c +++ b/clang/test/Sema/attr-counted-by-struct-ptrs.c @@ -221,3 +221,39 @@ struct on_void_ty { // expected-error@+1{{field has incomplete type 'void'}} void wrong_ty __counted_by(count); }; + +//============================================================================== +// __counted_by on pointer members in unions +//============================================================================== + +// Pointer in anonymous union with count in parent struct - OK +struct ptr_in_anon_union_count_in_parent { + int count; + union { + int a; + struct size_known *buf __counted_by(count); + }; +}; + +// Pointer in named union - ERROR +union ptr_in_named_union { + int count; + struct size_known *buf __counted_by(count); // expected-error {{'counted_by' cannot be applied to a union member}} +}; + +// Both pointer and count in same anonymous union - ERROR (they share storage) +struct ptr_and_count_in_same_anon_union { + union { + int count; + struct size_known *buf __counted_by(count); // expected-error {{'counted_by' cannot be applied to a union member}} + }; +}; + +// Count in anonymous union, pointer in parent struct - ERROR (count in union) +struct count_in_anon_union_ptr_in_parent { + union { + int count; + int x; + }; + struct size_known *buf __counted_by(count); // expected-error {{'counted_by' argument cannot refer to a union member}} +}; diff --git a/clang/test/Sema/attr-counted-by-vla.c b/clang/test/Sema/attr-counted-by-vla.c index 5b1b4833dce7d..be430081a2fae 100644 --- a/clang/test/Sema/attr-counted-by-vla.c +++ b/clang/test/Sema/attr-counted-by-vla.c @@ -196,3 +196,49 @@ struct buffer_of_const_structs_with_annotated_vla { const struct has_annotated_VLA Arr[] __counted_by(count); }; +//============================================================================== +// __counted_by on flexible array members in unions +//============================================================================== + +// FAM in anonymous union with count in parent struct - OK +struct fam_in_anon_union_count_in_parent { + int count; + union { + char a; + char fam[] __counted_by(count); + }; +}; + +// FAM in named union - ERROR +union fam_in_named_union { + int count; + char fam[] __counted_by(count); // expected-error {{'counted_by' cannot be applied to a union member}} +}; + +// Both FAM and count in same anonymous union - ERROR (they share storage) +struct fam_and_count_in_same_anon_union { + union { + int count; + char fam[] __counted_by(count); // expected-error {{'counted_by' cannot be applied to a union member}} + }; +}; + +// Count in anonymous union, FAM in parent struct - ERROR (count in union) +struct count_in_anon_union_fam_in_parent { + union { + int count; + int x; + }; + char fam[] __counted_by(count); // expected-error {{'counted_by' argument cannot refer to a union member}} +}; + +// FAM in nested anonymous struct inside anonymous union - OK +struct fam_in_nested_anon_struct_in_anon_union { + int count; + union { + int a; + struct { + char fam[] __counted_by(count); + }; + }; +}; 
…mous unions The 'counted_by' attribute was previously rejected on flexible array members (FAMs) and pointers that were inside any union. This change allows them when the FAM/pointer is in an anonymous union and the count field is in the parent struct. This pattern is useful for representing tagged unions where different array types share the same count: struct buffer { int count; union { char *bytes __counted_by(count); int *ints __counted_by(count); }; }; The union cases are handled as such: Allowed (this patch): - FAM/pointer in anonymous union, count in parent struct: the count field is in stable storage outside the union. Rejected (unchanged): - FAM/pointer in named union: named unions are not part of the parent struct's namespace. - Both FAM/pointer and count in same anonymous union: they share storage, making the count unreliable. - Count in anonymous union: the count field itself cannot be in a union (shared storage). Provides parity with GCC, which has just been fixed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122495 https://gcc.gnu.org/pipermail/gcc-patches/2025-December/703480.html
@kees kees force-pushed the counted_by-in-union branch from cec5a54 to 230682b Compare December 12, 2025 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

2 participants