Skip to content

Commit 360b079

Browse files
committed
interval_map: non_const iterator
The interval_map code cannot cope with iterators which change the size of an interval. Due to this, they use const iterators. However, many other modifications to intervals ARE ok and more efficient, nicer looking code can be written with them. This PR adds non-const iterators, but also adds some policing that the size of the bufferlist has not changed over the interval. Everything is hidden behind a template, as this changes the behaviour of interval map in a way that we don't want to use without careful testing of each instance. Signed-off-by: Alex Ainscow <[email protected]>
1 parent 3fb436a commit 360b079

File tree

2 files changed

+101
-4
lines changed

2 files changed

+101
-4
lines changed

src/common/interval_map.h

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* commutativity, which doesn't work if we want more recent insertions
3131
* to overwrite previous ones.
3232
*/
33-
template <typename K, typename V, typename S, template<typename, typename, typename ...> class C = std::map>
33+
template <typename K, typename V, typename S, template<typename, typename, typename ...> class C = std::map, bool nonconst_iterator = false>
3434
class interval_map {
3535
S s;
3636
using Map = C<K, std::pair<K, V> >;
@@ -215,6 +215,7 @@ class interval_map {
215215
set.insert(i.get_off(), i.get_len());
216216
}
217217
}
218+
218219
class const_iterator {
219220
Cmapiter it;
220221
const_iterator(Cmapiter &&it) : it(std::move(it)) {}
@@ -269,6 +270,77 @@ class interval_map {
269270
const_iterator end() const {
270271
return const_iterator(m.end());
271272
}
273+
274+
const_iterator cbegin() const {
275+
return const_iterator(m.begin());
276+
}
277+
const_iterator cend() const {
278+
return const_iterator(m.end());
279+
}
280+
281+
class iterator {
282+
Mapiter it;
283+
bool end;
284+
iterator(Mapiter &&it, bool end) : it(std::move(it)), end(end) {}
285+
286+
friend class interval_map;
287+
public:
288+
iterator(const iterator &) = default;
289+
iterator &operator=(const iterator &) = default;
290+
291+
iterator &operator++() {
292+
/* While the buffer can be modified with a non-const iterator, it
293+
* not change size. Allow changes in size would allow for the interval
294+
* to be merged into the next one, which would allow for unexpected
295+
* behaviour.
296+
*/
297+
if (!end && get_val().length() != get_len()) {
298+
throw std::out_of_range("buffer length has changed");
299+
}
300+
++it;
301+
return *this;
302+
}
303+
iterator operator++(int) {
304+
return const_iterator(it++);
305+
}
306+
iterator &operator--() {
307+
--it;
308+
return *this;
309+
}
310+
iterator operator--(int) {
311+
return const_iterator(it--);
312+
}
313+
bool operator==(const iterator &rhs) const {
314+
return it == rhs.it;
315+
}
316+
bool operator!=(const iterator &rhs) const {
317+
return it != rhs.it;
318+
}
319+
K get_off() const {
320+
return it->first;
321+
}
322+
K get_len() const {
323+
return it->second.first;
324+
}
325+
V &get_val() {
326+
return it->second.second;
327+
}
328+
iterator &operator*() {
329+
return *this;
330+
}
331+
constexpr bool contains(K _off, K _len) const {
332+
K off = get_off();
333+
K len = get_len();
334+
return off <= _off && _off + _len <= off + len;
335+
}
336+
};
337+
static constexpr bool nonconst_iterator_cond() { return nonconst_iterator; }
338+
iterator begin() requires (nonconst_iterator) {
339+
return iterator(m.begin(), false);
340+
}
341+
iterator end() requires (nonconst_iterator) {
342+
return iterator(m.end(), true);
343+
}
272344
std::pair<const_iterator, const_iterator> get_containing_range(
273345
K off,
274346
K len) const {

src/test/common/test_interval_map.cc

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class IntervalMapTest : public ::testing::Test {
2727
using TestType = T;
2828
};
2929

30-
template <typename _key, typename _can_merge, template<typename, typename, typename ...> class _map = std::map>
30+
template <typename _key, typename _can_merge, template<typename, typename, typename ...> class _map = std::map, bool _nonconst_iterator = false>
3131
struct bufferlist_test_type {
3232
using key = _key;
3333
using value = bufferlist;
@@ -62,7 +62,7 @@ struct bufferlist_test_type {
6262
};
6363

6464
using splitter = boost::mpl::apply<make_splitter, _can_merge>;
65-
using imap = interval_map<key, value, splitter, _map>;
65+
using imap = interval_map<key, value, splitter, _map, _nonconst_iterator>;
6666

6767
struct generate_random {
6868
bufferlist operator()(key len) {
@@ -81,7 +81,8 @@ using IntervalMapTypes = ::testing::Types<
8181
bufferlist_test_type<uint64_t, std::false_type>,
8282
bufferlist_test_type<uint64_t, std::true_type>,
8383
bufferlist_test_type<uint64_t, std::false_type, boost::container::flat_map>,
84-
bufferlist_test_type<uint64_t, std::true_type, boost::container::flat_map>
84+
bufferlist_test_type<uint64_t, std::true_type, boost::container::flat_map>,
85+
bufferlist_test_type<uint64_t, std::true_type, boost::container::flat_map, true>
8586
>;
8687

8788
TYPED_TEST_SUITE(IntervalMapTest, IntervalMapTypes);
@@ -405,3 +406,27 @@ TYPED_TEST(IntervalMapTest, print) {
405406
EXPECT_EQ("{0~5(5),10~5(5)}", out.str() );
406407
}
407408
}
409+
410+
/* This test does nothing unless nonconst_iterator is set on the interval map
411+
* If it is set, then the simple fact that append_zero() compiles means that
412+
* the non-const iterator has been enabled and used. The test then checks that
413+
* changing the size of a buffer is illegal.
414+
*/
415+
TYPED_TEST(IntervalMapTest, append_buffer_in_loop) {
416+
USING_WITH_MERGE;
417+
imap m;
418+
if constexpr (m.nonconst_iterator_cond()) {
419+
m.insert(0, 5, gen(5));
420+
try {
421+
for (auto && i : m) {
422+
i.get_val().append_zero(10);
423+
}
424+
FAIL(); // Should panic
425+
} catch (const std::out_of_range& exception) {
426+
ASSERT_EQ("buffer length has changed"sv, exception.what());
427+
} catch (const std::exception&) {
428+
// Wrong exception.
429+
FAIL();
430+
}
431+
}
432+
}

0 commit comments

Comments
 (0)