Skip to content

Commit dd1d107

Browse files
committed
btreegraph: Replace BTreeSet with BTreeMap to avoid incorrect assume_init()
assume_init() can't be used here because it may create invalid pointers (eg. if L = Vec<_>) or uninhabited types.
1 parent 1b32d73 commit dd1d107

File tree

1 file changed

+17
-86
lines changed

1 file changed

+17
-86
lines changed

webgraph/src/graphs/btree_graph.rs

Lines changed: 17 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2023 Inria
2+
* SPDX-FileCopyrightText: 2023-2025 Inria
33
* SPDX-FileCopyrightText: 2023 Sebastiano Vigna
44
*
55
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
@@ -8,7 +8,7 @@
88
use crate::prelude::*;
99

1010
use lender::prelude::*;
11-
use std::{collections::BTreeSet, mem::MaybeUninit};
11+
use std::collections::BTreeMap;
1212

1313
/// A mutable [`LabeledRandomAccessGraph`] implementation based on a vector of
1414
/// [`BTreeSet`].
@@ -20,12 +20,12 @@ use std::{collections::BTreeSet, mem::MaybeUninit};
2020
/// By setting the feature `serde`, this struct can be serialized and
2121
/// deserialized using [serde](https://crates.io/crates/serde).
2222
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23-
#[derive(Clone, Debug)]
23+
#[derive(Clone, Debug, PartialEq, Eq)]
2424
pub struct LabeledBTreeGraph<L: Clone + 'static = ()> {
2525
/// The number of arcs in the graph.
2626
number_of_arcs: u64,
2727
/// For each node, its list of successors.
28-
succ: Vec<BTreeSet<Successor<L>>>,
28+
succ: Vec<BTreeMap<usize, L>>,
2929
}
3030

3131
impl<L: Clone + 'static> core::default::Default for LabeledBTreeGraph<L> {
@@ -34,38 +34,6 @@ impl<L: Clone + 'static> core::default::Default for LabeledBTreeGraph<L> {
3434
}
3535
}
3636

37-
/// Manual implementation of [`PartialEq`]. This implementation is necessary
38-
/// because the private struct [`Successor`] that we use to store in a
39-
/// [`BTreeSet`] the tuple `(usize, Label)` implements [`PartialEq`] ignoring
40-
/// the label so to enforce the absence of duplicate arcs. This implies that the
41-
/// derived implementation of [`PartialEq`] would not check labels, so the same
42-
/// graph with different labels would be equal, and this is not the intended
43-
/// semantics.
44-
impl<L: Clone + 'static + PartialEq> PartialEq for LabeledBTreeGraph<L> {
45-
fn eq(&self, other: &Self) -> bool {
46-
if self.number_of_arcs != other.number_of_arcs {
47-
return false;
48-
}
49-
if self.succ.len() != other.succ.len() {
50-
return false;
51-
}
52-
for (s, o) in self.succ.iter().zip(other.succ.iter()) {
53-
if s.len() != o.len() {
54-
return false;
55-
}
56-
let s_iter = s.iter().map(|x| (x.0, &x.1));
57-
let o_iter = o.iter().map(|x| (x.0, &x.1));
58-
for (v1, v2) in s_iter.zip(o_iter) {
59-
if v1 != v2 {
60-
return false;
61-
}
62-
}
63-
}
64-
true
65-
}
66-
}
67-
impl<L: Clone + 'static + Eq> Eq for LabeledBTreeGraph<L> {}
68-
6937
impl<L: Clone + 'static> LabeledBTreeGraph<L> {
7038
/// Creates a new empty graph.
7139
pub fn new() -> Self {
@@ -79,7 +47,7 @@ impl<L: Clone + 'static> LabeledBTreeGraph<L> {
7947
pub fn empty(n: usize) -> Self {
8048
Self {
8149
number_of_arcs: 0,
82-
succ: Vec::from_iter((0..n).map(|_| BTreeSet::new())),
50+
succ: Vec::from_iter((0..n).map(|_| BTreeMap::new())),
8351
}
8452
}
8553

@@ -91,7 +59,7 @@ impl<L: Clone + 'static> LabeledBTreeGraph<L> {
9159
/// than the number of nodes in the graph.
9260
pub fn add_node(&mut self, node: usize) -> bool {
9361
let len = self.succ.len();
94-
self.succ.extend((len..=node).map(|_| BTreeSet::new()));
62+
self.succ.extend((len..=node).map(|_| BTreeMap::new()));
9563
len <= node
9664
}
9765

@@ -105,9 +73,9 @@ impl<L: Clone + 'static> LabeledBTreeGraph<L> {
10573
self.succ.len(),
10674
);
10775
}
108-
let result = self.succ[u].insert(Successor(v, l));
109-
self.number_of_arcs += result as u64;
110-
result
76+
let is_new_arc = self.succ[u].insert(v, l).is_none();
77+
self.number_of_arcs += is_new_arc as u64;
78+
is_new_arc
11179
}
11280

