Update/relax str/String utf8 safety docs#134598
Conversation
This comment has been minimized.
This comment has been minimized.
| /// * `String::as_bytes` | ||
| /// * `String::as_bytes_mut` | ||
| /// * `String::as_str` | ||
| /// * `String::as_mut_str` | ||
| /// * `String::as_ptr` | ||
| /// * `String::as_mut_ptr` | ||
| /// * `String::as_mut_vec` | ||
| /// * `String::capacity` | ||
| /// * `String::len` | ||
| /// * `String::clear` | ||
| /// * `<String as Drop>::drop` (i.e. dropping a `String` that contains invalid UTF-8 does not alone cause UB) | ||
| /// * `String::leak` | ||
| /// * `String::into_boxed_str` | ||
| /// * `String::reserve` | ||
| /// * `String::reserve_exact` | ||
| /// * `String::try_reserve` | ||
| /// * `String::try_reserve_exact` | ||
| /// * `<String as Deref>::deref` | ||
| /// * `<String as DerefMut>::deref_mut` | ||
| /// * `<String as AsRef<str>>::as_ref` | ||
| /// * `<String as AsMut<str>>::as_mut` | ||
| /// * `<String as Borrow<str>>::borrow` | ||
| /// * `<String as BorrowMut<str>>::borrow_mut` | ||
| /// * `<String as Clone>::clone` | ||
| /// * `<String as Clone>::clone_from` |
There was a problem hiding this comment.
I think this list should be cut way down.
There should be a couple of things that are the gateways to using it in ways that temporarily break invariants that make this promise, but everything else should be allowed to check if it feels like.
TBH, I'd be tempted to say that the only thing that explicitly allows it is as_mut_vec, since you can always do whatever you wanted on that Vec<u8>.
(Let others make the case why they need anything else.)
There was a problem hiding this comment.
The specific list is not set in stone, I mostly just chose functions that don't do any string manipulation, and intended for T-libs-api to chime in here too.
I don't really see a reason to make conceptually non-"string manipulation" methods require the String to be valid UTF-8 on pain of immediate UB. At the very least I would expect that methods which conceptually don't "read" the heap allocation at all would be valid (definitely len and capacity, probably clear and dropping). Also the methods explicitly meant for accessing the raw bytes (as_mut_vec like you mentioned, into_bytes, which I just realized are actually as_mut_bytes, and as_mut_ptrstrs methods via DerefMut).
There was a problem hiding this comment.
I personally think this list is entirely unhelpful, honestly. Instead of documenting this here, we should say that the expectation is absolute and will only be documented if there are exceptions, in specific methods.
So, for example, I think it would be fair to say in the docs for as_mut_vec and as_bytes_mut that it's okay to violate the invariant as long as you fix it before you use any String methods. I don't think that offering this massive list is reasonable as an API guarantee, though.
Like, it's very likely that calling len and is_empty will always be fine, but, these methods also exist on &mut [u8] and &mut Vec<u8>, so, why even bother specifying? Additionally, "don't call any methods on String" is a much easier invariant to potentially automatically scan for, e.g. via a clippy lint, than verifying that a list of String methods aren't called. You can more easily detect when the borrow for as_mut_vec ends, for example, than when any method is called in the meantime.
There was a problem hiding this comment.
I personally think this list is entirely unhelpful, honestly. Instead of documenting this here, we should say that the expectation is absolute and will only be documented if there are exceptions, in specific methods.
Done in latest push.
So, for example, I think it would be fair to say in the docs for
as_mut_vecandas_bytes_mutthat it's okay to violate the invariant as long as you fix it before you use anyStringmethods.
We already say this for these.
This PR does relax str::as_bytes_mut's requirements from "must be UTF-8 when the borrow expires" to "invalid UTF-8 after the borrow expires is not immediate UB but is unsafe" to match String::as_mut_vec's.
This PR also relaxes both of them to not be UB on invalid UTF-8 on their input so that they can be used to "fix" invalid UTF-8.
I don't think that offering this massive list is reasonable as an API guarantee, though.
I disagree, but I've reduced the list of exceptions for this PR to only str::as_bytes_mut and String::as_mut_vec, and left other functions to later PRs to not bikeshed on it here.
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The bytes passed in must be valid UTF-8. |
There was a problem hiding this comment.
I think we should keep this requirement, to preserve the freedom for tooling (miri, ubchecks, kani, whatever) to validate it, even if we don't in release builds.
People can always pointer-cast if they want to do turn a &[u8] into &str without involving any standard library methods.
There was a problem hiding this comment.
It's true that you can use pointer casting to get a &str with invalid UTF-8 from a &[u8] even if this function requires valid UTF-8 on pain of immediate UB, but String::from_utf8_unchecked currently does not require valid UTF-8 on pain of immediate UB (only on pain of later UB if the String is misused), so there's an asymmetry here.
So, we can either:
- Relax
str::from_utf8_unchecked(_mut)to not be immediate UB on invalid UTF-8 (what this PR does) - Restrict
String::from_utf8_uncheckedto be immediate UB on invalid UTF-8 (probably a breaking change) - Status quo with
str::from_utf8_uncheckedandString::from_utf8_uncheckedhaving different preconditions w.r.t. UTF-8 - Something else? (maybe declare calling
_::from_utf8_uncheckedon invalid UTF-8 "library erroneous behaviour"?)
|
Triage: comments are not answered |
|
Responded to comments @rustbot ready |
|
Triage: don't think that this is suitable to merge as-is, left additional comments. @rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
a29cb96 to
732cc42
Compare
This comment has been minimized.
This comment has been minimized.
|
I've pared down the exceptions from the third commit to only @rustbot ready |
This comment has been minimized.
This comment has been minimized.
* (str,core::str)::from_utf8_unchecked: doc: do not declare that invalid UTF-8 is immediate UB, analogous to `String::from_utf8_unchecked`. * (str,core::str)::from_utf8_unchecked: update internal safety comments, and use pointer cast instead of transmute * (str,core::str)::from_utf8_unchecked_mut: add `# Safety` section mirroring `from_utf8_unchecked`, instead of just referring to it. * str::as_bytes_mut: doc: do not declare that invalid UTF-8 after borrow ends is immediate UB, analogous to `String::as_mut_vec`.
Documents that invalid UTF-8 in a `String` is not immediate UB, but other code may assume that `String`s are valid UTF-8, so it can lead to UB later.
…o str/String's UTF-8 requirements.
56deaae to
3b27e71
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
(I think the mir-opt changes are due to changing |
View all comments
Relax UTF-8 requirements of
Stringas well as somestr/String-related functions from "must be UTF-8 or UB" to "invalid UTF-8 is not immediate UB but is unsafe" to match the validity requirement forstr.cc @joshtriplett #71033 (comment)
Current state:
strcurrently documents that it's UTF-8 requirement is not a language-level requirement (i.e. having a non-UTF-8stris not immediately UB, but it can cause later UB because other code can assume thatstr's are valid UTF-8), thoughstd::str::from_utf8_uncheckedcurrently still has a stronger requirement.Stringdoes not currently have such docs, though some of its associated functions' docs imply it1.(first commit) This PR relaxes
(str,std::str)::from_utf8_unchecked(_mut)'s andstr::as_bytes_mut's UTF-8 requirements to match that ofstritself. Updates the safety comments and implementation in(str,std::str)::from_utf8_unchecked(_mut). Adds# Safetydocumentation to*_mutthat previously just deferred to the immutable versions.(second commit) This PR also adds an "Invariant" section to
String's docs to matchstr, which documents that(third commit) This PR also documents
str::as_bytes_mutandString::as_mut_vecas "exceptions" tostr/String's "Invariant" sections, that they are not UB to call on invalid-UTF-8str/Strings.As this relaxes stable API requirements, I think this needs a T-libs-api FCP?
@rustbot label T-libs-api
r? @joshtriplett (or other T-libs-api)
Footnotes
String::from_utf8_unchecked's docs say "This function is unsafe because it does not check that the bytes passed to it are valid UTF-8. If this constraint is violated, it may cause memory unsafety issues with future users of the String, as the rest of the standard library assumes that Strings are valid UTF-8." Note that it mentions only future users, implying that callingString::from_utf8_uncheckedwith invalid UTF-8 alone is not immediate UB. ↩