Skip to content

Commit 416bbe0

Browse files
Allow non-const depth_iteratort to be created from a const root
This allows creation of a non-const depth_iteratort from a const root as long as a function is provided to get a non-const version of the root if/when mutate if finally called.
1 parent bb88574 commit 416bbe0

File tree

4 files changed

+172
-27
lines changed

4 files changed

+172
-27
lines changed

src/util/expr.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,10 @@ const_depth_iteratort exprt::depth_cbegin() const
526526
{ return const_depth_iteratort(*this); }
527527
const_depth_iteratort exprt::depth_cend() const
528528
{ return const_depth_iteratort(); }
529+
depth_iteratort exprt::depth_begin(std::function<exprt &()> mutate_root) const
530+
{
531+
return depth_iteratort(*this, std::move(mutate_root));
532+
}
529533

530534
const_unique_depth_iteratort exprt::unique_depth_begin() const
531535
{ return const_unique_depth_iteratort(*this); }

src/util/expr.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Author: Daniel Kroening, [email protected]
1212

1313
#define OPERANDS_IN_GETSUB
1414

15+
#include <functional>
1516
#include "type.h"
1617

1718
#define forall_operands(it, expr) \
@@ -172,6 +173,7 @@ class exprt:public irept
172173
const_depth_iteratort depth_end() const;
173174
const_depth_iteratort depth_cbegin() const;
174175
const_depth_iteratort depth_cend() const;
176+
depth_iteratort depth_begin(std::function<exprt &()> mutate_root) const;
175177
const_unique_depth_iteratort unique_depth_begin() const;
176178
const_unique_depth_iteratort unique_depth_end() const;
177179
const_unique_depth_iteratort unique_depth_cbegin() const;

src/util/expr_iterator.h

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,24 @@ class depth_iterator_baset
7373
typedef const exprt *pointer; // NOLINT
7474
typedef const exprt &reference; // NOLINT
7575
typedef std::forward_iterator_tag iterator_category; // NOLINT
76-
bool operator==(const depth_iterator_t &other) const
76+
77+
template <typename other_depth_iterator_t>
78+
friend class depth_iterator_baset;
79+
80+
template <typename other_depth_iterator_t>
81+
bool operator==(
82+
const depth_iterator_baset<other_depth_iterator_t> &other) const
7783
{
7884
return m_stack==other.m_stack;
7985
}
8086

87+
template <typename other_depth_iterator_t>
88+
bool operator!=(
89+
const depth_iterator_baset<other_depth_iterator_t> &other) const
90+
{
91+
return !(*this == other);
92+
}
93+
8194
/// Preincrement operator
8295
/// Do not call on the end() iterator
8396
depth_iterator_t &operator++()
@@ -152,27 +165,39 @@ class depth_iterator_baset
152165
depth_iterator_baset &operator=(depth_iterator_baset &&other)
153166
{ m_stack=std::move(other.m_stack); }
154167

155-
/// Obtain non-const exprt reference. Performs a copy-on-write on
156-
/// the root node as a side effect. To be called only if a the root
157-
/// is a non-const exprt. Do not call on the end() iterator
168+
const exprt &get_root()
169+
{
170+
return m_stack.front().expr.get();
171+
}
172+
173+
/// Obtain non-const exprt reference. Performs a copy-on-write on the root
174+
/// node as a side effect.
175+
/// \remarks
176+
/// To be called only if a the root is a non-const exprt.
177+
/// Do not call on the end() iterator
158178
exprt &mutate()
159179
{
160180
PRECONDITION(!m_stack.empty());
161-
auto expr=std::ref(const_cast<exprt &>(m_stack.front().expr.get()));
181+
// Cast the root expr to non-const
182+
exprt *expr = &const_cast<exprt &>(get_root());
162183
for(auto &state : m_stack)
163184
{
164-
auto &operands=expr.get().operands();
185+
// This deliberately breaks sharing as expr is now non-const
186+
auto &operands = expr->operands();
187+
// Get iterators into the operands of the new expr corresponding to the
188+
// ones into the operands of the old expr
165189
const auto i=operands.size()-(state.end-state.it);
166190
const auto it=operands.begin()+i;
167-
state.expr=expr.get();
191+
state.expr = *expr;
168192
state.it=it;
169193
state.end=operands.end();
194+
// Get the expr for the next level down to use in the next iteration
170195
if(!(state==m_stack.back()))
171196
{
172-
expr=std::ref(*it);
197+
expr = &*it;
173198
}
174199
}
175-
return expr.get();
200+
return *expr;
176201
}
177202

