Take another pass at pointer/slice discussion in API-CONVENTIONS Folks seem to get confused about NULLs a lot, largely due to mistakes in Rust. Let's see if this discussion is clearer. Change-Id: Iafadcd65f3cb8fcf04559d8413bd081a934cda5c Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/105148 Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com>
diff --git a/API-CONVENTIONS.md b/API-CONVENTIONS.md index a1b5223..c0cd64a 100644 --- a/API-CONVENTIONS.md +++ b/API-CONVENTIONS.md
@@ -82,56 +82,10 @@ ## Pointers and slices -Unless otherwise specified, pointer parameters that refer to a single object, -either as an input or output parameter, may not be `NULL`. In this case, -BoringSSL often will not check for `NULL` before dereferencing, so passing -`NULL` may crash or exhibit other undefined behavior. (Sometimes the function -will check for `NULL` anyway, for OpenSSL compatibility, but we still consider -passing `NULL` to be a caller error.) - -Pointer parameters may also refer to a contiguous sequence of objects, sometimes -referred to as a *slice*. These will typically be a pair of pointer and length -parameters named like `plaintext` and `plaintext_len`, or `objs` and `num_objs`. -We prefer the former for byte buffers and the latter for sequences of other -types. The documentation will usually refer to both parameters together, e.g. -"`EVP_DigestUpdate` hashes `len` bytes from `data`." - -Parameters in C and C++ that use array syntax, such as -`uint8_t out[SHA256_DIGEST_LENGTH]`, are really pointers. In BoringSSL's uses of -this syntax, the pointer must point to the specified number of values. - -In other cases, the documentation will describe how the function parameters -determine the slice's length. For example, a slice's length may be measured in -units other than element count, multiple slice parameters may share a length, or -a slice's length may be implicitly determined by other means like RSA key size. - -By default, BoringSSL follows C++'s -[slice conventions](https://davidben.net/2024/01/15/empty-slices.html) -for pointers. That is, unless otherwise specified, pointers for non-empty -(non-zero length) slices must be represented by a valid pointer to that many -objects in memory. Pointers for empty (zero length) slices must either be `NULL` -or point within some sequence of objects of a compatible type. - -WARNING: The dangling, non-null pointer used by Rust empty slices may *not* be -passed into BoringSSL. Rust FFIs must adjust such pointers to before passing to -BoringSSL. For example, see the `FfiSlice` abstraction in `bssl-crypto`. (We may -relax this if pointer arithmetic rules in C/C++ are adjusted to permit Rust's -pointers. Until then, it is impractical for a C/C++ library to act on such a -slice representation. See -[this document](https://davidben.net/2024/01/15/empty-slices.html) for more -discussion.) - -In some cases, OpenSSL compatibility requires that a function will treat `NULL` -slice pointers differently from non-`NULL` pointers. Such behavior will be -described in documentation. For examples, see `EVP_EncryptUpdate`, -`EVP_DigestSignFinal`, and `HMAC_Init_ex`. Callers passing potentially empty -slices into such functions should take care that the `NULL` case is either -unreachable or still has the desired behavior. - -If a `const char *` parameter is described as a "NUL-terminated string" or a -"C string", it must point to a sequence of `char` values containing a NUL (zero) -value, which determines the length. Unless otherwise specified, the pointer may -not be `NULL`, matching the C standard library. +Pointer parameters and return values in BoringSSL may refer to a single object, +or refer to a contiguous sequence of objects, sometimes referred to as a +*slice*. The expectations between the two differ. It will generally be clear +from the documentation and context which it is. For purposes of C and C++'s [strict aliasing](https://en.cppreference.com/w/c/language/object#Strict_aliasing) @@ -145,7 +99,102 @@ recommend consumers build with `-fno-strict-aliasing`. Pointer parameters additionally have ownership and lifetime requirements, -discussed in the section below. +discussed in the following section. + + +### Single objects + +Unless otherwise specified, pointers that refer to a single object, either as a +parameter or return value, may not be `NULL`. BoringSSL generally will not check +paramters for `NULL` before dereferencing, so passing `NULL` may crash or +exhibit other undefined behavior. (Sometimes the function will check for `NULL` +anyway, for OpenSSL compatibility, but we still consider passing `NULL` to be a +caller error.) + +When a single-object pointer parameter may be `NULL`, BoringSSL's documentation +will specify the semantics of passing in `NULL`. When a single-object pointer +return value may be `NULL`, BoringSSL's documentation will specify the +circumstances when it may be `NULL`. + + +### Slices + +Slices have a number of representations. As mismatching pointers and lengths +result in memory errors, the primary concern is determining the length of the +slice: + +* In most cases, BoringSSL represents slices as a pair of pointer and length + values. As parameters, these typically have names like `plaintext` and + `plaintext_len`, or `objs` and `num_objs`. We prefer the former for byte + buffers and the latter for sequences of other types. The documentation will + usually refer to both parameters together, e.g. "`EVP_DigestUpdate` hashes + `len` bytes from `data`." + +* BoringSSL has a few different conventions for returning slices. Sometimes the + pointer and length are returned from separate functions, as in `CBS_data` and + `CBS_len`. Sometimes they are returned from output parameters, as in + `SSL_SESSION_get0_ticket`. Sometimes they are a mix of output parameters and + return value, as in `SSL_SESSION_get0_id_context`. + +* Parameters in C and C++ that use array syntax, such as + `uint8_t out[SHA256_DIGEST_LENGTH]`, are really pointers. In BoringSSL's uses + of this syntax, this is a slice with a compile-time length. That is, the + pointer must point to the specified number of values. + +* If a `const char *` parameter is described as a "NUL-terminated string" or a + "C string", it must point to a sequence of `char` values containing a NUL + (zero) value, which determines the length. Unless otherwise specified, the + pointer may not be `NULL`, matching the C standard library. + +* In other cases, the documentation will describe how the function parameters + determine the slice's length. For example, a slice's length may be measured in + units other than element count, multiple slice parameters may share a length, + or a slice's length may be implicitly determined by other means like RSA key + size. + +By default, BoringSSL follows C++'s +[slice conventions](https://davidben.net/2024/01/15/empty-slices.html) +for pointers. That is, unless otherwise specified, pointers for non-empty +(non-zero length) slices must be represented by a valid pointer to that many +objects in memory. Pointers for empty (zero length) slices must either be `NULL` +or point within some sequence of objects of a compatible type. + +These conventions are set to match the conventions of C++ standard library +types like `std::span`. They are also set so that the expected underlying +pointer operations are valid, including: + +* `data + i` for all `0 <= i <= len`, including `data + len` +* `data[i]` for all `0 <= i < len` +* `(data + i) - (data + j)` for all `0 <= i <= len` and `0 <= j <= len` + +It is undefined behavior for a caller to pass an invalid slice to BoringSSL. +Callers can assume that all slices returned from BoringSSL are valid. + +WARNING: The dangling, non-null pointer used by Rust empty slices may *not* be +passed into BoringSSL. Rust FFIs must adjust such pointers to before passing to +BoringSSL. For example, see the `FfiSlice` abstraction in `bssl-crypto`. (We may +relax this if pointer arithmetic rules in C/C++ are adjusted to permit Rust's +pointers. Until then, it is impractical for a C/C++ library to act on such a +slice representation. See +[this document](https://davidben.net/2024/01/15/empty-slices.html) for more +discussion.) + +In general, callers should not attempt to special-case `NULL` pointers from +BoringSSL. This is usually unreachable or buggy code, or both. BoringSSL will +never return a slice with `NULL` pointer and non-zero length. BoringSSL may +return a zero-length slice with `NULL` pointer, but this is not semantically +different from a zero-length slice with non-`NULL` pointer. Callers that wish to +check for zero-length slices should instead condition on the length. Callers +that wish to accept zero-length slices should accept both `NULL` and non-`NULL` +forms. + +Likewise, BoringSSL functions prefer to treat zero-length slices with `NULL` and +non-`NULL` pointers equivalently. However, in some cases, OpenSSL compatibility +requires that BoringSSL treat a `NULL` pointer differently. Such behavior is +against our default conventions, so it will be described in documentation. For +examples, see `EVP_EncryptUpdate`, `EVP_DigestSignFinal`, and `HMAC_Init_ex`. +Callers passing potentially empty slices into such functions should take care +that the `NULL` case is either unreachable or still has the desired behavior. ## Object initialization and cleanup