Skip to content

Commit 8e142a6

Browse files
committed
Document String::as(_mut)_str and str::as_str as exceptions to str/String's UTF-8 requirements.
1 parent 17fd52d commit 8e142a6

4 files changed

Lines changed: 140 additions & 124 deletions

File tree

‎library/alloc/src/string.rs‎

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,15 +1077,25 @@ impl String {
10771077
///
10781078
/// assert_eq!("foo", s.as_str());
10791079
/// ```
1080+
///
1081+
/// # Safety
1082+
///
1083+
/// As an exception to the [general rule](String#utf-8), this function does not assume
1084+
/// that the input `String` contains valid UTF-8, and is guaranteed to not cause
1085+
/// immediate undefined behavior if called on a `String` containing invalid UTF-8.
10801086
#[inline]
10811087
#[must_use]
10821088
#[stable(feature = "string_as_str", since = "1.7.0")]
10831089
#[rustc_diagnostic_item = "string_as_str"]
10841090
#[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")]
10851091
pub const fn as_str(&self) -> &str {
1086-
// SAFETY: String contents are stipulated to be valid UTF-8, invalid contents are an error
1087-
// at construction.
1088-
unsafe { str::from_utf8_unchecked(self.vec.as_slice()) }
1092+
// Note: cannot use `str::from_utf8_unchecked` as that may not allow invalid UTF-8.
1093+
let bytes = self.vec.as_slice();
1094+
// SAFETY: the pointer dereference is safe because that pointer
1095+
// comes from a reference which is guaranteed to be valid for reads.
1096+
// If the input bytes are not valid UTF-8, then the returned `&str` will
1097+
// have invalid UTF-8, which is unsafe but not immediate UB. See the "# Safety" section above.
1098+
unsafe { &*(bytes as *const [u8] as *const str) }
10891099
}
10901100

10911101
/// Converts a `String` into a mutable string slice.
@@ -1100,15 +1110,25 @@ impl String {
11001110
///
11011111
/// assert_eq!("FOOBAR", s_mut_str);
11021112
/// ```
1113+
///
1114+
/// # Safety
1115+
///
1116+
/// As an exception to the [general rule](String#utf-8), this function does not assume
1117+
/// that the input `String` contains valid UTF-8, and is guaranteed to not cause
1118+
/// immediate undefined behavior if called on a `String` containing invalid UTF-8.
11031119
#[inline]
11041120
#[must_use]
11051121
#[stable(feature = "string_as_str", since = "1.7.0")]
11061122
#[rustc_diagnostic_item = "string_as_mut_str"]
11071123
#[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")]
11081124
pub const fn as_mut_str(&mut self) -> &mut str {
1109-
// SAFETY: String contents are stipulated to be valid UTF-8, invalid contents are an error
1110-
// at construction.
1111-
unsafe { str::from_utf8_unchecked_mut(self.vec.as_mut_slice()) }
1125+
// Note: cannot use `str::from_utf8_unchecked_mut` as that may not allow invalid UTF-8.
1126+
let bytes = self.vec.as_mut_slice();
1127+
// SAFETY: the pointer dereference is safe because that pointer
1128+
// comes from a reference which is guaranteed to be valid for reads and writes.
1129+
// If the input bytes are not valid UTF-8, then the returned `&str` will
1130+
// have invalid UTF-8, which is unsafe but not immediate UB. See the "# Safety" section above.
1131+
unsafe { &mut *(bytes as *mut [u8] as *mut str) }
11121132
}
11131133

11141134
/// Appends a given string slice onto the end of this `String`.
@@ -2966,13 +2986,8 @@ impl ops::Deref for String {
29662986
/// immediate undefined behavior if called on a `String` containing invalid UTF-8.
29672987
#[inline]
29682988
fn deref(&self) -> &str {
2969-
// Note: cannot use `String::as_str` as that may not allow invalid UTF-8.
2970-
let bytes = self.vec.as_slice();
2971-
// SAFETY: the pointer dereference is safe because that pointer
2972-
// comes from a reference which is guaranteed to be valid for reads.
2973-
// If the input bytes are not valid UTF-8, then the returned `&str` will
2974-
// have invalid UTF-8, which is unsafe but not immediate UB. See the "# Safety" section above.
2975-
unsafe { &*(bytes as *const [u8] as *const str) }
2989+
// SAFETY: `String::as_str` has the same guarantee that invalid UTF-8 is not immediate UB
2990+
String::as_str(self)
29762991
}
29772992
}
29782993

@@ -2990,13 +3005,8 @@ impl ops::DerefMut for String {
29903005
/// immediate undefined behavior if called on a `String` containing invalid UTF-8.
29913006
#[inline]
29923007
fn deref_mut(&mut self) -> &mut str {
2993-
// Note: cannot use `String::as_mut_str` as that may not allow invalid UTF-8.
2994-
let bytes = self.vec.as_mut_slice();
2995-
// SAFETY: the pointer dereference is safe because that pointer
2996-
// comes from a reference which is guaranteed to be valid for reads and writes.
2997-
// If the input bytes are not valid UTF-8, then the returned `&str` will
2998-
// have invalid UTF-8, which is unsafe but not immediate UB. See the "# Safety" section above.
2999-
unsafe { &mut *(bytes as *mut [u8] as *mut str) }
3008+
// SAFETY: `String::as_mut_str` has the same guarantee that invalid UTF-8 is not immediate UB
3009+
String::as_mut_str(self)
30003010
}
30013011
}
30023012

‎library/core/src/str/mod.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3277,6 +3277,12 @@ impl str {
32773277
/// This method is redundant when used directly on `&str`, but
32783278
/// it helps dereferencing other string-like types to string slices,
32793279
/// for example references to `Box<str>` or `Arc<str>`.
3280+
///
3281+
/// # Safety
3282+
///
3283+
/// As an exception to the [general rule](prim@str#invariant), this function does not assume
3284+
/// that the input `str` contains valid UTF-8, and is guaranteed to not cause
3285+
/// immediate undefined behavior if called on a `str` containing invalid UTF-8.
32803286
#[inline]
32813287
#[unstable(feature = "str_as_str", issue = "130366")]
32823288
pub const fn as_str(&self) -> &str {

‎tests/mir-opt/jump_threading.chained_conditions.JumpThreading.panic-abort.diff‎

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,29 @@
4747
scope 6 (inlined #[track_caller] <String as Index<RangeFull>>::index) {
4848
let _29: &str;
4949
scope 7 (inlined String::as_str) {
50-
scope 8 (inlined Vec::<u8>::as_slice) {
51-
let _30: *const [u8];
52-
let mut _31: *const u8;
53-
let mut _32: usize;
54-
scope 9 (inlined Vec::<u8>::as_ptr) {
55-
scope 10 (inlined alloc::raw_vec::RawVec::<u8>::ptr) {
56-
scope 11 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
57-
scope 12 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
58-
let mut _33: std::ptr::NonNull<u8>;
59-
scope 13 (inlined NonNull::<u8>::cast::<u8>) {
60-
scope 14 (inlined NonNull::<u8>::as_ptr) {
50+
let _30: *const str;
51+
scope 8 {
52+
}
53+
scope 9 (inlined Vec::<u8>::as_slice) {
54+
let _31: *const [u8];
55+
let mut _32: *const u8;
56+
let mut _33: usize;
57+
scope 10 (inlined Vec::<u8>::as_ptr) {
58+
scope 11 (inlined alloc::raw_vec::RawVec::<u8>::ptr) {
59+
scope 12 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
60+
scope 13 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
61+
let mut _34: std::ptr::NonNull<u8>;
62+
scope 14 (inlined NonNull::<u8>::cast::<u8>) {
63+
scope 15 (inlined NonNull::<u8>::as_ptr) {
6164
}
6265
}
6366
}
64-
scope 15 (inlined NonNull::<u8>::as_ptr) {
67+
scope 16 (inlined NonNull::<u8>::as_ptr) {
6568
}
6669
}
6770
}
6871
}
6972
}
70-
scope 16 (inlined from_utf8_unchecked) {
71-
let _34: *const str;
72-
}
7373
}
7474
scope 17 (inlined #[track_caller] core::str::traits::<impl SliceIndex<str> for RangeFull>::index) {
7575
}
@@ -119,29 +119,29 @@
119119
scope 32 (inlined #[track_caller] <String as Index<RangeFull>>::index) {
120120
let _49: &str;
121121
scope 33 (inlined String::as_str) {
122-
scope 34 (inlined Vec::<u8>::as_slice) {
123-
let _50: *const [u8];
124-
let mut _51: *const u8;
125-
let mut _52: usize;
126-
scope 35 (inlined Vec::<u8>::as_ptr) {
127-
scope 36 (inlined alloc::raw_vec::RawVec::<u8>::ptr) {
128-
scope 37 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
129-
scope 38 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
130-
let mut _53: std::ptr::NonNull<u8>;
131-
scope 39 (inlined NonNull::<u8>::cast::<u8>) {
132-
scope 40 (inlined NonNull::<u8>::as_ptr) {
122+
let _50: *const str;
123+
scope 34 {
124+
}
125+
scope 35 (inlined Vec::<u8>::as_slice) {
126+
let _51: *const [u8];
127+
let mut _52: *const u8;
128+
let mut _53: usize;
129+
scope 36 (inlined Vec::<u8>::as_ptr) {
130+
scope 37 (inlined alloc::raw_vec::RawVec::<u8>::ptr) {
131+
scope 38 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
132+
scope 39 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
133+
let mut _54: std::ptr::NonNull<u8>;
134+
scope 40 (inlined NonNull::<u8>::cast::<u8>) {
135+
scope 41 (inlined NonNull::<u8>::as_ptr) {
133136
}
134137
}
135138
}
136-
scope 41 (inlined NonNull::<u8>::as_ptr) {
139+
scope 42 (inlined NonNull::<u8>::as_ptr) {
137140
}
138141
}
139142
}
140143
}
141144
}
142-
scope 42 (inlined from_utf8_unchecked) {
143-
let _54: *const str;
144-
}
145145
}
146146
scope 43 (inlined #[track_caller] core::str::traits::<impl SliceIndex<str> for RangeFull>::index) {
147147
}
@@ -226,22 +226,22 @@
226226
_27 = no_retag copy (*_8);
227227
_28 = no_retag copy (*_10);
228228
StorageLive(_29);
229-
StorageLive(_30);
230229
StorageLive(_31);
231-
StorageLive(_33);
232-
_33 = copy (((((*_27).0: std::vec::Vec<u8>).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::NonNull<u8>);
233-
_31 = copy _33 as *const u8 (Transmute);
234-
StorageDead(_33);
235230
StorageLive(_32);
236-
_32 = copy (((*_27).0: std::vec::Vec<u8>).1: usize);
237-
_30 = *const [u8] from (copy _31, move _32);
238-
StorageDead(_32);
239-
StorageDead(_31);
240231
StorageLive(_34);
241-
_34 = copy _30 as *const str (PtrToPtr);
242-
_29 = &(*_34);
232+
_34 = copy (((((*_27).0: std::vec::Vec<u8>).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::NonNull<u8>);
233+
_32 = copy _34 as *const u8 (Transmute);
243234
StorageDead(_34);
235+
StorageLive(_33);
236+
_33 = copy (((*_27).0: std::vec::Vec<u8>).1: usize);
237+
_31 = *const [u8] from (copy _32, move _33);
238+
StorageDead(_33);
239+
StorageDead(_32);
240+
StorageLive(_30);
241+
_30 = copy _31 as *const str (PtrToPtr);
242+
_29 = &(*_30);
244243
StorageDead(_30);
244+
StorageDead(_31);
245245
StorageLive(_36);
246246
StorageLive(_38);
247247
_36 = copy _29 as &[u8] (Transmute);
@@ -288,22 +288,22 @@
288288
_47 = no_retag copy (*_15);
289289
_48 = no_retag copy (*_17);
290290
StorageLive(_49);
291-
StorageLive(_50);
292291
StorageLive(_51);
293-
StorageLive(_53);
294-
_53 = copy (((((*_47).0: std::vec::Vec<u8>).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::NonNull<u8>);
295-
_51 = copy _53 as *const u8 (Transmute);
296-
StorageDead(_53);
297292
StorageLive(_52);
298-
_52 = copy (((*_47).0: std::vec::Vec<u8>).1: usize);
299-
_50 = *const [u8] from (copy _51, move _52);
300-
StorageDead(_52);
301-
StorageDead(_51);
302293
StorageLive(_54);
303-
_54 = copy _50 as *const str (PtrToPtr);
304-
_49 = &(*_54);
294+
_54 = copy (((((*_47).0: std::vec::Vec<u8>).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::NonNull<u8>);
295+
_52 = copy _54 as *const u8 (Transmute);
305296
StorageDead(_54);
297+
StorageLive(_53);
298+
_53 = copy (((*_47).0: std::vec::Vec<u8>).1: usize);
299+
_51 = *const [u8] from (copy _52, move _53);
300+
StorageDead(_53);
301+
StorageDead(_52);
302+
StorageLive(_50);
303+
_50 = copy _51 as *const str (PtrToPtr);
304+
_49 = &(*_50);
306305
StorageDead(_50);
306+
StorageDead(_51);
307307
StorageLive(_56);
308308
StorageLive(_58);
309309
_56 = copy _49 as &[u8] (Transmute);

0 commit comments

Comments
 (0)