178203
/// Pushes expression onto the stack
@@ -194,13 +219,6 @@ class depth_iterator_baset
194219
{ return static_cast<depth_iterator_t &>(*this); }
195220
};
196221

197-
template<typename T, typename std::enable_if<
198-
std::is_base_of<depth_iterator_baset<T>, T>::value, int>::type=0>
199-
bool operator!=(
200-
const T &left,
201-
const T &right)
202-
{ return !(left==right); }
203-
204222
class const_depth_iteratort final:
205223
public depth_iterator_baset<const_depth_iteratort>
206224
{
@@ -215,17 +233,58 @@ class const_depth_iteratort final:
215233
class depth_iteratort final:
216234
public depth_iterator_baset<depth_iteratort>
217235
{
236+
private:
237+
/// If this is non-empty then the root is currently const and this function
238+
/// must be called before any mutations occur
239+
std::function<exprt &()> mutate_root;
240+
218241
public:
219242
/// Create an end iterator
220243
depth_iteratort()=default;
244+
221245
/// Create iterator starting at the supplied node (root)
222246
/// All mutations of the child nodes will be reflected on this node
223-
explicit depth_iteratort(exprt &expr):
224-
depth_iterator_baset(expr) { }
225-
/// Obtain non-const reference to the pointed exprt instance
226-
/// Modifies root expression
247+
/// \param expr: The root node to begin iteration at
248+
explicit depth_iteratort(exprt &expr) : depth_iterator_baset(expr)
249+
{
250+
}
251+
252+
/// Create iterator starting at the supplied node (root)
253+
/// If mutations of the child nodes are required then the passed
254+
/// mutate_root function will be called to get a non-const copy of the same
255+
/// root on which the mutations will be done.
256+
/// \param expr: The root node to begin iteration at
257+
/// \param mutate_root: The function to call to get a non-const copy of the
258+
/// same root expression passed in the expr parameter
259+
explicit depth_iteratort(
260+
const exprt &expr,
261+
std::function<exprt &()> mutate_root)
262+
: depth_iterator_baset(expr), mutate_root(std::move(mutate_root))
263+
{
264+
// If you don't provide a mutate_root function then you must pass a
265+
// non-const exprt (use the other constructor).
266+
PRECONDITION(this->mutate_root);
267+
}
268+
269+
/// Obtain non-const reference to the exprt instance currently pointed to
270+
/// by the iterator.
271+
/// If the iterator is currently using a const root exprt then calls
272+
/// mutate_root to get a non-const root and copies it if it is shared
273+
/// \returns A non-const reference to the element this iterator is
274+
/// currently pointing to
227275
exprt &mutate()
228-
{ return depth_iterator_baset::mutate(); }
276+
{
277+
if(mutate_root)
278+
{
279+
exprt &new_root = mutate_root();
280+
INVARIANT(
281+
&new_root.read() == &get_root().read(),
282+
"mutate_root must return the same root node that depth_iteratort was "
283+
"constructed with");
284+
mutate_root = nullptr;
285+
}
286+
return depth_iterator_baset::mutate();
287+
}
229288
};
230289

231290
class const_unique_depth_iteratort final:

unit/util/expr_iterator.cpp

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
/*******************************************************************\
1+
// Copyright 2018 DiffBlue Limited. All Rights Reserved.
22

3-
Module: Example Catch Tests
4-
5-
Author: DiffBlue Limited. All rights reserved.
6-
7-
\*******************************************************************/
3+
/// \file Tests for depth_iteratort and friends
84

95
#include <testing-utils/catch.hpp>
106
#include <util/expr.h>
@@ -156,3 +152,87 @@ TEST_CASE("next_sibling_or_parent, next parent ")
156152
it.next_sibling_or_parent();
157153
REQUIRE(it==input[0].depth_end());
158154
}
155+
156+
/// The mutate_root feature of depth_iteratort can be useful when you have an
157+
/// `exprt` and want to depth iterate its first operand. As part of that
158+
/// iteration you may or may not decide to mutate one of the children,
159+
/// depending on the state of those children. If you do not decide to mutate
160+
/// a child then you probably don't want to call the non-const version of
161+
/// `op0()` because that will break sharing, so you create the depth iterator
162+
/// with the const `exprt` returned from the const invocation of `op0()`, but
163+
/// if you decide during iteration that you do want to mutate a child then
164+
/// you can call the non-const version of `op0()` on the original `exprt` in
165+
/// order to get a non-const `exprt` that the iterator can copy-on-write
166+
/// update to change the child. At this point the iterator needs to be
167+
/// informed that it is now safe to write to the `exprt` it contains. This is
168+
/// achieved by providing a call-back function to the iterator.
169+
SCENARIO("depth_iterator_mutate_root", "[core][utils][depth_iterator]")
170+
{
171+
GIVEN("A sample expression with a child with id() == ID_1")
172+
{
173+
// Set up test expression
174+
exprt test_expr;
175+
// This is the expression we will iterate over
176+
exprt test_root;
177+
// This is the expression we might mutate when we find it
178+
exprt test_operand(ID_1);
179+
test_root.move_to_operands(test_operand);
180+
test_expr.move_to_operands(test_root);
181+
WHEN("Iteration occurs without mutation")
182+
{
183+
// Create shared copies
184+
exprt original_expr = test_expr;
185+
exprt expr = original_expr;
186+
THEN("Shared copy should return object with same address from read()")
187+
{
188+
REQUIRE(&expr.read() == &original_expr.read());
189+
}
190+
// Create iterator on first operand of expr
191+
// We don't want to copy-on-write expr, so we get its first operand
192+
// using a const reference to it
193+
const exprt &root = static_cast<const exprt &>(expr).op0();
194+
// This function gets a mutable version of root but in so doing it
195+
// copy-on-writes expr
196+
auto get_non_const_root = [&expr]() -> exprt & { return expr.op0(); };
197+
// Create the iterator over root
198+
depth_iteratort it = root.depth_begin(get_non_const_root);
199+
for(; it != root.depth_cend(); ++it)
200+
{
201+
if(it->id() == ID_0) // This will never happen
202+
it.mutate().id(ID_1);
203+
}
204+
THEN("No breaking of sharing should have occurred")
205+
{
206+
REQUIRE(&expr.read() == &original_expr.read());
207+
}
208+
}
209+
WHEN("Iteration occurs with mutation")
210+
{
211+
// Create shared copies
212+
exprt original_expr = test_expr;
213+
exprt expr = original_expr;
214+
THEN("Shared copy should return object with same address from read()")
215+
{
216+
REQUIRE(&expr.read() == &original_expr.read());
217+
}
218+
// Create iterator on first operand of expr
219+
// We don't want to copy-on-write expr, so we get its first operand
220+
// using a const reference to it
221+
const exprt &root = static_cast<const exprt &>(expr).op0();
222+
// This function gets a mutable version of root but in so doing it
223+
// copy-on-writes expr
224+
auto get_non_const_root = [&expr]() -> exprt & { return expr.op0(); };
225+
// Create the iterator over root
226+
depth_iteratort it = root.depth_begin(get_non_const_root);
227+
for(; it != root.depth_cend(); ++it)
228+
{
229+
if(it->id() == ID_1)
230+
it.mutate().id(ID_0);
231+
}
232+
THEN("Breaking of sharing should have occurred")
233+
{
234+
REQUIRE(&expr.read() != &original_expr.read());
235+
}
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)