Skip to content

Commit 5208f63

Browse files
committed
Auto merge of #81728 - Qwaz:fix-80335, r=joshtriplett
Fixes API soundness issue in join() Fixes #80335
2 parents 1df2056 + 26a6270 commit 5208f63

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

library/alloc/src/str.rs

+25-19
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ impl<S: Borrow<str>> Join<&str> for [S] {
9292
}
9393
}
9494

95-
macro_rules! spezialize_for_lengths {
96-
($separator:expr, $target:expr, $iter:expr; $($num:expr),*) => {
95+
macro_rules! specialize_for_lengths {
96+
($separator:expr, $target:expr, $iter:expr; $($num:expr),*) => {{
9797
let mut target = $target;
9898
let iter = $iter;
9999
let sep_bytes = $separator;
@@ -104,19 +104,22 @@ macro_rules! spezialize_for_lengths {
104104
$num => {
105105
for s in iter {
106106
copy_slice_and_advance!(target, sep_bytes);
107-
copy_slice_and_advance!(target, s.borrow().as_ref());
107+
let content_bytes = s.borrow().as_ref();
108+
copy_slice_and_advance!(target, content_bytes);
108109
}
109110
},
110111
)*
111112
_ => {
112113
// arbitrary non-zero size fallback
113114
for s in iter {
114115
copy_slice_and_advance!(target, sep_bytes);
115-
copy_slice_and_advance!(target, s.borrow().as_ref());
116+
let content_bytes = s.borrow().as_ref();
117+
copy_slice_and_advance!(target, content_bytes);
116118
}
117119
}
118120
}
119-
};
121+
target
122+
}}
120123
}
121124

122125
macro_rules! copy_slice_and_advance {
@@ -155,30 +158,33 @@ where
155158
// if the `len` calculation overflows, we'll panic
156159
// we would have run out of memory anyway and the rest of the function requires
157160
// the entire Vec pre-allocated for safety
158-
let len = sep_len
161+
let reserved_len = sep_len
159162
.checked_mul(iter.len())
160163
.and_then(|n| {
161164
slice.iter().map(|s| s.borrow().as_ref().len()).try_fold(n, usize::checked_add)
162165
})
163166
.expect("attempt to join into collection with len > usize::MAX");
164167

165-
// crucial for safety
166-
let mut result = Vec::with_capacity(len);
167-
assert!(result.capacity() >= len);
168+
// prepare an uninitialized buffer
169+
let mut result = Vec::with_capacity(reserved_len);
170+
debug_assert!(result.capacity() >= reserved_len);
168171

169172
result.extend_from_slice(first.borrow().as_ref());
170173

171174
unsafe {
172-
{
173-
let pos = result.len();
174-
let target = result.get_unchecked_mut(pos..len);
175-
176-
// copy separator and slices over without bounds checks
177-
// generate loops with hardcoded offsets for small separators
178-
// massive improvements possible (~ x2)
179-
spezialize_for_lengths!(sep, target, iter; 0, 1, 2, 3, 4);
180-
}
181-
result.set_len(len);
175+
let pos = result.len();
176+
let target = result.get_unchecked_mut(pos..reserved_len);
177+
178+
// copy separator and slices over without bounds checks
179+
// generate loops with hardcoded offsets for small separators
180+
// massive improvements possible (~ x2)
181+
let remain = specialize_for_lengths!(sep, target, iter; 0, 1, 2, 3, 4);
182+
183+
// A weird borrow implementation may return different
184+
// slices for the length calculation and the actual copy.
185+
// Make sure we don't expose uninitialized bytes to the caller.
186+
let result_len = reserved_len - remain.len();
187+
result.set_len(result_len);
182188
}
183189
result
184190
}

library/alloc/tests/str.rs

+30
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,36 @@ fn test_join_for_different_lengths_with_long_separator() {
160160
test_join!("~~~~~a~~~~~bc", ["", "a", "bc"], "~~~~~");
161161
}
162162

163+
#[test]
164+
fn test_join_isue_80335() {
165+
use core::{borrow::Borrow, cell::Cell};
166+
167+
struct WeirdBorrow {
168+
state: Cell<bool>,
169+
}
170+
171+
impl Default for WeirdBorrow {
172+
fn default() -> Self {
173+
WeirdBorrow { state: Cell::new(false) }
174+
}
175+
}
176+
177+
impl Borrow<str> for WeirdBorrow {
178+
fn borrow(&self) -> &str {
179+
let state = self.state.get();
180+
if state {
181+
"0"
182+
} else {
183+
self.state.set(true);
184+
"123456"
185+
}
186+
}
187+
}
188+
189+
let arr: [WeirdBorrow; 3] = Default::default();
190+
test_join!("0-0-0", arr, "-");
191+
}
192+
163193
#[test]
164194
#[cfg_attr(miri, ignore)] // Miri is too slow
165195
fn test_unsafe_slice() {

0 commit comments

Comments
 (0)