diff --git a/clang/docs/BoundsSafetyImplPlans.rst b/clang/docs/BoundsSafetyImplPlans.rst index b374b0a0c68a..e7d0cb09810c 100644 --- a/clang/docs/BoundsSafetyImplPlans.rst +++ b/clang/docs/BoundsSafetyImplPlans.rst @@ -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 ====================================== diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 1a5bd2301dfc..2c62f46d448b 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -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]; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 05e4cb087065..0fe41671cad3 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 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 `__ 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 `__ (see :ref:`Current status of +-fbounds-safety support in upstream Clang `). 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 `__, 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 `__ 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 `__ (see :ref:`Current status of +-fbounds-safety support in upstream Clang `). 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 `__, 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 `__ makes use of the +distinction between ``counted_by_or_null`` and ``counted_by`` (see +:ref:`Current status of -fbounds-safety support in upstream Clang +`). + }]; +} + +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 `__ makes use of the +distinction between ``sized_by_or_null`` and ``sized_by`` (see +:ref:`Current status of -fbounds-safety support in upstream Clang +`). + }]; +} + def ClspvLibclcBuiltinDoc : Documentation { let Category = DocCatFunction; let Content = [{