[llvm-branch-commits] [clang] [BoundsSafety] Support the counted_by family on array parameters (PR #234950)

Mohammed Ashraf via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Oct 23 06:55:36 PDT 2026


https://github.com/Holo-xy created https://github.com/llvm/llvm-project/pull/234950

None

>From 12d47d306fadfa8e68590bdfa49c58d758ec2707 Mon Sep 17 00:00:00 2001
From: Holo-xy <mohammedashraf4599 at gmail.com>
Date: Fri, 23 Oct 2026 16:01:05 +0300
Subject: [PATCH 1/2] [BoundsSafety] Support the counted_by family on function
 parameters

Allow counted_by, sized_by and their _or_null variants on function
parameters. The count is parsed where it is written, so it names a
parameter declared before the annotated one.

  - The count must name a parameter of the function, or of an enclosing
    function declarator.
  - Counts on K&R parameter declarations are dropped.
  - Objective-C method parameters are checked the same way.
  - Array parameters, out parameters and dereferenced counts are not
    supported yet.

The checks that the count is an integer and a plain reference to a
declaration are shared with fields.

CodeGen does not use parameter counts yet: they bound neither
-fsanitize=array-bounds nor __builtin_dynamic_object_size.
---
 clang/include/clang/Basic/Attr.td             |  8 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/include/clang/Sema/Sema.h               |  7 ++
 clang/lib/Sema/SemaBoundsSafety.cpp           | 64 +++++++++++----
 clang/lib/Sema/SemaType.cpp                   | 15 ++++
 .../AST/attr-counted-by-function-params.c     | 21 +++++
 .../CodeGen/attr-counted-by-function-params.c | 25 ++++++
 .../Sema/attr-counted-by-function-params.c    | 80 +++++++++++++++++++
 .../attr-counted-by-late-parsed-regressions.c |  7 +-
 .../SemaObjC/attr-counted-by-method-params.m  | 15 ++++
 10 files changed, 220 insertions(+), 24 deletions(-)
 create mode 100644 clang/test/AST/attr-counted-by-function-params.c
 create mode 100644 clang/test/CodeGen/attr-counted-by-function-params.c
 create mode 100644 clang/test/Sema/attr-counted-by-function-params.c
 create mode 100644 clang/test/SemaObjC/attr-counted-by-method-params.m

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f8bd097733529a..34ded4724e5bcc 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2712,7 +2712,7 @@ def TypeNullUnspecified : TypeAttr {
 
 def CountedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"counted_by">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2722,7 +2722,7 @@ def CountedBy : DeclOrTypeAttr {
 
 def CountedByOrNull : DeclOrTypeAttr {
   let Spellings = [Clang<"counted_by_or_null">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2745,7 +2745,7 @@ def PointerFieldProtection : DeclOrTypeAttr {
 
 def SizedBy : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
@@ -2755,7 +2755,7 @@ def SizedBy : DeclOrTypeAttr {
 
 def SizedByOrNull : DeclOrTypeAttr {
   let Spellings = [Clang<"sized_by_or_null">];
-  let Subjects = SubjectList<[Field], ErrorDiag>;
+  let Subjects = SubjectList<[Field, ParmVar], ErrorDiag>;
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b67706a2a6c6a9..4dc03f4170507b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7210,6 +7210,8 @@ def err_counted_by_attr_refer_to_itself : Error<
   "'counted_by' cannot refer to the flexible array member %0">;
 def err_count_attr_must_be_in_structure : Error<
   "field %0 in '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside structure">;
+def err_count_attr_refer_to_non_param : Error<
+  "count expression in function declaration may only reference function parameters">;
 def err_count_attr_argument_not_integer : Error<
   "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' requires a non-boolean integer type argument">;
 def err_count_attr_only_support_simple_decl_reference : Error<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 93cfbe2e5420d5..e3c0045a885f0d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2637,6 +2637,13 @@ class Sema final : public SemaBase {
   bool ActOnLateParsedTypeAttrArgument(BoundsAttributedType *BATy,
                                        FieldDecl *FD, Expr *Arg);
 
+  /// Check that \p E is a valid count for a counted_by-family attribute on
+  /// \p ParamTy, the type of a parameter.
+  ///
+  /// \returns false iff semantically valid.
+  bool CheckCountedByAttrOnParam(QualType ParamTy, Expr *E, bool CountInBytes,
+                                 bool OrNull);
+
   /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
   /// `__counted_by_or_null` pointer type \param LHSTy.
   ///
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index c4477c228a08ac..719a3dd2d2339a 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -271,6 +271,26 @@ bool Sema::ValidateBoundsAttrTypeForTypePosition(
   return true;
 }
 
+/// The checks shared by the counts of fields and parameters: \p E must be a
+/// non-boolean integer and a plain reference to a declaration. Returns that
+/// declaration, or null after diagnosing.
+static ValueDecl *checkCountRefersToDecl(Sema &S, Expr *E, unsigned Kind) {
+  if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
+    S.Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
+        << Kind << E->getSourceRange();
+    return nullptr;
+  }
+
+  auto *DRE = dyn_cast<DeclRefExpr>(E);
+  if (!DRE) {
+    S.Diag(E->getBeginLoc(),
+           diag::err_count_attr_only_support_simple_decl_reference)
+        << Kind << E->getSourceRange();
+    return nullptr;
+  }
+  return DRE->getDecl();
+}
+
 bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
                                      bool OrNull) {
   // Check the context the attribute is used in
@@ -295,23 +315,11 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
     return true;
   }
 
-  // Validate the expression type
-  if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) {
-    Diag(E->getBeginLoc(), diag::err_count_attr_argument_not_integer)
-        << Kind << E->getSourceRange();
+  ValueDecl *CountDecl = checkCountRefersToDecl(*this, E, Kind);
+  if (!CountDecl)
     return true;
-  }
-
-  auto *DRE = dyn_cast<DeclRefExpr>(E);
-  if (!DRE) {
-    Diag(E->getBeginLoc(),
-         diag::err_count_attr_only_support_simple_decl_reference)
-        << Kind << E->getSourceRange();
-    return true;
-  }
 
   // Validate count field references
-  auto *CountDecl = DRE->getDecl();
   FieldDecl *CountFD = dyn_cast<FieldDecl>(CountDecl);
   if (auto *IFD = dyn_cast<IndirectFieldDecl>(CountDecl)) {
     CountFD = IFD->getAnonField();
@@ -350,6 +358,34 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
   return false;
 }
 
+bool Sema::CheckCountedByAttrOnParam(QualType ParamTy, Expr *E,
+                                     bool CountInBytes, bool OrNull) {
+  // An invalid count was already diagnosed.
+  if (E->containsErrors())
+    return true;
+
+  // FIXME: Support array parameters. The count has to move to the pointer
+  // that the array decays to.
+  if (ParamTy->isArrayType()) {
+    Diag(E->getBeginLoc(),
+         diag::err_counted_by_attr_on_array_not_flexible_array_member);
+    return true;
+  }
+
+  const unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+  ValueDecl *CountDecl = checkCountRefersToDecl(*this, E, Kind);
+  if (!CountDecl)
+    return true;
+
+  // The parameters of an enclosing function declarator are in scope too.
+  if (!isa<ParmVarDecl>(CountDecl)) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_refer_to_non_param)
+        << E->getSourceRange();
+    return true;
+  }
+  return false;
+}
+
 static void EmitIncompleteCountedByPointeeNotes(Sema &S,
                                                 const CountAttributedType *CATy,
                                                 NamedDecl *IncompleteTyDecl) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 153be2119a1500..c3faae9733a108 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9149,7 +9149,12 @@ static void HandleCountedByAttrOnType(TypeProcessingState &State,
   if (!CountExpr)
     return;
 
+  // Counts are not supported on K&R parameter declarations, which C23
+  // removed; drop the count.
   const Declarator &D = State.getDeclarator();
+  if (D.getContext() == DeclaratorContext::KNRTypeList)
+    return;
+
   unsigned chunkIndex = State.getCurrentChunkIndex();
 
   Sema::BoundsAttrFlags Flags;
@@ -9161,6 +9166,16 @@ static void HandleCountedByAttrOnType(TypeProcessingState &State,
     return;
   }
 
+  // A parameter's count is checked before the type is built, and dropped if
+  // invalid.
+  if ((D.getContext() == DeclaratorContext::Prototype ||
+       D.getContext() == DeclaratorContext::ObjCParameter) &&
+      S.CheckCountedByAttrOnParam(CurType, CountExpr, Flags.CountInBytes,
+                                  Flags.OrNull)) {
+    Attr.setInvalid();
+    return;
+  }
+
   QualType NewType = S.BuildCountAttributedArrayOrPointerType(
       CurType, CountExpr, Flags.CountInBytes, Flags.OrNull);
   if (NewType.isNull()) {
diff --git a/clang/test/AST/attr-counted-by-function-params.c b/clang/test/AST/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..afffe70149d0e5
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-function-params.c
@@ -0,0 +1,21 @@
+// RUN: %clang_cc1 -verify %s -ast-dump | FileCheck %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -verify %s -ast-dump | FileCheck %s
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+int global_count;
+
+// CHECK: FunctionDecl {{.*}} back_ref 'void (int, int * __counted_by(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
+void back_ref(int count, int *__counted_by(count) buf);
+
+// A rejected count is not applied, and the function stays valid.
+// CHECK: FunctionDecl {{.*}} not_a_param 'void (int *)'
+// expected-error at +1{{count expression in function declaration may only reference function parameters}}
+void not_a_param(int *__counted_by(global_count) buf);
+
+// A count on a K&R parameter declaration is dropped.
+// CHECK: FunctionDecl {{.*}} knr 'void (int, int *)'
+// expected-warning at +1{{a function definition without a prototype is deprecated}}
+void knr(count, buf) int count; int *__counted_by(count) buf; {}
diff --git a/clang/test/CodeGen/attr-counted-by-function-params.c b/clang/test/CodeGen/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..bab6d00b376864
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-function-params.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=array-bounds -emit-llvm -o - %s | FileCheck %s
+
+// CodeGen does not use a function parameter's count yet: unlike a field's
+// count, it bounds neither -fsanitize=array-bounds nor
+// __builtin_dynamic_object_size.
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+// CHECK-LABEL: define dso_local i32 @subscript(
+// CHECK-NOT:     __ubsan_handle_out_of_bounds
+// CHECK:         ret i32
+//
+// FIXME: Check the index against the count.
+int subscript(int count, int *__counted_by(count) buf, int i) {
+  return buf[i];
+}
+
+// CHECK-LABEL: define dso_local i64 @bdos(
+// CHECK-NOT:     counted_by.load
+// CHECK:         call i64 @llvm.objectsize.i64.p0(
+//
+// FIXME: Compute the object size from the count.
+unsigned long bdos(int count, int *__counted_by(count) buf) {
+  return __builtin_dynamic_object_size(buf, 0);
+}
diff --git a/clang/test/Sema/attr-counted-by-function-params.c b/clang/test/Sema/attr-counted-by-function-params.c
new file mode 100644
index 00000000000000..c4c5661bd2633e
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-function-params.c
@@ -0,0 +1,80 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
+
+// The counted_by family on a function parameter. The count is parsed where it
+// is written, also with late parsing, so it names a parameter declared before
+// the annotated one.
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+#define __counted_by_or_null(f)  __attribute__((counted_by_or_null(f)))
+#define __sized_by(f)  __attribute__((sized_by(f)))
+#define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
+
+typedef int *int_ptr;
+int global_count;
+
+//==============================================================================
+// Valid: the count is declared before the annotated pointer
+//==============================================================================
+
+void back_ref(int count, int *__counted_by(count) buf);
+void back_ref_or_null(int count, int *__counted_by_or_null(count) buf);
+void back_ref_sized(int count, void *__sized_by(count) buf);
+void back_ref_sized_or_null(int count, void *__sized_by_or_null(count) buf);
+
+// Two pointers may share a count, but a pointer has only one.
+void two_buffers(int count, int *__counted_by(count) a,
+                 int *__counted_by(count) b);
+// expected-error at +1{{'counted_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+void repeated(int n, int m, int *buf __counted_by(n) __counted_by(m));
+
+// Written after the declarator, or in the declaration specifiers.
+void trailing(int count, int *buf __counted_by(count));
+void declspec(int count, int_ptr __counted_by(count) buf);
+
+// The C23 spelling is checked the same way.
+void c23_spelling(int count, int *[[clang::counted_by(count)]] buf);
+
+int sum(int count, int *__counted_by(count) buf) {
+  int total = 0;
+  for (int i = 0; i < count; ++i)
+    total += buf[i];
+  return total;
+}
+
+//==============================================================================
+// The count is parsed where it is written
+//==============================================================================
+
+// expected-error at +1{{use of undeclared identifier 'count'}}
+void fwd_ref(int *__counted_by(count) buf, int count);
+
+//==============================================================================
+// What the count may name
+//==============================================================================
+
+// A parameter of the function, or of an enclosing function declarator.
+void inner(void (*cb)(int len, int *__counted_by(len) p));
+void outer(int n, void (*cb)(int *__counted_by(n) p));
+
+// expected-error at +1{{count expression in function declaration may only reference function parameters}}
+void not_a_param(int *__counted_by(global_count) buf);
+// expected-error at +1{{'counted_by' requires a non-boolean integer type argument}}
+void not_an_integer(float count, int *__counted_by(count) buf);
+// expected-error at +1{{'counted_by' argument must be a simple declaration reference}}
+void not_a_reference(int count, int *__counted_by(count + 1) buf);
+
+//==============================================================================
+// Not supported yet
+//==============================================================================
+
+// expected-error at +1{{'counted_by' on arrays only applies to C99 flexible array members}}
+void array(int count, int arr[] __counted_by(count));
+
+// expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
+void out(int count, int *__counted_by(count) *buf);
+// expected-error at +1{{'counted_by' argument must be a simple declaration reference}}
+void deref(int *count, int *__counted_by(*count) buf);
+
+// expected-error at +1{{'counted_by' attribute cannot be applied to a function return type}}
+void cb_return(int len, int *__counted_by(len) (*cb)(void));
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
index 3a50bca54e63a6..c7989cf5b87ae5 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed-regressions.c
@@ -30,12 +30,7 @@ struct nested_record_declspec_attr {
 // rather than silently building a null-count type.
 void fwd_ref_param(int *__counted_by(n) p, // expected-error {{use of undeclared identifier 'n'}}
                    int n);
-
-// FIXME: counted_by on a function parameter isn't supported yet; the eager
-// decl-attribute path rejects it. What matters for this regression is that it
-// is diagnosed here, not left as an unfinished type for a later crash.
-void bwd_ref_param(int n,
-                   int *__counted_by(n) p); // expected-error {{'counted_by' attribute only applies to non-static data members}}
+void bwd_ref_param(int n, int *__counted_by(n) p);
 
 // A declaration-specifier-position attribute is shared by every declarator in
 // the declaration, and ConvertDeclSpecToType walks the DeclSpec's late-attribute
diff --git a/clang/test/SemaObjC/attr-counted-by-method-params.m b/clang/test/SemaObjC/attr-counted-by-method-params.m
new file mode 100644
index 00000000000000..efccacbc27553d
--- /dev/null
+++ b/clang/test/SemaObjC/attr-counted-by-method-params.m
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define __counted_by(f)  __attribute__((counted_by(f)))
+
+int global_count;
+
+// An Objective-C method parameter's count is checked like a function
+// parameter's.
+ at interface I
+// expected-error at +1{{count expression in function declaration may only reference function parameters}}
+- (void)global:(int *__counted_by(global_count))buf;
+// The method's earlier parameters are not in scope yet.
+// expected-error at +1{{use of undeclared identifier 'n'}}
+- (void)count:(int)n buf:(int *__counted_by(n))buf;
+ at end

>From a49c54c7fc0dda2d37518791a98ff95b9524a506 Mon Sep 17 00:00:00 2001
From: Holo-xy <mohammedashraf4599 at gmail.com>
Date: Mon, 19 Oct 2026 19:40:18 +0300
Subject: [PATCH 2/2] [BoundsSafety] Support the counted_by family on array
 parameters

An array parameter decays to a pointer, so its count moves to that
pointer, also when other type attributes are written after the count. A
count that comes with the array's type through a typedef or __typeof__
belongs to another declaration and does not move.

An array with an explicit size cannot also have a count.
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/include/clang/Sema/Sema.h               |  8 +++-
 clang/lib/Sema/SemaBoundsSafety.cpp           | 34 +++++++++++----
 clang/lib/Sema/SemaDecl.cpp                   | 29 +++++++++++++
 clang/lib/Sema/SemaType.cpp                   | 22 ++++++----
 .../AST/attr-counted-by-function-params.c     | 42 +++++++++++++++++++
 .../Sema/attr-counted-by-function-params.c    | 19 ++++++++-
 7 files changed, 136 insertions(+), 20 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4dc03f4170507b..5e09741d962464 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7212,6 +7212,8 @@ def err_count_attr_must_be_in_structure : Error<
   "field %0 in '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside structure">;
 def err_count_attr_refer_to_non_param : Error<
   "count expression in function declaration may only reference function parameters">;
+def err_count_attr_on_sized_array_param : Error<
+  "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' cannot be applied to an array parameter with an explicit size">;
 def err_count_attr_argument_not_integer : Error<
   "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' requires a non-boolean integer type argument">;
 def err_count_attr_only_support_simple_decl_reference : Error<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e3c0045a885f0d..38eccfdb03c50a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2562,13 +2562,14 @@ class Sema final : public SemaBase {
   /// array declarator chunks enclosing the attribute, up to the nearest
   /// function chunk. \p OnReturnType is true if a function chunk encloses it,
   /// i.e. the attribute is in a function's return type.
+  /// \p IsParameter is set for a count in a parameter's declarator.
   ///
   /// \returns false after diagnosing an attribute that must be dropped (see
   /// ValidateBoundsAttrTypeShape).
   bool ValidateBoundsAttrTypeForTypePosition(
       QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
       SourceRange AttrRange, unsigned PointerNestLevel, bool OnReturnType,
-      BoundsAttrFlags &Flags);
+      BoundsAttrFlags &Flags, bool IsParameter);
 
   /// Check where a counted_by-family attribute of kind \p Kind sits in its
   /// declarator, as described by \p PointerNestLevel and \p OnReturnType (see
@@ -2644,6 +2645,11 @@ class Sema final : public SemaBase {
   bool CheckCountedByAttrOnParam(QualType ParamTy, Expr *E, bool CountInBytes,
                                  bool OrNull);
 
+  /// A parameter declared as an array decays to a pointer to its element type.
+  /// Move the valid count \p CATy on the array to that pointer.
+  void AdjustCountedArrayParamType(ParmVarDecl *PVD,
+                                   const CountAttributedType *CATy);
+
   /// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
   /// `__counted_by_or_null` pointer type \param LHSTy.
   ///
diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp
index 719a3dd2d2339a..642cdda280be61 100644
--- a/clang/lib/Sema/SemaBoundsSafety.cpp
+++ b/clang/lib/Sema/SemaBoundsSafety.cpp
@@ -243,10 +243,16 @@ bool Sema::ValidateBoundsAttrPosition(BoundsAttributedType::BoundsAttrKind Kind,
 bool Sema::ValidateBoundsAttrTypeForTypePosition(
     QualType Ty, AttributeCommonInfo::Kind AttrKind, SourceLocation AttrLoc,
     SourceRange AttrRange, unsigned PointerNestLevel, bool OnReturnType,
-    BoundsAttrFlags &Flags) {
+    BoundsAttrFlags &Flags, bool IsParameter) {
   Flags = BoundsAttrFlags::get(AttrKind);
 
-  if (!ValidateBoundsAttrTypeShape(Ty, AttrLoc, AttrRange, Flags,
+  // An array parameter decays to a pointer, which is what the attribute
+  // describes. Whether the array may have a size is checked on the parameter.
+  QualType ShapeTy = Ty;
+  if (IsParameter && Ty->isArrayType())
+    ShapeTy = getASTContext().getArrayDecayedType(Ty);
+
+  if (!ValidateBoundsAttrTypeShape(ShapeTy, AttrLoc, AttrRange, Flags,
                                    /*AttrSpelling=*/{}, /*AllowRedecl=*/false,
                                    /*AttrArg=*/nullptr, /*UpdateFlags=*/false))
     return false;
@@ -364,15 +370,15 @@ bool Sema::CheckCountedByAttrOnParam(QualType ParamTy, Expr *E,
   if (E->containsErrors())
     return true;
 
-  // FIXME: Support array parameters. The count has to move to the pointer
-  // that the array decays to.
-  if (ParamTy->isArrayType()) {
-    Diag(E->getBeginLoc(),
-         diag::err_counted_by_attr_on_array_not_flexible_array_member);
+  // An array parameter decays to a pointer, which the count then describes.
+  // An array with an explicit size is not supported: in the -fbounds-safety
+  // programming model, its size becomes that pointer's count.
+  const unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
+  if (ParamTy->isArrayType() && !ParamTy->isIncompleteArrayType()) {
+    Diag(E->getBeginLoc(), diag::err_count_attr_on_sized_array_param) << Kind;
     return true;
   }
 
-  const unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
   ValueDecl *CountDecl = checkCountRefersToDecl(*this, E, Kind);
   if (!CountDecl)
     return true;
@@ -386,6 +392,18 @@ bool Sema::CheckCountedByAttrOnParam(QualType ParamTy, Expr *E,
   return false;
 }
 
+void Sema::AdjustCountedArrayParamType(ParmVarDecl *PVD,
+                                       const CountAttributedType *CATy) {
+  if (!CATy->desugar()->isArrayType())
+    return;
+  // The parameter's type is already adjusted, with the qualifiers written
+  // outside the count moved to the element (C99 6.7.3p8), but the decayed
+  // pointer does not carry the count. Put it back on the pointer.
+  PVD->setType(Context.getCountAttributedType(
+      PVD->getType(), CATy->getCountExpr(), CATy->isCountInBytes(),
+      CATy->isOrNull(), CATy->getCoupledDecls()));
+}
+
 static void EmitIncompleteCountedByPointeeNotes(Sema &S,
                                                 const CountAttributedType *CATy,
                                                 NamedDecl *IncompleteTyDecl) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6db74e47f77af2..a295e24e4dbfbc 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15928,6 +15928,27 @@ static void CheckExplicitObjectParameter(Sema &S, ParmVarDecl *P,
     LSI->ExplicitObjectParameter = P;
 }
 
+/// Return the CountAttributedType at the top of \p T, looking through only
+/// parentheses, type attributes, and macro qualifiers. Return null otherwise.
+/// A count inside a typedef or `__typeof__` belongs to another declaration.
+static const CountAttributedType *getWrittenCountAttributedType(QualType T) {
+  const Type *Ty = T.getTypePtr();
+  while (true) {
+    if (const auto *CATy = dyn_cast<CountAttributedType>(Ty))
+      return CATy;
+    if (const auto *A = dyn_cast<AttributedType>(Ty))
+      Ty = A->getModifiedType().getTypePtr();
+    else if (const auto *A = dyn_cast<BTFTagAttributedType>(Ty))
+      Ty = A->getWrappedType().getTypePtr();
+    else if (const auto *P = dyn_cast<ParenType>(Ty))
+      Ty = P->getInnerType().getTypePtr();
+    else if (const auto *M = dyn_cast<MacroQualifiedType>(Ty))
+      Ty = M->getUnderlyingType().getTypePtr();
+    else
+      return nullptr;
+  }
+}
+
 Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
                                  SourceLocation ExplicitThisLoc) {
   const DeclSpec &DS = D.getDeclSpec();
@@ -16050,6 +16071,14 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
 
   ProcessDeclAttributes(S, New, D);
 
+  // A count written on an array parameter, also under other type attributes
+  // written with it, moves to the pointer the parameter decays to; it was
+  // checked where it was applied. One that comes with the array's type through
+  // a typedef or `__typeof__` belongs to another declaration and stays behind.
+  if (const CountAttributedType *CATy =
+          getWrittenCountAttributedType(TInfo->getType()))
+    AdjustCountedArrayParamType(New, CATy);
+
   if (D.getDeclSpec().isModulePrivateSpecified())
     Diag(New->getLocation(), diag::err_module_private_local)
         << 1 << New << SourceRange(D.getDeclSpec().getModulePrivateSpecLoc())
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c3faae9733a108..cbac62fcf1384b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9157,19 +9157,21 @@ static void HandleCountedByAttrOnType(TypeProcessingState &State,
 
   unsigned chunkIndex = State.getCurrentChunkIndex();
 
+  bool IsParameter = D.getContext() == DeclaratorContext::Prototype ||
+                     D.getContext() == DeclaratorContext::ObjCParameter;
+
   Sema::BoundsAttrFlags Flags;
   if (!S.ValidateBoundsAttrTypeForTypePosition(
           CurType, Attr.getKind(), Attr.getLoc(), Attr.getRange(),
           getPointerNestLevel(D, chunkIndex),
-          isOnFunctionReturnType(D, chunkIndex), Flags)) {
+          isOnFunctionReturnType(D, chunkIndex), Flags, IsParameter)) {
     Attr.setInvalid();
     return;
   }
 
   // A parameter's count is checked before the type is built, and dropped if
   // invalid.
-  if ((D.getContext() == DeclaratorContext::Prototype ||
-       D.getContext() == DeclaratorContext::ObjCParameter) &&
+  if (IsParameter &&
       S.CheckCountedByAttrOnParam(CurType, CountExpr, Flags.CountInBytes,
                                   Flags.OrNull)) {
     Attr.setInvalid();
@@ -9200,10 +9202,11 @@ bool Sema::ProcessLateParsedTypeAttr(LateParsedTypeAttrInfo &LA, QualType &QT,
   if (!LA.TypeToComplete) {
     BoundsAttrFlags Flags;
     // The position is checked per declarator below, so skip it here.
-    if (!ValidateBoundsAttrTypeForTypePosition(QT, LA.Kind, LA.NameLoc,
-                                               SourceRange(LA.NameLoc),
-                                               /*PointerNestLevel=*/0,
-                                               /*OnReturnType=*/false, Flags)) {
+    // Parameters are not late-parsed.
+    if (!ValidateBoundsAttrTypeForTypePosition(
+            QT, LA.Kind, LA.NameLoc, SourceRange(LA.NameLoc),
+            /*PointerNestLevel=*/0, /*OnReturnType=*/false, Flags,
+            /*IsParameter=*/false)) {
       LA.Invalid = true;
       return false;
     }
@@ -10267,8 +10270,9 @@ QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
                                                       Expr *CountExpr,
                                                       bool CountInBytes,
                                                       bool OrNull) {
-  // Accept any array or pointer type here. For arrays, validation that it's
-  // a flexible array member is deferred until CheckCountedByAttrOnField.
+  // Accept any array or pointer type here. For a field's array, validation that
+  // it's a flexible array member is deferred until CheckCountedByAttrOnField;
+  // a parameter's array is checked by CheckCountedByAttrOnParam.
   assert(WrappedTy->isArrayType() || WrappedTy->isPointerType());
 
   // Reject non-DeclRefExpr early to avoid cast failure in
diff --git a/clang/test/AST/attr-counted-by-function-params.c b/clang/test/AST/attr-counted-by-function-params.c
index afffe70149d0e5..019cc4d78c35e0 100644
--- a/clang/test/AST/attr-counted-by-function-params.c
+++ b/clang/test/AST/attr-counted-by-function-params.c
@@ -2,6 +2,7 @@
 // RUN: %clang_cc1 -fexperimental-late-parse-attributes -verify %s -ast-dump | FileCheck %s
 
 #define __counted_by(f)  __attribute__((counted_by(f)))
+#define __sized_by_or_null(f)  __attribute__((sized_by_or_null(f)))
 
 int global_count;
 
@@ -10,6 +11,47 @@ int global_count;
 // CHECK-NEXT: ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *'
 void back_ref(int count, int *__counted_by(count) buf);
 
+// An array parameter's count moves to the pointer it decays to.
+// CHECK: FunctionDecl {{.*}} array 'void (int, int * __sized_by_or_null(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} arr 'int * __sized_by_or_null(count)':'int *'
+void array(int count, int arr[] __sized_by_or_null(count));
+
+// The array's qualifiers apply to its element (C99 6.7.3p8), also when they are
+// outside the count, as when it is written in the declaration specifiers.
+typedef int int_array[];
+// CHECK: FunctionDecl {{.*}} array_qualified 'void (int, const int * __counted_by(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} arr 'const int * __counted_by(count)':'const int *'
+void array_qualified(int count, const int_array __counted_by(count) arr);
+
+// Parentheses around the name do not hide a count written in the declaration
+// specifiers.
+// CHECK: FunctionDecl {{.*}} array_paren 'void (int, int * __counted_by(count))'
+// CHECK-NEXT: ParmVarDecl {{.*}} count 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} arr 'int * __counted_by(count)':'int *'
+void array_paren(int count, int_array __counted_by(count) (arr));
+
+// A type attribute written after the count does not hide it.
+// CHECK: FunctionDecl {{.*}} array_attr_after 'void (int, int * __counted_by(count))'
+void array_attr_after(int count,
+                      int arr[] __counted_by(count) __attribute__((btf_type_tag("t"))));
+// Nor does one that comes from a macro.
+#define __noderef __attribute__((noderef))
+// CHECK: FunctionDecl {{.*}} array_macro_attr_after 'void (int, int * __counted_by(count))'
+void array_macro_attr_after(int count, int arr[] __counted_by(count) __noderef);
+
+// A count that comes with the array's type through '__typeof__' names a field,
+// so it does not move to the pointer the parameter decays to.
+struct fam { int n; int arr[] __counted_by(n); };
+// CHECK: FunctionDecl {{.*}} typeof_fam 'void (int *)'
+void typeof_fam(__typeof__(((struct fam *)0)->arr) arr);
+
+// A second count is rejected, so the first stays on the pointer.
+// CHECK: FunctionDecl {{.*}} repeated_array 'void (int, int, int * __counted_by(n))'
+// expected-error at +1{{'counted_by' attribute cannot be applied to a pointer that already has a 'counted_by' attribute}}
+void repeated_array(int n, int m, int arr[] __counted_by(n) __counted_by(m));
+
 // A rejected count is not applied, and the function stays valid.
 // CHECK: FunctionDecl {{.*}} not_a_param 'void (int *)'
 // expected-error at +1{{count expression in function declaration may only reference function parameters}}
diff --git a/clang/test/Sema/attr-counted-by-function-params.c b/clang/test/Sema/attr-counted-by-function-params.c
index c4c5661bd2633e..1df3617c08a282 100644
--- a/clang/test/Sema/attr-counted-by-function-params.c
+++ b/clang/test/Sema/attr-counted-by-function-params.c
@@ -65,11 +65,26 @@ void not_an_integer(float count, int *__counted_by(count) buf);
 void not_a_reference(int count, int *__counted_by(count + 1) buf);
 
 //==============================================================================
-// Not supported yet
+// Array parameters
 //==============================================================================
 
-// expected-error at +1{{'counted_by' on arrays only applies to C99 flexible array members}}
+// An array without a size decays to a pointer that carries the count, so each
+// kind is allowed, although a flexible array member takes only counted_by.
 void array(int count, int arr[] __counted_by(count));
+void array_sized_or_null(int count, int arr[] __sized_by_or_null(count));
+// expected-error at +1{{'counted_by' cannot be applied to an array parameter with an explicit size}}
+void array_with_size(int count, int arr[10] __counted_by(count));
+
+// A type attribute written after the count does not hide it, so the count
+// still moves to the pointer.
+void array_attr_after(int count,
+                      int arr[] [[clang::counted_by(count),
+                                  clang::annotate_type("x")]]);
+void array_attr_after(int count, int *__counted_by(count) arr);
+
+//==============================================================================
+// Not supported yet
+//==============================================================================
 
 // expected-error at +1{{'counted_by' attribute on nested pointer type is not allowed}}
 void out(int count, int *__counted_by(count) *buf);



More information about the llvm-branch-commits mailing list