https://github.com/isanbard created 
https://github.com/llvm/llvm-project/pull/232223

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.


>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] [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");
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to