Author: Dan Liew
Date: 2026-08-04T14:46:53-07:00
New Revision: 745271fc23fd23f83eace959285abfd580a267a5

URL: 
https://github.com/llvm/llvm-project/commit/745271fc23fd23f83eace959285abfd580a267a5
DIFF: 
https://github.com/llvm/llvm-project/commit/745271fc23fd23f83eace959285abfd580a267a5.diff

LOG: [NFC][BoundsSafety] Give `counted_by_or_null`, `sized_by`, and 
`sized_by_or_null` their own documentation (#212877)

Previously the `counted_by`, `counted_by_or_null`, `sized_by`, and
`sized_by_or_null` attributes all pointed at a single `Documentation`
object (`CountedByDocs`). The generated `AttributeReference.rst`
therefore folded all four into one entry whose text described only
`counted_by` used on a C99 flexible array member.

This was misleading because the attributes differ in important ways that
went undocumented:

* `counted_by` counts *elements* whereas `sized_by` counts *bytes*.
* `counted_by` and `sized_by` require a null pointer to have a zero
count/size, while the `_or_null` variants allow a null pointer
regardless of the count or size.
* `counted_by` may be applied to a flexible array member, but
`sized_by`, `counted_by_or_null`, and `sized_by_or_null` apply to
pointers only.

As a result a reader looking up any of the three non-`counted_by`
attributes was shown documentation for a different attribute with
different behavior.

This change gives `sized_by`, `counted_by_or_null`, and
`sized_by_or_null` their own documentation so that each renders as its
own entry describing its actual behavior. The substantive text lives in
`counted_by` and `sized_by`; the `_or_null` variants are described in
terms of their base attribute.

While we're here, this patch also:

* Restructures `counted_by`'s documentation to explain the attribute's
meaning first, with the flexible-array-member material moved into its
own sub-section rather than leading the entry. It made sense that FAMs
were the leading entry when that was the only context the attribute was
supported in, but it's also now supported on pointer fields in a struct.
* Discusses how `counted_by` on `void *` is supported as a GNU
extension.
* Adds two sections on keeping the pointer and count/size value in sync.
The two sections are practically identical other than the attribute they
discuss and the associated count/size.
* For `counted_by`, explains how a pointer to a forward-declared type is
allowed as long as the type is complete at the use sites.
* For `sized_by`, explains the motivation for using it on types that
aren't byte-sized.

Assisted-by: Claude Code

rdar://136979310

Added: 
    

Modified: 
    clang/docs/BoundsSafetyImplPlans.md
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td

Removed: 
    


################################################################################
diff  --git a/clang/docs/BoundsSafetyImplPlans.md 
b/clang/docs/BoundsSafetyImplPlans.md
index f8b4501d91272..5acc8032c006d 100644
--- a/clang/docs/BoundsSafetyImplPlans.md
+++ b/clang/docs/BoundsSafetyImplPlans.md
@@ -4,6 +4,12 @@
 :local: true
 ```
 
+(bounds-safety-current-upstream-status)=
+## Current status of `-fbounds-safety` support in upstream Clang
+
+<!-- FIXME: This is just a placeholder -->
+Not fully implemented upstream.
+
 ## Gradual updates with experimental flag
 
 The feature will be implemented as a series of smaller PRs and we will guard 
our

diff  --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index ff8c8160ad6c1..58a9d437566ac 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2692,7 +2692,7 @@ def CountedByOrNull : DeclOrTypeAttr {
   let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
-  let Documentation = [CountedByDocs];
+  let Documentation = [CountedByOrNullDocs];
   let LangOpts = [COnly];
 }
 
@@ -2715,7 +2715,7 @@ def SizedBy : DeclOrTypeAttr {
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
-  let Documentation = [CountedByDocs];
+  let Documentation = [SizedByDocs];
   let LangOpts = [COnly];
 }
 
@@ -2725,7 +2725,7 @@ def SizedByOrNull : DeclOrTypeAttr {
   let Args = [ExprArgument<"Size">, IntArgument<"NestedLevel", 1>];
   let LateParsed = LateAttrParseExperimentalExt;
   let ParseArgumentsAsUnevaluated = 1;
-  let Documentation = [CountedByDocs];
+  let Documentation = [SizedByOrNullDocs];
   let LangOpts = [COnly];
 }
 

diff  --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 05e4cb0870652..0fe41671cad36 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9895,13 +9895,79 @@ Example:
 
 def CountedByDocs : Documentation {
   let Category = DocCatField;
-  let Content = [{
-Clang supports the ``counted_by`` attribute on the flexible array member of a
-structure in C. The argument for the attribute is the name of a field member
-holding the count of elements in the flexible array. This information can be
-used to improve the results of the array bound sanitizer and the
-``__builtin_dynamic_object_size`` builtin. The ``count`` field member must be
-within the same non-anonymous, enclosing struct as the flexible array member.
+  let Heading = "counted_by";
+  let Content = [{
+The ``counted_by`` attribute is applied to a pointer or flexible array member 
to
+indicate that the pointer points to (or the flexible array member contains) at
+least the number of *elements* given by the attribute's argument.
+
+This attribute is used by `-fbounds-safety <BoundsSafety.html>`__ to propagate
+bounds information on API surfaces without any ABI changes. This attribute is
+also used to improve the results of the array bound sanitizer and the
+``__builtin_dynamic_object_size`` builtin.
+
+Because the size of the pointee type must be known to compute the pointer's
+bounds, such a pointer must not be used while its pointee type is incomplete; a
+pointer to a forward-declared type is accepted on fields annotated with
+``counted_by``, but the type must be completed before the pointer is used. If
+the pointee type can never be completed, ``counted_by`` is rejected and
+``sized_by`` should be used instead. ``void *`` is a special case: as a GNU
+extension (diagnosed by ``-Wgnu-pointer-arith``), ``counted_by`` is accepted on
+it, where it behaves like ``sized_by`` (the argument is treated as a byte 
count,
+``void`` having an assumed size of one byte).
+
+A pointer annotated with ``counted_by`` must have a count of zero when it is
+null. This requirement is currently only enforced when compiling with
+`-fbounds-safety <BoundsSafety.html>`__ (see :ref:`Current status of
+-fbounds-safety support in upstream Clang 
<bounds-safety-current-upstream-status>`). Use
+``counted_by_or_null`` for a pointer that may be null while carrying a nonzero
+count.
+
+Keeping pointer and count in sync
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``counted_by`` attribute establishes a relationship between the annotated
+pointer and its count: the pointer must point to at least ``count`` elements.
+Assigning to only one of them can break this relationship.
+Without `-fbounds-safety <BoundsSafety.html>`__, it is the programmer's
+responsibility to ensure the pointer and count remain in sync. With
+``-fbounds-safety`` it is automatically enforced. For example:
+
+.. code-block:: c
+
+  struct buffer {
+    int *buf __attribute__((counted_by(count)));
+    size_t count;
+  };
+
+  void grow(struct buffer *b, size_t new_count) {
+    // b->buf isn't updated. The underlying memory pointed to by b->buf might 
be
+    // smaller than new_count which would contradict the counted_by attribute.
+    // Compile error with -fbounds-safety but allowed without -fbounds-safety.
+    b->count = new_count;
+  }
+
+Updating both together - so that ``buf`` points to ``count`` elements - keeps
+the attribute true. For example:
+
+.. code-block:: c
+
+  void grow(struct buffer *b, size_t new_count) {
+    // Allowed by -fbounds-safety
+    int *new_buf = malloc(new_count * sizeof(int));
+    // -fbounds-safety enforces that the `new_buf` points to at least 
`new_count`
+    // integers at runtime. Without -fbounds-safety nothing enforces this.
+    b->buf = new_buf;
+    b->count = new_count;
+  }
+
+Flexible array members
+~~~~~~~~~~~~~~~~~~~~~~
+
+The ``counted_by`` attribute may also be applied to the flexible array member 
of
+a structure in C. In this case the argument names the field member holding the
+count of elements in the flexible array; that field must be within the same
+non-anonymous, enclosing struct as the flexible array member.
 
 This example specifies that the flexible array member ``array`` has the number
 of elements allocated for it in ``count``:
@@ -9971,6 +10037,159 @@ requirement:
   }];
 }
 
+def SizedByDocs : Documentation {
+  let Category = DocCatField;
+  let Heading = "sized_by";
+  let Content = [{
+The ``sized_by`` attribute is applied to a pointer to indicate that the pointer
+points to memory containing at least the number of *bytes* given by the
+attribute's argument. It is closely related to ``counted_by``; the 
diff erence is
+that ``counted_by`` counts the number of *elements* of the pointee type, 
whereas
+``sized_by`` counts the number of *bytes*. This makes ``sized_by`` the natural
+choice for ``void *`` and other byte buffers.
+
+This attribute is used by `-fbounds-safety <BoundsSafety.html>`__ to propagate
+bounds information on API surfaces without any ABI changes. This attribute is
+also used to improve the results of the array bound sanitizer and the
+``__builtin_dynamic_object_size`` builtin.
+
+The argument is an expression of integer type, following the same rules as the
+argument of ``counted_by``. Unlike ``counted_by``, ``sized_by`` cannot be
+applied to a C99 flexible array member; it applies to pointers only. For
+example:
+
+.. code-block:: c
+
+  struct object {
+    unsigned long size;
+    void *data __attribute__((sized_by(size)));
+  };
+
+A pointer annotated with ``sized_by`` must have a size of zero when it is null.
+This requirement is currently only enforced when compiling with
+`-fbounds-safety <BoundsSafety.html>`__ (see :ref:`Current status of
+-fbounds-safety support in upstream Clang 
<bounds-safety-current-upstream-status>`). Use
+``sized_by_or_null`` for a pointer that may be null while carrying a nonzero
+size.
+
+Keeping pointer and size in sync
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``sized_by`` attribute establishes a relationship between the annotated
+pointer and its size: the pointer must point to at least ``size`` bytes.
+Assigning to only one of them can break this relationship.
+Without `-fbounds-safety <BoundsSafety.html>`__, it is the programmer's
+responsibility to ensure the pointer and size remain in sync. With
+``-fbounds-safety`` it is automatically enforced. For example:
+
+.. code-block:: c
+
+  struct buffer {
+    uint8_t *buf __attribute__((sized_by(size)));
+    size_t size;
+  };
+
+  void grow(struct buffer *b, size_t new_size) {
+    // b->buf isn't updated. The underlying memory pointed to by b->buf might 
be
+    // smaller than new_size which would contradict the sized_by attribute.
+    // Compile error with -fbounds-safety but allowed without -fbounds-safety.
+    b->size = new_size;
+  }
+
+Updating both together - so that ``buf`` points to ``size`` bytes - keeps
+the attribute true. For example:
+
+.. code-block:: c
+
+  void grow(struct buffer *b, size_t new_size) {
+    // Allowed by -fbounds-safety
+    uint8_t *new_buf = malloc(new_size);
+    // -fbounds-safety enforces that the `new_buf` points to at least 
`new_size`
+    // bytes at runtime. Without -fbounds-safety nothing enforces this.
+    b->buf = new_buf;
+    b->size = new_size;
+  }
+
+Incomplete and variable-length pointees
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``sized_by`` is typically applied to ``void *`` or a pointer to a byte-sized
+type, but it may be used with any pointee type. Two situations call for this,
+both of which rule out counting fixed-size elements:
+
+First, the pointee type may be incomplete, such as an opaque type. Its element
+size is then unavailable, so ``counted_by`` cannot be used, whereas 
``sized_by``
+bounds the memory in bytes and imposes no completeness requirement.
+
+Second, the buffer may hold variable-length elements, so there is no fixed
+element size to count, even though the total byte size is well defined. For
+example, a buffer might pack together several structures that each end in a
+flexible array member of 
diff ering length:
+
+.. code-block:: c
+
+  struct var_len {
+    int fam_size;
+    char data[] __attribute__((counted_by(fam_size)));
+  };
+
+  struct buffer_view {
+    int byte_size;
+    struct var_len *buf __attribute__((sized_by(byte_size)));
+  };
+
+Here ``counted_by`` cannot be applied to ``buf`` because its pointee is a
+variable-length structure, but ``sized_by`` bounds the whole region in bytes;
+the region is traversed by advancing a byte offset rather than by indexing
+elements.
+  }];
+}
+
+def CountedByOrNullDocs : Documentation {
+  let Category = DocCatField;
+  let Heading = "counted_by_or_null";
+  let Content = [{
+The ``counted_by_or_null`` attribute is applied to a pointer to indicate that,
+if the pointer is non-null, it points to memory containing at least the number
+of *elements* given by the attribute's argument. If the pointer is null, the
+value of the argument is ignored and the pointer points to zero elements.
+
+The ``counted_by_or_null`` attribute is identical to ``counted_by`` except that
+it treats null pointers 
diff erently and cannot be applied to a flexible array
+member. Whereas ``counted_by`` requires a null pointer to have a count of zero,
+``counted_by_or_null`` allows the pointer to be null regardless of the value of
+the count. This supports the common idiom where a pointer is either null or
+points to memory containing at least the given number of elements.
+
+Currently only `-fbounds-safety <BoundsSafety.html>`__ makes use of the
+distinction between ``counted_by_or_null`` and ``counted_by`` (see
+:ref:`Current status of -fbounds-safety support in upstream Clang
+<bounds-safety-current-upstream-status>`).
+  }];
+}
+
+def SizedByOrNullDocs : Documentation {
+  let Category = DocCatField;
+  let Heading = "sized_by_or_null";
+  let Content = [{
+The ``sized_by_or_null`` attribute is applied to a pointer to indicate that, if
+the pointer is non-null, it points to memory containing at least the number of
+*bytes* given by the attribute's argument. If the pointer is null, the value of
+the argument is ignored and the pointer points to zero bytes.
+
+The ``sized_by_or_null`` attribute is identical to ``sized_by`` except in how
+it treats null pointers. Whereas ``sized_by`` requires a null pointer to have a
+size of zero, ``sized_by_or_null`` allows the pointer to be null regardless of
+the value of the size. This supports the common idiom where a pointer is either
+null or points to memory containing at least the given number of bytes.
+
+Currently only `-fbounds-safety <BoundsSafety.html>`__ makes use of the
+distinction between ``sized_by_or_null`` and ``sized_by`` (see
+:ref:`Current status of -fbounds-safety support in upstream Clang
+<bounds-safety-current-upstream-status>`).
+  }];
+}
+
 def ClspvLibclcBuiltinDoc : Documentation {
   let Category = DocCatFunction;
   let Content = [{


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

Reply via email to