https://github.com/isanbard updated https://github.com/llvm/llvm-project/pull/232223
>From 0061e86004d1bf4529cdf027c83fa89cfae52864 Mon Sep 17 00:00:00 2001 From: Bill Wendling <[email protected]> Date: Wed, 14 Oct 2026 11:52:45 -0700 Subject: [PATCH 1/2] [Clang][counted_by][NFC] Add missing tests for counted_by and __builtin_counted_by_ref Add Sema coverage for __builtin_counted_by_ref: - a bit-field counter on a 'counted_by' pointer (not just a FAM), - the '_or_null' and 'sized_by' variants, which return 'void *', - qualified, enum, and typedef counter types, which determine the returned pointee type, - nested, anonymous, by-value, and subscripted bases. Add CodeGen coverage for the base-expression shapes reaching a counted_by flexible array member, for both __builtin_dynamic_object_size and -fsanitize=array-bounds: call and side-effecting bases (which __bdos must not re-evaluate), pointer chains, conditional bases, negative constant indices, enum counters, and mixed index widths. Assisted-by: Claude Opus 5.5 --- .../test/CodeGen/attr-counted-by-base-exprs.c | 306 ++++++++++++++++++ .../CodeGen/attr-counted-by-union-storage.c | 21 ++ clang/test/Sema/builtin-counted-by-ref.c | 105 ++++++ 3 files changed, 432 insertions(+) create mode 100644 clang/test/CodeGen/attr-counted-by-base-exprs.c diff --git a/clang/test/CodeGen/attr-counted-by-base-exprs.c b/clang/test/CodeGen/attr-counted-by-base-exprs.c new file mode 100644 index 00000000000000..403b9de7a0b3ad --- /dev/null +++ b/clang/test/CodeGen/attr-counted-by-base-exprs.c @@ -0,0 +1,306 @@ +// 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 -O2 -Wno-missing-declarations -fsanitize=array-bounds -fsanitize-trap=array-bounds -emit-llvm -o - %s | FileCheck %s + +// Tests the different shapes of base expression that can be used to reach a +// 'counted_by' flexible array member or pointer, for both +// __builtin_dynamic_object_size and -fsanitize=array-bounds. +// +// The main properties being checked are: +// - a base with side-effects is never re-evaluated to load the count. The +// __bdos of such an expression falls back to the default (-1), while the +// bounds check reuses the already-evaluated base. +// - bases that are reachable without side-effects (nested pointers, array +// subscripts, explicit dereferences) load the count from the right struct. +// - a provably out-of-range constant index folds to zero. + +#define __bdos(P) __builtin_dynamic_object_size(P, 1) + +struct s { + int count; + int array[] __attribute__((counted_by(count))); +}; + +struct wrap { + long tag; + struct s *s; +}; + +enum count_enum { CE_ZERO, CE_ONE }; + +struct e { + enum count_enum count; + int array[] __attribute__((counted_by(count))); +}; + +struct s *get_s(void); + +// A call has side-effects, so __bdos can't use it to load the count. +// CHECK-LABEL: define dso_local noundef i64 @test_bdos_call_base( +// CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: ret i64 -1 +// +unsigned long test_bdos_call_base(void) { + return __bdos(get_s()->array); +} + +// The bounds check must reuse the call's result rather than calling again. +// CHECK-LABEL: define dso_local i32 @test_index_call_base( +// CHECK-SAME: i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[CALL:%.*]] = tail call ptr @get_s() #[[ATTR8:[0-9]+]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[CALL]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = icmp ult i32 [[I]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6:![0-9]+]] +// CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = and i1 [[TMP1]], [[TMP0]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP2]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7:![0-9]+]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9:[0-9]+]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[CALL]], i64 4 +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8:![0-9]+]] +// CHECK-NEXT: ret i32 [[TMP3]] +// +int test_index_call_base(int i) { + return get_s()->array[i]; +} + +// CHECK-LABEL: define dso_local noundef i64 @test_bdos_side_effect_base( +// CHECK-SAME: ptr nofree noundef readnone captures(none) [[PP:%.*]]) local_unnamed_addr #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: ret i64 -1 +// +unsigned long test_bdos_side_effect_base(struct s **pp) { + return __bdos((*pp++)->array); +} + +// CHECK-LABEL: define dso_local i32 @test_index_side_effect_base( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[PP:%.*]], i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR4:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[PP]], align 8, !tbaa [[_ZTS1SPTR_TBAA9:![0-9]+]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[TMP0]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = icmp ult i32 [[I]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP3:%.*]] = and i1 [[TMP2]], [[TMP1]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP3]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0]], i64 4 +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP4]] +// +int test_index_side_effect_base(struct s **pp, int i) { + return (*pp++)->array[i]; +} + +// CHECK-LABEL: define dso_local range(i64 -8589934592, 8589934589) i64 @test_bdos_array_of_ptrs( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[PP:%.*]], i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR5:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [8 x i8], ptr [[PP]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[ARRAYIDX]], align 8, !tbaa [[_ZTS1SPTR_TBAA9]] +// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[TMP0]], 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: [[TMP1:%.*]] = icmp sgt i32 [[COUNTED_BY_LOAD]], -1 +// CHECK-NEXT: [[TMP2:%.*]] = select i1 [[TMP1]], i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0 +// CHECK-NEXT: ret i64 [[TMP2]] +// +unsigned long test_bdos_array_of_ptrs(struct s **pp, int i) { + return __bdos(pp[i]->array); +} + +// CHECK-LABEL: define dso_local range(i64 -8589934592, 8589934589) i64 @test_bdos_through_ptr_member( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[W:%.*]]) local_unnamed_addr #[[ATTR5]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[S:%.*]] = getelementptr inbounds nuw i8, ptr [[W]], i64 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[S]], align 8, !tbaa [[_ZTS1SPTR_TBAA12:![0-9]+]] +// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[TMP0]], 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: [[TMP1:%.*]] = icmp sgt i32 [[COUNTED_BY_LOAD]], -1 +// CHECK-NEXT: [[TMP2:%.*]] = select i1 [[TMP1]], i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0 +// CHECK-NEXT: ret i64 [[TMP2]] +// +unsigned long test_bdos_through_ptr_member(struct wrap *w) { + return __bdos(w->s->array); +} + +// CHECK-LABEL: define dso_local i32 @test_index_through_ptr_member( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[W:%.*]], i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR4]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[S:%.*]] = getelementptr inbounds nuw i8, ptr [[W]], i64 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[S]], align 8, !tbaa [[_ZTS1SPTR_TBAA12]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[TMP0]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = icmp ult i32 [[I]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP3:%.*]] = and i1 [[TMP2]], [[TMP1]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP3]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0]], i64 4 +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP4]] +// +int test_index_through_ptr_member(struct wrap *w, int i) { + return w->s->array[i]; +} + +// CHECK-LABEL: define dso_local range(i64 -8589934592, 8589934589) i64 @test_bdos_explicit_deref( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]]) local_unnamed_addr #[[ATTR6:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[A]], 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: ret i64 [[TMP1]] +// +unsigned long test_bdos_explicit_deref(struct s *a) { + return __bdos((*a).array); +} + +// CHECK-LABEL: define dso_local i64 @test_bdos_conditional_base( +// CHECK-SAME: ptr nofree noundef readnone [[A:%.*]], ptr nofree noundef readnone [[B:%.*]], i32 noundef [[C:%.*]]) local_unnamed_addr #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: ret i64 -1 +// +unsigned long test_bdos_conditional_base(struct s *a, struct s *b, int c) { + return __bdos((c ? a : b)->array); +} + +// CHECK-LABEL: define dso_local i32 @test_index_conditional_base( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]], ptr nofree noundef readonly captures(none) [[B:%.*]], i32 noundef [[C:%.*]], i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR4]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[C]], 0 +// CHECK-NEXT: [[COND:%.*]] = select i1 [[TOBOOL_NOT]], ptr [[B]], ptr [[A]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[COND]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = icmp ult i32 [[I]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]] +// CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = and i1 [[TMP1]], [[TMP0]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP2]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[COND]], i64 4 +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP3]] +// +int test_index_conditional_base(struct s *a, struct s *b, int c, int i) { + return (c ? a : b)->array[i]; +} + +// CHECK-LABEL: define dso_local noundef i64 @test_bdos_negative_const_index( +// CHECK-SAME: ptr nofree noundef readnone captures(none) [[A:%.*]]) local_unnamed_addr #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: ret i64 0 +// +unsigned long test_bdos_negative_const_index(struct s *a) { + return __bdos(&a->array[-1]); +} + +// CHECK-LABEL: define dso_local range(i64 0, 17179869181) i64 @test_bdos_enum_count( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]]) local_unnamed_addr #[[ATTR6]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: [[COUNT:%.*]] = zext i32 [[COUNTED_BY_LOAD]] to i64 +// CHECK-NEXT: [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = shl nuw nsw i64 [[COUNT]], 2 +// CHECK-NEXT: ret i64 [[FLEXIBLE_ARRAY_MEMBER_SIZE]] +// +unsigned long test_bdos_enum_count(struct e *a) { + return __bdos(a->array); +} + +// CHECK-LABEL: define dso_local i32 @test_index_enum_count( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]], i32 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR7:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = icmp ult i32 [[I]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP0]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4 +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i32 [[I]] to i64 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP1]] +// +int test_index_enum_count(struct e *a, int i) { + return a->array[i]; +} + +// Index types narrower and wider than the count. +// CHECK-LABEL: define dso_local i32 @test_index_char( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]], i8 noundef signext [[I:%.*]]) local_unnamed_addr #[[ATTR7]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = sext i8 [[I]] to i32 +// CHECK-NEXT: [[TMP1:%.*]] = icmp ugt i32 [[DOTCOUNTED_BY_LOAD]], [[TMP0]], !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP3:%.*]] = and i1 [[TMP2]], [[TMP1]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP3]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[IDXPROM:%.*]] = sext i8 [[I]] to i64 +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARRAY]], i64 [[IDXPROM]] +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP4]] +// +int test_index_char(struct s *a, signed char i) { + return a->array[i]; +} + +// CHECK-LABEL: define dso_local i32 @test_index_unsigned_long( +// CHECK-SAME: ptr nofree noundef readonly captures(none) [[A:%.*]], i64 noundef [[I:%.*]]) local_unnamed_addr #[[ATTR7]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = zext i32 [[DOTCOUNTED_BY_LOAD]] to i64, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP1:%.*]] = icmp ult i64 [[I]], [[TMP0]], !nosanitize [[META6]] +// CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]] +// CHECK-NEXT: [[TMP3:%.*]] = and i1 [[TMP2]], [[TMP1]], !nosanitize [[META6]] +// CHECK-NEXT: br i1 [[TMP3]], label %[[CONT:.*]], label %[[TRAP:.*]], !prof [[PROF7]], !nosanitize [[META6]] +// CHECK: [[TRAP]]: +// CHECK-NEXT: tail call void @llvm.ubsantrap(i8 18) #[[ATTR9]], !nosanitize [[META6]] +// CHECK-NEXT: unreachable, !nosanitize [[META6]] +// CHECK: [[CONT]]: +// CHECK-NEXT: [[ARRAY:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 4 +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARRAY]], i64 [[I]] +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[INT_TBAA8]] +// CHECK-NEXT: ret i32 [[TMP4]] +// +int test_index_unsigned_long(struct s *a, unsigned long i) { + return a->array[i]; +} +//. +// CHECK: [[META3:![0-9]+]] = !{!"int", [[META4:![0-9]+]], i64 0} +// CHECK: [[META4]] = !{!"omnipotent char", [[META5:![0-9]+]], i64 0} +// CHECK: [[META5]] = !{!"Simple C/C++ TBAA"} +// CHECK: [[META6]] = !{} +// CHECK: [[PROF7]] = !{!"branch_weights", i32 1048575, i32 1} +// CHECK: [[INT_TBAA8]] = !{[[META3]], [[META3]], i64 0} +// CHECK: [[_ZTS1SPTR_TBAA9]] = !{[[META10:![0-9]+]], [[META10]], i64 0} +// CHECK: [[META10]] = !{!"p1 _ZTS1s", [[META11:![0-9]+]], i64 0} +// CHECK: [[META11]] = !{!"any pointer", [[META4]], i64 0} +// CHECK: [[_ZTS1SPTR_TBAA12]] = !{[[META13:![0-9]+]], [[META10]], i64 8} +// CHECK: [[META13]] = !{!"wrap", [[META14:![0-9]+]], i64 0, [[META10]], i64 8} +// CHECK: [[META14]] = !{!"long", [[META4]], i64 0} +//. diff --git a/clang/test/CodeGen/attr-counted-by-union-storage.c b/clang/test/CodeGen/attr-counted-by-union-storage.c index b05f3863dce6f9..b777370334e17a 100644 --- a/clang/test/CodeGen/attr-counted-by-union-storage.c +++ b/clang/test/CodeGen/attr-counted-by-union-storage.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fsanitize=array-bounds -fsanitize-trap=array-bounds -emit-llvm -o - %s | FileCheck %s --check-prefix=SAN // The union's IR type is struct work, so num_layers has to be reached by byte // offset rather than through struct work's field indices. @@ -33,6 +34,26 @@ unsigned long test_bdos(struct domain *d) { return __builtin_dynamic_object_size(d->masks, 1); } +// __builtin_counted_by_ref shares the GEP with __builtin_dynamic_object_size. +// Walking struct work's field indices would store into work_free.entry.next +// (offset 40), corrupting a pointer, instead of into num_layers (offset 36). +// CHECK-LABEL: define {{.*}} @test_counted_by_ref( +// CHECK: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr %d, i64 36 +// CHECK: store i32 %n, ptr [[GEP]] +void test_counted_by_ref(struct domain *d, unsigned int n) { + *__builtin_counted_by_ref(d->masks) = n; +} + +// The -fsanitize=array-bounds check must load its bound from num_layers too. +// SAN-LABEL: define {{.*}} @test_index( +// SAN: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr %d, i64 36 +// SAN: [[COUNT:%.*]] = load i32, ptr [[GEP]] +// SAN: icmp ult i32 %i, [[COUNT]] +// SAN: call void @llvm.ubsantrap( +unsigned int test_index(struct domain *d, int i) { + return d->masks[i]; +} + // A bit-field count can't be loaded by byte offset, so the size is unknown. struct bitfield_count { unsigned int pad : 20; diff --git a/clang/test/Sema/builtin-counted-by-ref.c b/clang/test/Sema/builtin-counted-by-ref.c index 7c039a8d0349dd..88468b6a8d1d79 100644 --- a/clang/test/Sema/builtin-counted-by-ref.c +++ b/clang/test/Sema/builtin-counted-by-ref.c @@ -194,3 +194,108 @@ struct bitfield_counter { void test_bitfield_counter(struct bitfield_counter *p) { __builtin_counted_by_ref(p->array); // expected-error {{'__builtin_counted_by_ref' cannot be used on 'p->array' because its 'counted_by' attribute refers to bit-field 'count'}} } + +// The bit-field restriction also applies to 'counted_by' pointers, not just +// flexible array members, and to a bit-field counter that isn't at the start of +// its storage unit. +struct bitfield_counter_ptr { + unsigned flags : 3; + unsigned count : 13; // expected-note {{bit-field 'count' declared here}} + int *ptr __attribute__((counted_by(count))); +}; + +void test_bitfield_counter_ptr(struct bitfield_counter_ptr *p) { + __builtin_counted_by_ref(p->ptr); // expected-error {{'__builtin_counted_by_ref' cannot be used on 'p->ptr' because its 'counted_by' attribute refers to bit-field 'count'}} +} + +// Only 'counted_by' is supported. The '_or_null' and 'sized_by' variants return +// 'void *', the same as a member without any attribute, so that the _Generic +// idiom from the documentation still compiles for them. +struct counted_by_or_null_ptr { + int count; + int *ptr __attribute__((counted_by_or_null(count))); +}; + +struct sized_by_ptr { + int count; + char *ptr __attribute__((sized_by(count))); +}; + +struct sized_by_or_null_ptr { + int count; + char *ptr __attribute__((sized_by_or_null(count))); +}; + +void test12(struct counted_by_or_null_ptr *a, struct sized_by_ptr *b, + struct sized_by_or_null_ptr *c) { + _Static_assert(_Generic(__builtin_counted_by_ref(a->ptr), void * : 1, default : 0) == 1, "should be void*"); + _Static_assert(_Generic(__builtin_counted_by_ref(b->ptr), void * : 1, default : 0) == 1, "should be void*"); + _Static_assert(_Generic(__builtin_counted_by_ref(c->ptr), void * : 1, default : 0) == 1, "should be void*"); +} + +// The returned pointer's pointee type is the counter's declared type, including +// qualifiers, enums, and typedefs. +enum count_enum { CE_ZERO, CE_ONE }; +typedef unsigned short count_t; + +struct volatile_count { + volatile int count; + int array[] __attribute__((counted_by(count))); +}; + +struct const_count { + const int count; + int array[] __attribute__((counted_by(count))); +}; + +struct enum_count { + enum count_enum count; + int array[] __attribute__((counted_by(count))); +}; + +struct typedef_count { + count_t count; + int array[] __attribute__((counted_by(count))); +}; + +void test13(struct volatile_count *vp, struct const_count *cp, + struct enum_count *ep, struct typedef_count *tp) { + _Static_assert(_Generic(__builtin_counted_by_ref(vp->array), volatile int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(cp->array), const int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(ep->array), enum count_enum * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(tp->array), count_t * : 1, default : 0) == 1, "wrong return type"); + + *__builtin_counted_by_ref(vp->array) = 42; // ok + *__builtin_counted_by_ref(cp->array) = 42; // expected-error {{read-only variable is not assignable}} +} + +// Various ways of naming the flexible array member, including through nested +// and anonymous structs. +struct nested_inner { + int count; + int array[] __attribute__((counted_by(count))); +}; + +struct nested_outer { + long tag; + struct nested_inner inner; +}; + +struct anon_count { + long tag; + struct { + int pad; + short count; + }; + int array[] __attribute__((counted_by(count))); +}; + +void test14(struct nested_inner *p, struct nested_inner s, + struct nested_outer *o, struct anon_count *a) { + _Static_assert(_Generic(__builtin_counted_by_ref((*p).array), int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(p[1].array), int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref((p)->array), int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(s.array), int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(o->inner.array), int * : 1, default : 0) == 1, "wrong return type"); + _Static_assert(_Generic(__builtin_counted_by_ref(a->array), short * : 1, default : 0) == 1, "wrong return type"); +} >From 1bc9e5c08b807642982067a7d92b70d71911118a Mon Sep 17 00:00:00 2001 From: Bill Wendling <[email protected]> Date: Thu, 15 Oct 2026 01:26:59 -0700 Subject: [PATCH 2/2] Rename test functions for clarity and consistency --- clang/test/Sema/builtin-counted-by-ref.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/test/Sema/builtin-counted-by-ref.c b/clang/test/Sema/builtin-counted-by-ref.c index 88468b6a8d1d79..98fa892a210ae9 100644 --- a/clang/test/Sema/builtin-counted-by-ref.c +++ b/clang/test/Sema/builtin-counted-by-ref.c @@ -226,8 +226,8 @@ struct sized_by_or_null_ptr { char *ptr __attribute__((sized_by_or_null(count))); }; -void test12(struct counted_by_or_null_ptr *a, struct sized_by_ptr *b, - struct sized_by_or_null_ptr *c) { +void test_or_null_kinds(struct counted_by_or_null_ptr *a, struct sized_by_ptr *b, + struct sized_by_or_null_ptr *c) { _Static_assert(_Generic(__builtin_counted_by_ref(a->ptr), void * : 1, default : 0) == 1, "should be void*"); _Static_assert(_Generic(__builtin_counted_by_ref(b->ptr), void * : 1, default : 0) == 1, "should be void*"); _Static_assert(_Generic(__builtin_counted_by_ref(c->ptr), void * : 1, default : 0) == 1, "should be void*"); @@ -258,8 +258,8 @@ struct typedef_count { int array[] __attribute__((counted_by(count))); }; -void test13(struct volatile_count *vp, struct const_count *cp, - struct enum_count *ep, struct typedef_count *tp) { +void test_qualified_enum_typedef(struct volatile_count *vp, struct const_count *cp, + struct enum_count *ep, struct typedef_count *tp) { _Static_assert(_Generic(__builtin_counted_by_ref(vp->array), volatile int * : 1, default : 0) == 1, "wrong return type"); _Static_assert(_Generic(__builtin_counted_by_ref(cp->array), const int * : 1, default : 0) == 1, "wrong return type"); _Static_assert(_Generic(__builtin_counted_by_ref(ep->array), enum count_enum * : 1, default : 0) == 1, "wrong return type"); @@ -290,8 +290,8 @@ struct anon_count { int array[] __attribute__((counted_by(count))); }; -void test14(struct nested_inner *p, struct nested_inner s, - struct nested_outer *o, struct anon_count *a) { +void test_nested_anonymous_by_value_subscripted(struct nested_inner *p, struct nested_inner s, + struct nested_outer *o, struct anon_count *a) { _Static_assert(_Generic(__builtin_counted_by_ref((*p).array), int * : 1, default : 0) == 1, "wrong return type"); _Static_assert(_Generic(__builtin_counted_by_ref(p[1].array), int * : 1, default : 0) == 1, "wrong return type"); _Static_assert(_Generic(__builtin_counted_by_ref((p)->array), int * : 1, default : 0) == 1, "wrong return type"); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
