[llvm-branch-commits] [clang] release/22.x: [Clang][CodeGen] Fix __builtin_counted_by_ref for nested struct FAMs (#182575) (#182590) (PR #182606)
Cullen Rhodes via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Feb 23 00:37:16 PST 2026
https://github.com/c-rhodes updated https://github.com/llvm/llvm-project/pull/182606
>From 23fed3fa2f9aa8e573f6028a24ce03d630a77b0b Mon Sep 17 00:00:00 2001
From: Kees Cook <kees at kernel.org>
Date: Fri, 20 Feb 2026 13:34:17 -0800
Subject: [PATCH] [Clang][CodeGen] Fix __builtin_counted_by_ref for nested
struct FAMs (#182575) (#182590)
GetCountedByFieldExprGEP() used getOuterLexicalRecordContext() to find
the RecordDecl containing the counted_by count field. This walks up
through all lexically enclosing records to find the outermost one, which
is wrong when a struct with a counted_by FAM is defined nested inside
another named struct.
For example, when struct inner (containing the FAM) is defined inside
struct outer, getOuterLexicalRecordContext() resolves to struct outer
instead of struct inner. The StructAccessBase visitor then fails to
match the base expression type (struct inner *) against the expected
record (struct outer), returning nullptr. This nullptr propagates back
as the GEP result, and the subsequent dereference in
*__builtin_counted_by_ref() triggers an assertion failure in
Address::getBasePointer().
Replace getOuterLexicalRecordContext() with a walk that only traverses
anonymous structs and unions, which are transparent in C and must be
walked past. Named nested structs are independently-addressable types,
so the walk stops at them.
Add a regression test for a FAM struct defined nested inside another
struct.
This also fixes __builtin_dynamic_object_size() for FAMs in nested
structs, which was silently returning -1 (unknown) instead of computing
the correct size. Update the attr-counted-by-pr88931.c test to reflect
the now-correct dynamic object size calculation.
Fixes #182575
Signed-off-by: Kees Cook <kees at kernel.org>
(cherry picked from commit 09a3d830a888f15abca0ca51f68786e5517cd61f)
---
clang/lib/CodeGen/CGExpr.cpp | 13 +++++++++-
clang/test/CodeGen/attr-counted-by-pr88931.c | 7 ++++-
clang/test/CodeGen/builtin-counted-by-ref.c | 27 ++++++++++++++++++++
3 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 56e2569c8122f..24878710d65e9 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1187,7 +1187,18 @@ static bool getGEPIndicesToField(CodeGenFunction &CGF, const RecordDecl *RD,
llvm::Value *CodeGenFunction::GetCountedByFieldExprGEP(
const Expr *Base, const FieldDecl *FAMDecl, const FieldDecl *CountDecl) {
- const RecordDecl *RD = CountDecl->getParent()->getOuterLexicalRecordContext();
+ // Find the record containing the count field. Walk up through anonymous
+ // structs/unions (which are transparent in C) but stop at named records.
+ // Using getOuterLexicalRecordContext() here would be wrong because it walks
+ // past named nested structs to the outermost record, causing a crash when a
+ // struct with a counted_by FAM is defined nested inside another struct.
+ const RecordDecl *RD = CountDecl->getParent();
+ while (RD->isAnonymousStructOrUnion()) {
+ const auto *Parent = dyn_cast<RecordDecl>(RD->getLexicalParent());
+ if (!Parent)
+ break;
+ RD = Parent;
+ }
// Find the base struct expr (i.e. p in p->a.b.c.d).
const Expr *StructBase = StructAccessBase(RD).Visit(Base);
diff --git a/clang/test/CodeGen/attr-counted-by-pr88931.c b/clang/test/CodeGen/attr-counted-by-pr88931.c
index 1bd6f24461422..ded301f521f71 100644
--- a/clang/test/CodeGen/attr-counted-by-pr88931.c
+++ b/clang/test/CodeGen/attr-counted-by-pr88931.c
@@ -15,7 +15,12 @@ void init(void * __attribute__((pass_dynamic_object_size(0))));
// CHECK-SAME: ptr noundef [[P:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: entry:
// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 4
-// CHECK-NEXT: tail call void @init(ptr noundef nonnull [[ARRAY]], i64 noundef -1) #[[ATTR2:[0-9]+]]
+// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[P]], align 4
+// CHECK-NEXT: [[COUNT:%.*]] = sext i32 [[COUNTED_BY_LOAD]] to i64
+// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = shl nsw i64 [[COUNT]], 2
+// CHECK-NEXT: [[TMP0:%.*]] = icmp sgt i32 [[COUNTED_BY_LOAD]], -1
+// CHECK-NEXT: [[TMP1:%.*]] = select i1 [[TMP0]], i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0
+// CHECK-NEXT: tail call void @init(ptr noundef nonnull [[ARRAY]], i64 noundef [[TMP1]]) #[[ATTR2:[0-9]+]]
// CHECK-NEXT: ret void
//
void test1(struct bar *p) {
diff --git a/clang/test/CodeGen/builtin-counted-by-ref.c b/clang/test/CodeGen/builtin-counted-by-ref.c
index d44dbf5d0c1a2..8b1ef0edb8bd9 100644
--- a/clang/test/CodeGen/builtin-counted-by-ref.c
+++ b/clang/test/CodeGen/builtin-counted-by-ref.c
@@ -233,3 +233,30 @@ struct d *test4(int size, int *data) {
*__builtin_counted_by_ref(p->ptr) = size;
return p;
}
+
+// Test for FAM struct defined nested inside another struct (issue #182575).
+// The nested struct is named (not anonymous), so getOuterLexicalRecordContext()
+// would incorrectly resolve to 'struct outer' instead of 'struct inner'.
+struct outer {
+ struct inner {
+ int counter;
+ int ent[] __attribute__((counted_by(counter)));
+ } *entries;
+};
+
+// X86_64-LABEL: define dso_local ptr @test5(
+// X86_64-SAME: i32 noundef [[COUNT:%.*]]) #[[ATTR0]] {
+// X86_64: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_INNER:%.*]], ptr {{%.*}}, i32 0, i32 0
+// X86_64-NEXT: store i32 [[TMP1:%.*]], ptr [[DOT_COUNTED_BY_GEP]], align 4
+//
+// I386-LABEL: define dso_local ptr @test5(
+// I386-SAME: i32 noundef [[COUNT:%.*]]) #[[ATTR0]] {
+// I386: [[DOT_COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_INNER:%.*]], ptr {{%.*}}, i32 0, i32 0
+// I386-NEXT: store i32 [[TMP1:%.*]], ptr [[DOT_COUNTED_BY_GEP]], align 4
+//
+struct inner *test5(int count) {
+ struct inner *entries = __builtin_malloc(sizeof(*entries) + count * sizeof(*entries->ent));
+ if (entries)
+ *__builtin_counted_by_ref(entries->ent) = count;
+ return entries;
+}
More information about the llvm-branch-commits
mailing list