[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
(cherry picked from commit 745271fc23fd23f83eace959285abfd580a267a5)

Conflicts:
	clang/docs/BoundsSafetyImplPlans.rst
This commit is contained in:
Dan Liew authored and Douglas Yung committed 2026-08-24 12:07:12 +00:00
1 parent 8b6fd979c9
commit 4e7f3f4e07
3 files changed
+237 -9

No files matched your search

+9
View File
@@ -5,6 +5,15 @@ Implementation plans for ``-fbounds-safety``
.. contents::
:local:
.. _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
======================================
+3 -3
View File
@@ -2685,7 +2685,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];
}
@@ -2708,7 +2708,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];
}
@@ -2718,7 +2718,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];
}
+225 -6
View File
@@ -9895,13 +9895,79 @@ Example:
def CountedByDocs : Documentation {
let Category = DocCatField;
let Heading = "counted_by";
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.
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 difference 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 differing 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 differently 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 = [{