Repository navigation
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.
56deaae to
3b27e71
Compare
This comment has been minimized.
This comment has been minimized.
|
(I think the mir-opt changes are due to changing |
|
r? libs |
* (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`.
…section. Documents that other code may assume that `String`s are valid UTF-8. (Implying that invalid UTF-8 in a `String` is not immediate UB, but invalid UTF-8 in a `String` can lead to UB later)
73420c8 to
223a3b9
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. |
This comment has been minimized.
This comment has been minimized.
0470431 to
679b11c
Compare
…ec`, and `String::deref(_mut)` as exceptions to str/String's UTF-8 requirements.
…invalid UTF-8 input entirely instead of specifying that it is not UB but gives a `&(mut) str` with invalid UTF-8.
679b11c to
fd99c21
Compare
|
Okay, latest push has Since this version of the PR doesn't add the guarantee for I reimplemented @rustbot ready |
|
For FCP, this is proposing to explicitly guarantee that the following do not and will never rely on the
@rustbot fcp merge libs |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
|
@rfcbot fcp merge libs (see previous comment for info) |
|
@jhpratt has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/libs-ping: FCP proposed for libs, please feel free to register concerns. |
| #[inline] | ||
| fn deref(&self) -> &str { | ||
| self.as_str() | ||
| // Note: cannot use `String::as_str` as that may not allow invalid UTF-8. |
There was a problem hiding this comment.
It feels odd to bless String::deref but not the explicit form (as_str). Is there a reason we opted for that? It looks like this also has the effect of increasing noise in MIR (if I'm reading the differences right) which seems not great.
There was a problem hiding this comment.
I originally proposed including deref, as_str and many other functions. This proved to be controversial, so I pared the list down to only str::as_mut_bytes and String::as_mut_vec. I was then asked to add specifically as_bytes (it wasn't specified str::as_bytes or String::as_bytes, so I did both) and deref (and I inferred that deref should imply deref_mut), so I added those. I can add more, but FCP has already started on the specific list currently implemented, so that should probably be a follow-up PR? (though as of right now no-one has checked their box, so 🤷 IDK)
The mir-opt test differences aren't from this change; they're from changing str::from_utf8_unchecked from transmute::<&[u8], &str> to a pointer cast and reborrow. The mir-opt test that changed doesn't actually call String::deref, it calls <&String as PartialEq<&str>>::eq which calls <String as PartialEq<str>>::eq which calls <String as Index<RangeFull>>::index (not String::deref). The mir-opt test change is also a wash IMO (see other comment thread).
There was a problem hiding this comment.
I think the explicit forms of deref should be okay to guarantee as being equivalent. The main concern is that because deref happens automatically it should be clarified as okay.
There was a problem hiding this comment.
To be clear, the reason I bring it up is that I think having &**s be "valid" but s.as_str() be unsound is a bad idea. If we want to bless something here, we should bless the explicit form, and it seems fine to also bless the implicit form in that case.
I don't personally know that I feel strongly about expanding beyond just as_mut_vec/as_mut_bytes, but I don't feel great about just including Deref.
(This is a longer form of what Clar and you said...)
There was a problem hiding this comment.
Right, for reference my proposal is specifically to add it to the list for the FCP and make sure everyone who's checked their boxes is okay with that.
|
So, one potential additional valid concern: not all bytes are valid UTF-8 since there are a few prefixes that cannot show up. I think it might be desirable to retain this property for |
|
I do not think we should have a half-way condition like that. For example, I think something like a memcpy or rotate might in theory write arbitrary garbage (or so I'd expect) from not-actually-in-the-string bytes in the tail of some vector, to be later overwritten in the same function with initialized, correct bytes. That's probably rare but I'm not sure ruling it out fully makes sense. And I'm not sure what it buys us. |
|
Note that for the purposes of the guaranties, memcpy and rotate cannot actually cause UB even if the intermediate state is incorrect, since they're intrinsics. Although this would mean that we open up the option for adding range information on the bytes which would be part of provenance under tools like miri and would be detectable UB. |
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. ↩