[clang] [Clang][counted_by] Use the expr's RecordDecl that transitively contains the counter field (PR #205903)
Bill Wendling via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 5 20:58:59 PDT 2026
https://github.com/isanbard updated https://github.com/llvm/llvm-project/pull/205903
>From 57b81573b38b1140c93f0294cd9a1bd217d2c9ca Mon Sep 17 00:00:00 2001
From: Bill Wendling <morbo at google.com>
Date: Wed, 24 Jun 2026 23:22:32 +0000
Subject: [PATCH 1/4] [Clang][counted_by] Use the expr's RecordDecl if
available
---
clang/lib/CodeGen/CGExpr.cpp | 21 ++-
.../CodeGen/attr-counted-by-nested-structs.c | 127 ++++++++++++++++++
2 files changed, 142 insertions(+), 6 deletions(-)
create mode 100644 clang/test/CodeGen/attr-counted-by-nested-structs.c
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 465a020e38e74..bfe27bebbea9f 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1204,12 +1204,21 @@ llvm::Value *CodeGenFunction::GetCountedByFieldExprGEP(
// 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;
+ //
+ // The Base expression may itself be a pointer to a field of type 'struct'. If
+ // so, then we want to use that as the "base" RecordDecl.
+ QualType Ty = Base->IgnoreParenNoopCasts(getContext())->getType();
+ if (Ty->isPointerType())
+ Ty = Ty->getPointeeType();
+ const RecordDecl *RD = Ty->getAsRecordDecl();
+ if (!RD) {
+ 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).
diff --git a/clang/test/CodeGen/attr-counted-by-nested-structs.c b/clang/test/CodeGen/attr-counted-by-nested-structs.c
new file mode 100644
index 0000000000000..954764fde2c79
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-nested-structs.c
@@ -0,0 +1,127 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck %s
+
+#if !__has_attribute(counted_by)
+#error "has attribute broken"
+#endif
+
+#define __counted_by(member) __attribute__((__counted_by__(member)))
+#define __bdos(P) __builtin_dynamic_object_size(P, 0)
+
+struct flex {
+ unsigned count;
+ char a; /* force tail padding */
+ char fam[] __counted_by(count);
+};
+
+struct single_nested {
+ int header;
+ struct flex buf;
+};
+
+struct double_nested {
+ int header;
+ struct single_nested buf;
+};
+
+struct triple_nested {
+ int header;
+ struct double_nested buf;
+};
+
+struct quad_nested {
+ int header;
+ struct triple_nested buf;
+};
+
+// CHECK-LABEL: define dso_local i32 @test_size_of_single_nested(
+// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_SINGLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_SINGLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_FLEX:%.*]], ptr [[BUF1]], i32 0, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
+// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
+// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 12
+// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
+// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
+// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
+// CHECK-NEXT: ret i32 [[CONV]]
+//
+unsigned test_size_of_single_nested(struct single_nested *p) {
+ return __bdos(&p->buf);
+}
+
+// CHECK-LABEL: define dso_local i32 @test_size_of_double_nested(
+// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_DOUBLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_DOUBLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_SINGLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
+// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
+// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 16
+// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
+// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
+// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
+// CHECK-NEXT: ret i32 [[CONV]]
+//
+unsigned test_size_of_double_nested(struct double_nested *p) {
+ return __bdos(&p->buf);
+}
+
+// CHECK-LABEL: define dso_local i32 @test_size_of_triple_nested(
+// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_DOUBLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
+// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
+// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 20
+// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
+// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
+// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
+// CHECK-NEXT: ret i32 [[CONV]]
+//
+unsigned test_size_of_triple_nested(struct triple_nested *p) {
+ return __bdos(&p->buf);
+}
+
+// CHECK-LABEL: define dso_local i32 @test_size_of_quad_nested(
+// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_QUAD_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
+// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_QUAD_NESTED]], ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_TRIPLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
+// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
+// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 24
+// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
+// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
+// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
+// CHECK-NEXT: ret i32 [[CONV]]
+//
+unsigned test_size_of_quad_nested(struct quad_nested *p) {
+ return __bdos(&p->buf);
+}
>From c6cb53434d035d427231f20bae6ccadc7794d256 Mon Sep 17 00:00:00 2001
From: Bill Wendling <morbo at google.com>
Date: Thu, 25 Jun 2026 20:42:33 +0000
Subject: [PATCH 2/4] Re-update the testcase.
---
.../CodeGen/attr-counted-by-nested-structs.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/clang/test/CodeGen/attr-counted-by-nested-structs.c b/clang/test/CodeGen/attr-counted-by-nested-structs.c
index 954764fde2c79..9f715add35872 100644
--- a/clang/test/CodeGen/attr-counted-by-nested-structs.c
+++ b/clang/test/CodeGen/attr-counted-by-nested-structs.c
@@ -43,11 +43,11 @@ struct quad_nested {
// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_SINGLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_SINGLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_FLEX:%.*]], ptr [[BUF1]], i32 0, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_FLEX:%.*]], ptr [[BUF1]], i32 0, i32 0
// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
-// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 12
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 8
// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
@@ -66,11 +66,11 @@ unsigned test_size_of_single_nested(struct single_nested *p) {
// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_DOUBLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_DOUBLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_SINGLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_SINGLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 0
// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
-// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 16
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 12
// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
@@ -89,11 +89,11 @@ unsigned test_size_of_double_nested(struct double_nested *p) {
// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_TRIPLE_NESTED]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_DOUBLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_DOUBLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 0
// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
-// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 20
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 16
// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
@@ -112,11 +112,11 @@ unsigned test_size_of_triple_nested(struct triple_nested *p) {
// CHECK-NEXT: [[BUF:%.*]] = getelementptr inbounds nuw [[STRUCT_QUAD_NESTED:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P_ADDR]], align 8
// CHECK-NEXT: [[BUF1:%.*]] = getelementptr inbounds nuw [[STRUCT_QUAD_NESTED]], ptr [[TMP1]], i32 0, i32 1
-// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_TRIPLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 1, i32 0, i32 1, i32 0
+// CHECK-NEXT: [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds [[STRUCT_TRIPLE_NESTED:%.*]], ptr [[BUF1]], i32 0, i32 1, i32 1, i32 1, i32 0
// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[COUNTED_BY_GEP]], align 4
// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64
// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 [[COUNT]], 1
-// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 24
+// CHECK-NEXT: [[RESULT:%.*]] = add i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], 20
// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
// CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], i64 [[RESULT]], i64 0
// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP3]] to i32
>From e28546cb948c0cb909f03759997ea6f970c92a3c Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Sun, 5 Jul 2026 20:33:17 -0700
Subject: [PATCH 3/4] Rework how we find the base Expr to build the GEP off of
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. recordContainsField — new static helper that checks if CountDecl is
reachable from RD through any level of nesting (mirrors what
getGEPIndicesToField traverses).
2. StructAccessBase — now takes const FieldDecl *CountDecl instead of
const RecordDecl *ExpectedRD. IsExpectedRecordDecl uses
recordContainsField so it matches any struct whose type
transitively contains CountDecl, not just the one that directly
holds it.
3. GetCountedByFieldExprGEP — removed the two-path RD pre-computation
(extract from Base's type, fall back to CountDecl's parent +
anonymous struct walk). Now just calls
StructAccessBase(CountDecl).Visit(Base) and derives RD from
whatever expression the visitor found.
---
clang/lib/CodeGen/CGExpr.cpp | 70 +++++++++++++++++++++---------------
1 file changed, 42 insertions(+), 28 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index efc47c5bbfb1f..e53b4be55926a 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1060,6 +1060,23 @@ static llvm::Value *getArrayIndexingBound(CodeGenFunction &CGF,
namespace {
+/// Returns true if \p Field is reachable from \p RD — either as a direct field
+/// or through a chain of nested record fields (including anonymous
+/// structs/unions). This mirrors the GEP path that getGEPIndicesToField builds,
+/// and is used to identify the right anchor expression in Base.
+static bool recordContainsField(const RecordDecl *RD,
+ const FieldDecl *Field) {
+ for (const FieldDecl *FD : RD->fields()) {
+ if (FD == Field)
+ return true;
+ QualType Ty = FD->getType();
+ if (Ty->isRecordType())
+ if (recordContainsField(Ty->getAsRecordDecl(), Field))
+ return true;
+ }
+ return false;
+}
+
/// \p StructAccessBase returns the base \p Expr of a field access. It returns
/// either a \p DeclRefExpr, representing the base pointer to the struct, i.e.:
///
@@ -1078,17 +1095,24 @@ namespace {
/// \p MemberExpr for \p p->ptr instead of \p p.
class StructAccessBase
: public ConstStmtVisitor<StructAccessBase, const Expr *> {
- const RecordDecl *ExpectedRD;
-
+ /// The count field we're navigating to. We stop at the innermost expression
+ /// whose struct type transitively contains this field, so that
+ /// getGEPIndicesToField can navigate from that struct down to it.
+ const FieldDecl *CountDecl;
+
+ /// Returns true if E's record type (or pointee record type) transitively
+ /// contains CountDecl. Handles both direct containment and nested structs,
+ /// so we don't need a pre-computed RD from the caller.
bool IsExpectedRecordDecl(const Expr *E) const {
QualType Ty = E->getType();
if (Ty->isPointerType())
Ty = Ty->getPointeeType();
- return ExpectedRD == Ty->getAsRecordDecl();
+ const RecordDecl *RD = Ty->getAsRecordDecl();
+ return RD && recordContainsField(RD, CountDecl);
}
public:
- StructAccessBase(const RecordDecl *ExpectedRD) : ExpectedRD(ExpectedRD) {}
+ StructAccessBase(const FieldDecl *CountDecl) : CountDecl(CountDecl) {}
//===--------------------------------------------------------------------===//
// Visitor Methods
@@ -1200,33 +1224,23 @@ static bool getGEPIndicesToField(CodeGenFunction &CGF, const RecordDecl *RD,
llvm::Value *CodeGenFunction::GetCountedByFieldExprGEP(
const Expr *Base, const FieldDecl *FAMDecl, const FieldDecl *CountDecl) {
- // 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.
- //
- // The Base expression may itself be a pointer to a field of type 'struct'. If
- // so, then we want to use that as the "base" RecordDecl.
- QualType Ty = Base->IgnoreParenNoopCasts(getContext())->getType();
- if (Ty->isPointerType())
- Ty = Ty->getPointeeType();
- const RecordDecl *RD = Ty->getAsRecordDecl();
- if (!RD) {
- 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);
+ // Walk Base to find the deepest sub-expression whose struct type transitively
+ // contains CountDecl. This is our GEP anchor — getGEPIndicesToField then
+ // builds the field indices from that struct down to CountDecl, handling any
+ // intermediate nesting without requiring us to pre-compute a RecordDecl from
+ // Base's type or from CountDecl's parent chain.
+ const Expr *StructBase = StructAccessBase(CountDecl).Visit(Base);
if (!StructBase || StructBase->HasSideEffects(getContext()))
return nullptr;
+ // Derive the record type from the anchor expression itself.
+ QualType StructTy = StructBase->getType();
+ if (StructTy->isPointerType())
+ StructTy = StructTy->getPointeeType();
+ const RecordDecl *RD = StructTy->getAsRecordDecl();
+ if (!RD)
+ return nullptr;
+
llvm::Value *Res = nullptr;
if (StructBase->getType()->isPointerType()) {
LValueBaseInfo BaseInfo;
>From bf49144a111d0c469107cea42f5dbf3be4132257 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Sun, 5 Jul 2026 20:58:47 -0700
Subject: [PATCH 4/4] Reformat
---
clang/lib/CodeGen/CGExpr.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index e53b4be55926a..57cab39c133c6 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1064,8 +1064,7 @@ namespace {
/// or through a chain of nested record fields (including anonymous
/// structs/unions). This mirrors the GEP path that getGEPIndicesToField builds,
/// and is used to identify the right anchor expression in Base.
-static bool recordContainsField(const RecordDecl *RD,
- const FieldDecl *Field) {
+static bool recordContainsField(const RecordDecl *RD, const FieldDecl *Field) {
for (const FieldDecl *FD : RD->fields()) {
if (FD == Field)
return true;
More information about the cfe-commits
mailing list