11381
/// Remove an arc from the graph and return whether it was present or not.
@@ -120,13 +88,9 @@ impl<L: Clone + 'static> LabeledBTreeGraph<L> {
12088
self.succ.len(),
12189
);
12290
}
123-
// SAFETY: the label is not used by Eq/Ord.
124-
let result = self.succ[u].remove(&Successor(v, unsafe {
125-
#[allow(clippy::uninit_assumed_init)]
126-
MaybeUninit::<L>::uninit().assume_init()
127-
}));
128-
self.number_of_arcs -= result as u64;
129-
result
91+
let arc_existed = self.succ[u].remove(&v).is_some();
92+
self.number_of_arcs -= arc_existed as u64;
93+
arc_existed
13094
}
13195

13296
/// Add nodes and labeled successors from an [`IntoLender`] yielding a
@@ -375,8 +339,8 @@ impl SequentialGraph for BTreeGraph {}
375339

376340
impl RandomAccessLabeling for BTreeGraph {
377341
type Labels<'succ> = std::iter::Map<
378-
std::collections::btree_set::Iter<'succ, Successor<()>>,
379-
fn(&Successor<()>) -> usize,
342+
std::collections::btree_map::Keys<'succ, usize, ()>,
343+
fn(&usize) -> usize,
380344
>;
381345

382346
#[inline(always)]
@@ -391,7 +355,7 @@ impl RandomAccessLabeling for BTreeGraph {
391355

392356
#[inline(always)]
393357
fn labels(&self, node: usize) -> <Self as RandomAccessLabeling>::Labels<'_> {
394-
self.0.succ[node].iter().map(|x| x.0)
358+
self.0.succ[node].keys().map(|&key| key)
395359
}
396360
}
397361

@@ -403,50 +367,17 @@ impl From<LabeledBTreeGraph<()>> for BTreeGraph {
403367
}
404368
}
405369

406-
#[doc(hidden)]
407-
/// A struct containing a successor.
408-
///
409-
/// By implementing equality and order on the first coordinate only, we
410-
/// can store the successors of a node and their labels as a
411-
/// [`BTreeSet`] of pairs `(usize, L)`.
412-
#[derive(Clone, Copy, Debug)]
413-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
414-
pub struct Successor<L: Clone + 'static>(usize, L);
415-
416-
impl<L: Clone + 'static> PartialEq for Successor<L> {
417-
#[inline(always)]
418-
fn eq(&self, other: &Self) -> bool {
419-
self.0 == other.0
420-
}
421-
}
422-
423-
impl<L: Clone + 'static> Eq for Successor<L> {}
424-
425-
impl<L: Clone + 'static> PartialOrd for Successor<L> {
426-
#[inline(always)]
427-
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
428-
Some(self.0.cmp(&other.0))
429-
}
430-
}
431-
432-
impl<L: Clone + 'static> Ord for Successor<L> {
433-
#[inline(always)]
434-
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
435-
self.0.cmp(&other.0)
436-
}
437-
}
438-
439370
#[doc(hidden)]
440371
#[repr(transparent)]
441-
pub struct Successors<'a, L: Clone + 'static>(std::collections::btree_set::Iter<'a, Successor<L>>);
372+
pub struct Successors<'a, L: Clone + 'static>(std::collections::btree_map::Iter<'a, usize, L>);
442373

443374
unsafe impl<L: Clone + 'static> SortedIterator for Successors<'_, L> {}
444375

445376
impl<L: Clone + 'static> Iterator for Successors<'_, L> {
446377
type Item = (usize, L);
447378
#[inline(always)]
448379
fn next(&mut self) -> Option<Self::Item> {
449-
self.0.next().cloned().map(|x| (x.0, x.1))
380+
self.0.next().map(|(succ, labels)| (*succ, labels.clone()))
450381
}
451382
}
452383

0 commit comments

Comments
 (0)