Skip to content

Commit 6db1a0e

Browse files
committed
Auto merge of #28497 - apasel422:issue-28493, r=Gankro
When both the key and value types were zero-sized, `BTreeMap` previously called `heap::allocate` with `size == 0` for leaf nodes, which is undefined behavior, and jemalloc would attempt to read invalid memory, crashing the process. This avoids undefined behavior by allocating enough space to store one edge in leaf nodes that would otherwise have `size == 0`. Although this uses extra memory, maps with zero-sized key types that have sensible implementations of the ordering traits can only contain a single key-value pair (and therefore only a single leaf node), and maps with key and value types that are both zero-sized have few uses, if any. Furthermore, this is a temporary fix that will likely be unnecessary once the `BTreeMap` implementation is rewritten to use parent pointers. Closes #28493.
2 parents 0b5b029 + 9526813 commit 6db1a0e

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

src/libcollections/btree/node.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,12 @@ fn calculate_allocation_generic<K, V>(capacity: usize, is_leaf: bool) -> (usize,
164164
let (keys_size, keys_align) = (capacity * mem::size_of::<K>(), mem::align_of::<K>());
165165
let (vals_size, vals_align) = (capacity * mem::size_of::<V>(), mem::align_of::<V>());
166166
let (edges_size, edges_align) = if is_leaf {
167-
(0, 1)
167+
// allocate one edge to ensure that we don't pass size 0 to `heap::allocate`
168+
if mem::size_of::<K>() == 0 && mem::size_of::<V>() == 0 {
169+
(1, mem::align_of::<Node<K, V>>())
170+
} else {
171+
(0, 1)
172+
}
168173
} else {
169174
((capacity + 1) * mem::size_of::<Node<K, V>>(), mem::align_of::<Node<K, V>>())
170175
};

src/libcollectionstest/btree/map.rs

+53
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,59 @@ fn test_extend_ref() {
294294
assert_eq!(a[&3], "three");
295295
}
296296

297+
#[test]
298+
fn test_zst() {
299+
let mut m = BTreeMap::new();
300+
assert_eq!(m.len(), 0);
301+
302+
assert_eq!(m.insert((), ()), None);
303+
assert_eq!(m.len(), 1);
304+
305+
assert_eq!(m.insert((), ()), Some(()));
306+
assert_eq!(m.len(), 1);
307+
assert_eq!(m.iter().count(), 1);
308+
309+
m.clear();
310+
assert_eq!(m.len(), 0);
311+
312+
for _ in 0..100 {
313+
m.insert((), ());
314+
}
315+
316+
assert_eq!(m.len(), 1);
317+
assert_eq!(m.iter().count(), 1);
318+
}
319+
320+
// This test's only purpose is to ensure that zero-sized keys with nonsensical orderings
321+
// do not cause segfaults when used with zero-sized values. All other map behavior is
322+
// undefined.
323+
#[test]
324+
fn test_bad_zst() {
325+
use std::cmp::Ordering;
326+
327+
struct Bad;
328+
329+
impl PartialEq for Bad {
330+
fn eq(&self, _: &Self) -> bool { false }
331+
}
332+
333+
impl Eq for Bad {}
334+
335+
impl PartialOrd for Bad {
336+
fn partial_cmp(&self, _: &Self) -> Option<Ordering> { Some(Ordering::Less) }
337+
}
338+
339+
impl Ord for Bad {
340+
fn cmp(&self, _: &Self) -> Ordering { Ordering::Less }
341+
}
342+
343+
let mut m = BTreeMap::new();
344+
345+
for _ in 0..100 {
346+
m.insert(Bad, Bad);
347+
}
348+
}
349+
297350
mod bench {
298351
use std::collections::BTreeMap;
299352
use std::__rand::{Rng, thread_rng};

0 commit comments

Comments
 (0)