https://github.com/kees created https://github.com/llvm/llvm-project/pull/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 >From 66caaf13cf51c6002b6f37132cbceab86008092c Mon Sep 17 00:00:00 2001 From: Kees Cook <[email protected]> Date: Fri, 20 Feb 2026 12:01:08 -0800 Subject: [PATCH] [Clang][CodeGen] Fix __builtin_counted_by_ref for nested struct FAMs (#182575) 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 <[email protected]> --- 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 7fee9a1122a7d..c67b6e992da16 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -1192,7 +1192,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; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
