[Clang][counted_by] Use the expr's RecordDecl that transitively contains the counter field (#205903)
The counted field could be in nested structs. This can happen when the
Expr is pointing to a field that has a struct type. The original code
didn't do a good job at finding the best RecordDecl to build the GEP off
of. It made too many assumptions about how structs' layouts.
This rewrite finds the RecordDecl that transitively contains the
__counted_by__'s counter field.
Fixes: #205652
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6abe433..6a0d127 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1058,6 +1058,22 @@
return nullptr;
}
+/// 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;
+}
+
namespace {
/// \p StructAccessBase returns the base \p Expr of a field access. It returns
@@ -1078,17 +1094,24 @@
/// \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,24 +1223,23 @@
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.
- 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);
+ // 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;
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 0000000..9f715ad
--- /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
+// 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]], 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
+// 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
+// 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_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
+// 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_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
+// 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_quad_nested(struct quad_nested *p) {
+ return __bdos(&p->buf);
+}