Skip to content

Commit 888f27d

Browse files
Refactoring of substitute array access
We make substitute_array return an expression. We add an argument to left-propagate values in the substitution. With this, we can avoid having to first replace the array_with with an array and then propagating to overwrite unknown values in the array. This function is now implemented using expr_try_dynamic_cast, which is better than checking ids: it can perform additional checks than just the id, it also does the check and convert in one function call. We split the function in several parts each responsible to handle a specific type, which makes the global structure clearer.
1 parent 68807ad commit 888f27d

File tree

2 files changed

+191
-123
lines changed

2 files changed

+191
-123
lines changed

src/solvers/refinement/string_refinement.cpp

Lines changed: 184 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ Author: Alberto Griggio, alberto.griggio@gmail.com
2929
#include <java_bytecode/java_types.h>
3030
#include <unordered_set>
3131

32-
static exprt substitute_array_with_expr(const exprt &expr, const exprt &index);
33-
3432
static bool is_valid_string_constraint(
3533
messaget::mstreamt &stream,
3634
const namespacet &ns,
@@ -124,6 +122,12 @@ static optionalt<exprt> get_array(
124122
messaget::mstreamt &stream,
125123
const array_string_exprt &arr);
126124

125+
static exprt substitute_array_access(
126+
const index_exprt &index_expr,
127+
const std::function<symbol_exprt(const irep_idt &, const typet &)>
128+
&symbol_generator,
129+
const bool left_propagate);
130+
127131
/// Convert index-value map to a vector of values. If a value for an
128132
/// index is not defined, set it to the value referenced by the next higher
129133
/// index.
@@ -1218,35 +1222,61 @@ void debug_model(
12181222
}
12191223

12201224
/// Create a new expression where 'with' expressions on arrays are replaced by
1221-
/// 'if' expressions. e.g. for an array access arr[x], where: `arr :=
1225+
/// 'if' expressions. e.g. for an array access arr[index], where: `arr :=
12221226
/// array_of(12) with {0:=24} with {2:=42}` the constructed expression will be:
12231227
/// `index==0 ? 24 : index==2 ? 42 : 12`
1228+
/// If `left_propagate` is set to true, the expression will look like
1229+
/// `index<=0 ? 24 : index<=2 ? 42 : 12`
12241230
/// \param expr: A (possibly nested) 'with' expression on an `array_of`
1225-
/// expression
1231+
/// expression. The function checks that the expression is of the form
1232+
/// `with_expr(with_expr(...(array_of(...)))`. This is the form in which
1233+
/// array valuations coming from the underlying solver are given.
12261234
/// \param index: An index with which to build the equality condition
12271235
/// \return An expression containing no 'with' expression
1228-
static exprt substitute_array_with_expr(const exprt &expr, const exprt &index)
1236+
static exprt substitute_array_access(
1237+
const with_exprt &expr,
1238+
const exprt &index,
1239+
const bool left_propagate)
12291240
{
1230-
if(expr.id()==ID_with)
1231-
{
1232-
const with_exprt &with_expr=to_with_expr(expr);
1233-
const exprt &then_expr=with_expr.new_value();
1234-
exprt else_expr=substitute_array_with_expr(with_expr.old(), index);
1235-
const typet &type=then_expr.type();
1236-
CHECK_RETURN(else_expr.type()==type);
1237-
CHECK_RETURN(index.type()==with_expr.where().type());
1238-
return if_exprt(
1239-
equal_exprt(index, with_expr.where()), then_expr, else_expr, type);
1240-
}
1241-
else
1242-
{
1243-
// Only handle 'with' expressions and 'array_of' expressions.
1244-
INVARIANT(
1245-
expr.id()==ID_array_of,
1246-
string_refinement_invariantt("only handles 'with' and 'array_of' "
1247-
"expressions, and expr is 'with' is handled above"));
1248-
return to_array_of_expr(expr).what();
1241+
std::vector<std::pair<std::size_t, exprt>> entries;
1242+
std::reference_wrapper<const exprt> ref =
1243+
std::ref(static_cast<const exprt &>(expr));
1244+
while(can_cast_expr<with_exprt>(ref.get()))
1245+
{
1246+
const auto &with_expr = expr_dynamic_cast<with_exprt>(ref.get());
1247+
auto current_index = numeric_cast_v<std::size_t>(with_expr.where());
1248+
entries.push_back(std::make_pair(current_index, with_expr.new_value()));
1249+
ref = with_expr.old();
1250+
}
1251+
1252+
// This function only handles 'with' and 'array_of' expressions
1253+
PRECONDITION(ref.get().id() == ID_array_of);
1254+
1255+
// sort entries by increasing index
1256+
std::sort(
1257+
entries.begin(),
1258+
entries.end(),
1259+
[](
1260+
const std::pair<std::size_t, exprt> &a,
1261+
const std::pair<std::size_t, exprt> &b) { return a.first < b.first; });
1262+
1263+
exprt result = expr_dynamic_cast<array_of_exprt>(ref.get()).what();
1264+
for(const auto &entry : entries)
1265+
{
1266+
const exprt &then_expr = entry.second;
1267+
const typet &type = then_expr.type();
1268+
CHECK_RETURN(type == result.type());
1269+
const exprt entry_index = from_integer(entry.first, index.type());
1270+
if(left_propagate)
1271+
{
1272+
const binary_relation_exprt index_small_eq(index, ID_le, entry_index);
1273+
result = if_exprt(index_small_eq, then_expr, result, type);
1274+
}
1275+
else
1276+
result =
1277+
if_exprt(equal_exprt(index, entry_index), then_expr, result, type);
12491278
}
1279+
return result;
12501280
}
12511281

12521282
/// Fill an array represented by a list of with_expr by propagating values to
@@ -1257,9 +1287,8 @@ static exprt substitute_array_with_expr(const exprt &expr, const exprt &index)
12571287
/// \param string_max_length: bound on the length of strings
12581288
/// \return an array expression with filled in values, or expr if it is simply
12591289
/// an `ARRAY_OF(x)` expression
1260-
exprt fill_in_array_with_expr(
1261-
const exprt &expr,
1262-
const std::size_t string_max_length)
1290+
static array_exprt
1291+
fill_in_array_with_expr(const exprt &expr, const std::size_t string_max_length)
12631292
{
12641293
PRECONDITION(expr.type().id()==ID_array);
12651294
PRECONDITION(expr.id()==ID_with || expr.id()==ID_array_of);
@@ -1325,112 +1354,148 @@ exprt fill_in_array_expr(const array_exprt &expr, std::size_t string_max_length)
13251354
return result;
13261355
}
13271356

1328-
/// create an equivalent expression where array accesses and 'with' expressions
1329-
/// are replaced by 'if' expressions, in particular:
1330-
/// * for an array access `arr[x]`, where:
1331-
/// `arr := {12, 24, 48}` the constructed expression will be:
1357+
/// Create an equivalent expression where array accesses are replaced by 'if'
1358+
/// expressions: for instance in array access `arr[index]`, where:
1359+
/// `arr := {12, 24, 48}` the constructed expression will be:
13321360
/// `index==0 ? 12 : index==1 ? 24 : 48`
1333-
/// * for an array access `arr[x]`, where:
1334-
/// `arr := array_of(12) with {0:=24} with {2:=42}` the constructed
1335-
/// expression will be: `index==0 ? 24 : index==2 ? 42 : 12`
1336-
/// * for an array access `(g1?arr1:arr2)[x]` where `arr1 := {12}` and
1337-
/// `arr2 := {34}`, the constructed expression will be: `g1 ? 12 : 34`
1338-
/// * for an access in an empty array `{ }[x]` returns a fresh symbol, this
1339-
/// corresponds to a non-deterministic result
1340-
/// \param expr: an expression containing array accesses
1341-
/// \param symbol_generator: function which given a prefix and a type generates
1342-
/// a fresh symbol of the given type
1343-
/// \return an expression containing no array access
1344-
static void substitute_array_access(
1345-
exprt &expr,
1361+
static exprt substitute_array_access(
1362+
const array_exprt &array_expr,
1363+
const exprt &index,
13461364
const std::function<symbol_exprt(const irep_idt &, const typet &)>
13471365
&symbol_generator)
13481366
{
1349-
for(auto &op : expr.operands())
1350-
substitute_array_access(op, symbol_generator);
1367+
const typet &char_type = array_expr.type().subtype();
13511368

1352-
if(expr.id()==ID_index)
1353-
{
1354-
index_exprt &index_expr=to_index_expr(expr);
1369+
// Access to an empty array is undefined (non deterministic result)
1370+
if(array_expr.operands().empty())
1371+
return symbol_generator("out_of_bound_access", char_type);
13551372

1356-
if(index_expr.array().id()==ID_symbol)
1357-
{
1358-
expr=index_expr;
1359-
return;
1360-
}
1373+
const std::size_t last_index = array_expr.operands().size() - 1;
1374+
exprt ite = array_expr.operands().back();
13611375

1362-
if(index_expr.array().id()==ID_with)
1363-
{
1364-
expr=substitute_array_with_expr(index_expr.array(), index_expr.index());
1365-
return;
1366-
}
1376+
if(ite.type() != char_type)
1377+
{
1378+
// We have to manually set the type for unknown values
1379+
INVARIANT(
1380+
ite.id() == ID_unknown,
1381+
string_refinement_invariantt(
1382+
"the last element can only have type char "
1383+
"or unknown, and it is not char type"));
1384+
ite.type() = char_type;
1385+
}
13671386

1368-
if(index_expr.array().id()==ID_array_of)
1369-
{
1370-
expr=to_array_of_expr(index_expr.array()).op();
1371-
return;
1372-
}
1387+
auto op_it = ++array_expr.operands().rbegin();
13731388

1374-
if(index_expr.array().id()==ID_if)
1389+
for(std::size_t i = last_index - 1; op_it != array_expr.operands().rend();
1390+
++op_it, --i)
1391+
{
1392+
const equal_exprt equals(index, from_integer(i, java_int_type()));
1393+
if(op_it->type() != char_type)
13751394
{
1376-
// Substitute recursively in branches of conditional expressions
1377-
if_exprt if_expr=to_if_expr(index_expr.array());
1378-
exprt true_case=index_exprt(if_expr.true_case(), index_expr.index());
1379-
substitute_array_access(true_case, symbol_generator);
1380-
exprt false_case=index_exprt(if_expr.false_case(), index_expr.index());
1381-
substitute_array_access(false_case, symbol_generator);
1382-
expr=if_exprt(if_expr.cond(), true_case, false_case);
1383-
return;
1395+
INVARIANT(
1396+
op_it->id() == ID_unknown,
1397+
string_refinement_invariantt(
1398+
"elements can only have type char or "
1399+
"unknown, and it is not char type"));
1400+
ite = if_exprt(equals, exprt(ID_unknown, char_type), ite);
13841401
}
1402+
else
1403+
ite = if_exprt(equals, *op_it, ite);
1404+
}
1405+
return ite;
1406+
}
13851407

1386-
DATA_INVARIANT(
1387-
index_expr.array().id()==ID_array,
1388-
string_refinement_invariantt("and index expression must be on a symbol, "
1389-
"with, array_of, if, or array, and all cases besides array are handled "
1390-
"above"));
1391-
array_exprt &array_expr=to_array_expr(index_expr.array());
1392-
1393-
const typet &char_type = index_expr.array().type().subtype();
1408+
static exprt substitute_array_access(
1409+
const if_exprt &if_expr,
1410+
const exprt &index,
1411+
const std::function<symbol_exprt(const irep_idt &, const typet &)>
1412+
&symbol_generator,
1413+
const bool left_propagate)
1414+
{
1415+
// Substitute recursively in branches of conditional expressions
1416+
const exprt true_case = substitute_array_access(
1417+
index_exprt(if_expr.true_case(), index), symbol_generator, left_propagate);
1418+
const exprt false_case = substitute_array_access(
1419+
index_exprt(if_expr.false_case(), index), symbol_generator, left_propagate);
13941420

1395-
// Access to an empty array is undefined (non deterministic result)
1396-
if(array_expr.operands().empty())
1397-
{
1398-
expr = symbol_generator("out_of_bound_access", char_type);
1399-
return;
1400-
}
1421+
return if_exprt(if_expr.cond(), true_case, false_case);
1422+
}
14011423

1402-
size_t last_index=array_expr.operands().size()-1;
1424+
static exprt substitute_array_access(
1425+
const index_exprt &index_expr,
1426+
const std::function<symbol_exprt(const irep_idt &, const typet &)>
1427+
&symbol_generator,
1428+
const bool left_propagate)
1429+
{
1430+
const exprt &array = index_expr.array();
1431+
1432+
if(array.id() == ID_symbol)
1433+
return index_expr;
1434+
if(auto array_of = expr_try_dynamic_cast<array_of_exprt>(array))
1435+
return array_of->op();
1436+
if(auto array_with = expr_try_dynamic_cast<with_exprt>(array))
1437+
return substitute_array_access(
1438+
*array_with, index_expr.index(), left_propagate);
1439+
if(auto array_expr = expr_try_dynamic_cast<array_exprt>(array))
1440+
return substitute_array_access(
1441+
*array_expr, index_expr.index(), symbol_generator);
1442+
if(auto if_expr = expr_try_dynamic_cast<if_exprt>(array))
1443+
return substitute_array_access(
1444+
*if_expr, index_expr.index(), symbol_generator, left_propagate);
1445+
1446+
UNREACHABLE;
1447+
}
14031448

1404-
exprt ite=array_expr.operands().back();
1449+
/// Create an equivalent expression where array accesses and 'with' expressions
1450+
/// are replaced by 'if' expressions, in particular:
1451+
/// * for an array access `arr[index]`, where:
1452+
/// `arr := {12, 24, 48}` the constructed expression will be:
1453+
/// `index==0 ? 12 : index==1 ? 24 : 48`
1454+
/// * for an array access `arr[index]`, where:
1455+
/// `arr := array_of(12) with {0:=24} with {2:=42}` the constructed
1456+
/// expression will be: `index==0 ? 24 : index==2 ? 42 : 12`
1457+
/// * for an array access `(g1?arr1:arr2)[x]` where `arr1 := {12}` and
1458+
/// `arr2 := {34}`, the constructed expression will be: `g1 ? 12 : 34`
1459+
/// * for an access in an empty array `{ }[x]` returns a fresh symbol, this
1460+
/// corresponds to a non-deterministic result
1461+
/// Note that if left_propagate is set to true, the `with` case will result in
1462+
/// something like: `index <= 0 ? 24 : index <= 2 ? 42 : 12`
1463+
/// \param expr: an expression containing array accesses
1464+
/// \param symbol_generator: function which given a prefix and a type generates
1465+
/// a fresh symbol of the given type
1466+
/// \param left_propagate: should values be propagated to the left in with
1467+
/// expressions
1468+
/// \return an expression containing no array access, and a Boolean which is
1469+
/// true if the expression is unchanged
1470+
std::pair<exprt, bool> substitute_array_access(
1471+
const exprt &expr,
1472+
const std::function<symbol_exprt(const irep_idt &, const typet &)>
1473+
&symbol_generator,
1474+
const bool left_propagate)
1475+
{
1476+
if(const auto index_expr = expr_try_dynamic_cast<index_exprt>(expr))
1477+
{
1478+
const exprt substituted =
1479+
substitute_array_access(*index_expr, symbol_generator, left_propagate);
1480+
return {substituted, false};
1481+
}
14051482

1406-
if(ite.type()!=char_type)
1407-
{
1408-
// We have to manually set the type for unknown values
1409-
INVARIANT(
1410-
ite.id()==ID_unknown,
1411-
string_refinement_invariantt("the last element can only have type char "
1412-
"or unknown, and it is not char type"));
1413-
ite.type()=char_type;
1414-
}
1483+
exprt::operandst operands;
1484+
bool unchanged = true;
1485+
for(auto &op : expr.operands())
1486+
{
1487+
std::pair<exprt, bool> pair =
1488+
substitute_array_access(op, symbol_generator, left_propagate);
1489+
unchanged = unchanged && pair.second;
1490+
operands.push_back(pair.first);
1491+
}
14151492

1416-
auto op_it=++array_expr.operands().rbegin();
1493+
if(unchanged)
1494+
return {expr, true};
14171495

1418-
for(size_t i=last_index-1;
1419-
op_it!=array_expr.operands().rend(); ++op_it, --i)
1420-
{
1421-
equal_exprt equals(index_expr.index(), from_integer(i, java_int_type()));
1422-
if(op_it->type()!=char_type)
1423-
{
1424-
INVARIANT(
1425-
op_it->id()==ID_unknown,
1426-
string_refinement_invariantt("elements can only have type char or "
1427-
"unknown, and it is not char type"));
1428-
op_it->type()=char_type;
1429-
}
1430-
ite=if_exprt(equals, *op_it, ite);
1431-
}
1432-
expr=ite;
1433-
}
1496+
exprt copy(expr);
1497+
copy.operands() = std::move(operands);
1498+
return {copy, false};
14341499
}
14351500

14361501
/// Negates the constraint to be fed to a solver. The intended usage is to find
@@ -1657,12 +1722,10 @@ static std::pair<bool, std::vector<exprt>> check_axioms(
16571722

16581723
exprt negaxiom=negation_of_constraint(axiom_in_model);
16591724
negaxiom = simplify_expr(negaxiom, ns);
1660-
exprt with_concretized_arrays =
1661-
concretize_arrays_in_expression(negaxiom, max_string_length, ns);
1662-
1663-
substitute_array_access(with_concretized_arrays, gen_symbol);
16641725

16651726
stream << indent << i << ".\n";
1727+
const exprt with_concretized_arrays =
1728+
substitute_array_access(negaxiom, gen_symbol, true).first;
16661729
debug_check_axioms_step(
16671730
stream, ns, axiom, axiom_in_model, negaxiom, with_concretized_arrays);
16681731

@@ -1713,10 +1776,8 @@ static std::pair<bool, std::vector<exprt>> check_axioms(
17131776
negation_of_not_contains_constraint(nc_axiom_in_model, univ_var);
17141777

17151778
negaxiom = simplify_expr(negaxiom, ns);
1716-
exprt with_concrete_arrays =
1717-
concretize_arrays_in_expression(negaxiom, max_string_length, ns);
1718-
1719-
substitute_array_access(with_concrete_arrays, gen_symbol);
1779+
const exprt with_concrete_arrays =
1780+
substitute_array_access(negaxiom, gen_symbol, true).first;
17201781

17211782
stream << indent << i << ".\n";
17221783
debug_check_axioms_step(

src/solvers/refinement/string_refinement.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,11 @@ union_find_replacet string_identifiers_resolution_from_equations(
116116
const namespacet &ns,
117117
messaget::mstreamt &stream);
118118

119+
// Declaration required for unit-test:
120+
std::pair<exprt, bool> substitute_array_access(
121+
const exprt &expr,
122+
const std::function<symbol_exprt(const irep_idt &, const typet &)>
123+
&symbol_generator,
124+
const bool left_propagate);
125+
119126
#endif

0 commit comments

Comments
 (0)