Skip to content

Commit bcf6f4f

Browse files
Merge pull request #7839 from thomasspriggs/tas/smt_empty_structs
Add empty struct support to incremental SMT decision procedure
2 parents a82e456 + ee6337f commit bcf6f4f

File tree

11 files changed

+114
-15
lines changed

11 files changed

+114
-15
lines changed

regression/cbmc/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ add_test_pl_profile(
3434
add_test_pl_profile(
3535
"cbmc-new-smt-backend"
3636
"$<TARGET_FILE:cbmc> --incremental-smt2-solver 'z3 --smt2 -in'"
37-
"-I;new-smt-backend;-s;new-smt-backend"
37+
"${gcc_only_string}-I;new-smt-backend;-s;new-smt-backend"
3838
"CORE"
3939
)
4040

regression/cbmc/Empty_struct1/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33

44
^EXIT=0$

regression/cbmc/Empty_struct2/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33

44
^EXIT=0$

regression/cbmc/Empty_struct3/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33
--json-ui
44
VERIFICATION FAILED

regression/cbmc/empty_compound_type1/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33

44
^EXIT=0$

regression/cbmc/empty_compound_type2/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33
--pointer-check
44
^EXIT=10$

regression/cbmc/empty_compound_type3/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE gcc-only
1+
CORE gcc-only new-smt-backend
22
main.c
33

44
^EXIT=10$

src/solvers/smt2_incremental/encoding/struct_encoding.cpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "struct_encoding.h"
44

5+
#include <util/arith_tools.h>
56
#include <util/bitvector_expr.h>
67
#include <util/bitvector_types.h>
78
#include <util/make_unique.h>
@@ -38,9 +39,14 @@ typet struct_encodingt::encode(typet type) const
3839
work_queue.pop();
3940
if(const auto struct_tag = type_try_dynamic_cast<struct_tag_typet>(current))
4041
{
41-
const auto width = (*boolbv_width)(*struct_tag);
42+
auto width = (*boolbv_width)(*struct_tag);
43+
// The bit vector theory of SMT disallows zero bit length length bit
44+
// vectors. C++ gives a minimum size for a struct (even an empty struct)
45+
// as being one byte; in order to ensure that structs have unique memory
46+
// locations. Therefore encoding empty structs as having 8 bits / 1 byte
47+
// is a reasonable solution in this case.
4248
if(width == 0)
43-
UNIMPLEMENTED_FEATURE("support for zero bit width structs.");
49+
width = 8;
4450
current = bv_typet{width};
4551
}
4652
if(const auto array = type_try_dynamic_cast<array_typet>(current))
@@ -102,11 +108,16 @@ static exprt encode(const with_exprt &with, const namespacet &ns)
102108
return struct_exprt{components, tag_type};
103109
}
104110

111+
/// Non-empty structs are flattened into a large bit vector using concatenation
112+
/// to express all the member operands of \p struct_expr. Empty structs are
113+
/// encoded as a zero byte. This has useful properties such as -
114+
/// * A zero byte is valid SMT, unlike zero length bit vectors.
115+
/// * Any two zero byte instances are always equal. This property would not
116+
/// be true of two instances of a non-det byte for instance.
105117
static exprt encode(const struct_exprt &struct_expr)
106118
{
107-
INVARIANT(
108-
!struct_expr.operands().empty(),
109-
"empty structs may not be encoded into SMT terms.");
119+
if(struct_expr.operands().empty())
120+
return from_integer(0, bv_typet{8});
110121
if(struct_expr.operands().size() == 1)
111122
return struct_expr.operands().front();
112123
return concatenation_exprt{struct_expr.operands(), struct_expr.type()};

src/solvers/smt2_incremental/smt2_incremental_decision_procedure.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -497,14 +497,15 @@ exprt smt2_incremental_decision_proceduret::get(const exprt &expr) const
497497
{
498498
return identifier_descriptor;
499499
}
500-
if(gather_dependent_expressions(expr).empty())
500+
const exprt lowered = lower(expr);
501+
if(gather_dependent_expressions(lowered).empty())
501502
{
502503
INVARIANT(
503-
objects_are_already_tracked(expr, object_map),
504+
objects_are_already_tracked(lowered, object_map),
504505
"Objects in expressions being read should already be tracked from "
505506
"point of being set/handled.");
506507
return ::convert_expr_to_smt(
507-
expr,
508+
lowered,
508509
object_map,
509510
pointer_sizes_map,
510511
object_size_function.make_application,

unit/solvers/smt2_incremental/encoding/struct_encoding.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,34 @@ TEST_CASE("decoding into struct expressions.", "[core][smt2_incremental]")
341341
REQUIRE(test.struct_encoding.decode(encoded, struct_tag) == expected);
342342
}
343343

344+
TEST_CASE("encoding of struct with no members", "[core][smt2_incremental]")
345+
{
346+
auto test = struct_encoding_test_environmentt::make();
347+
const struct_union_typet::componentst component_types{};
348+
const type_symbolt type_symbol{"emptyt", struct_typet{component_types}, ID_C};
349+
test.symbol_table.insert(type_symbol);
350+
const struct_tag_typet type_reference{type_symbol.name};
351+
const auto expected_type = bv_typet{8};
352+
const auto expected_zero_byte = from_integer(0, expected_type);
353+
SECTION("Type of empty struct")
354+
{
355+
REQUIRE(test.struct_encoding.encode(type_reference) == expected_type);
356+
}
357+
SECTION("Empty struct typed symbol expression")
358+
{
359+
const symbolt empty_value_symbol{"foo", type_reference, ID_C};
360+
test.symbol_table.insert(empty_value_symbol);
361+
const auto symbol_expr = empty_value_symbol.symbol_expr();
362+
const exprt expected_symbol = symbol_exprt{"foo", expected_type};
363+
REQUIRE(test.struct_encoding.encode(symbol_expr) == expected_symbol);
364+
}
365+
SECTION("Struct expression")
366+
{
367+
const struct_exprt empty{{}, type_reference};
368+
REQUIRE(test.struct_encoding.encode(empty) == expected_zero_byte);
369+
}
370+
}
371+
344372
TEST_CASE(
345373
"encoding of single member struct expressions",
346374
"[core][smt2_incremental]")

unit/solvers/smt2_incremental/smt2_incremental_decision_procedure.cpp

+59
Original file line numberDiff line numberDiff line change
@@ -1000,3 +1000,62 @@ TEST_CASE(
10001000

10011001
REQUIRE(returned == constant_exprt{"true", bool_typet{}});
10021002
}
1003+
1004+
TEST_CASE(
1005+
"smt2_incremental_decision_proceduret getting value of empty struct",
1006+
"[core][smt2_incremental]")
1007+
{
1008+
auto test = decision_procedure_test_environmentt::make();
1009+
const struct_union_typet::componentst component_types{};
1010+
const type_symbolt type_symbol{"emptyt", struct_typet{component_types}, ID_C};
1011+
test.symbol_table.insert(type_symbol);
1012+
const struct_tag_typet type_reference{type_symbol.name};
1013+
const symbolt foo{"foo", type_reference, ID_C};
1014+
test.symbol_table.insert(foo);
1015+
const symbolt bar{"bar", type_reference, ID_C};
1016+
test.symbol_table.insert(bar);
1017+
1018+
INFO("Sanity checking decision procedure and flushing size definitions");
1019+
test.mock_responses.push_front(smt_check_sat_responset{smt_sat_responset{}});
1020+
CHECK(test.procedure() == decision_proceduret::resultt::D_SATISFIABLE);
1021+
test.sent_commands.clear();
1022+
1023+
INFO("Defining an equality over empty structs");
1024+
const auto equality_expression =
1025+
test.procedure.handle(equal_exprt{foo.symbol_expr(), bar.symbol_expr()});
1026+
test.procedure.set_to(equality_expression, true);
1027+
const smt_identifier_termt expected_foo{"foo", smt_bit_vector_sortt{8}};
1028+
const smt_identifier_termt expected_bar{"bar", smt_bit_vector_sortt{8}};
1029+
const smt_identifier_termt expected_handle{"B0", smt_bool_sortt{}};
1030+
const smt_termt expected_equality =
1031+
smt_core_theoryt::equal(expected_foo, expected_bar);
1032+
const std::vector<smt_commandt> expected_problem_commands{
1033+
smt_declare_function_commandt{expected_foo, {}},
1034+
smt_declare_function_commandt{expected_bar, {}},
1035+
smt_define_function_commandt{"B0", {}, expected_equality},
1036+
smt_assert_commandt{expected_equality}};
1037+
REQUIRE(test.sent_commands == expected_problem_commands);
1038+
test.sent_commands.clear();
1039+
1040+
INFO("Ensuring decision procedure is in suitable state for getting output.");
1041+
const std::vector<smt_commandt> expected_check_commands{
1042+
smt_check_sat_commandt{}};
1043+
test.mock_responses.push_front(smt_check_sat_responset{smt_sat_responset{}});
1044+
REQUIRE(test.procedure() == decision_proceduret::resultt::D_SATISFIABLE);
1045+
CHECK(test.sent_commands == expected_check_commands);
1046+
test.sent_commands.clear();
1047+
1048+
INFO("Getting values involving empty structs.");
1049+
test.mock_responses.push_front(
1050+
smt_get_value_responset{{{expected_handle, smt_bool_literal_termt{true}}}});
1051+
CHECK(test.procedure.get(equality_expression) == true_exprt{});
1052+
test.mock_responses.push_front(smt_get_value_responset{
1053+
{{smt_identifier_termt{"foo", smt_bit_vector_sortt{8}},
1054+
smt_bit_vector_constant_termt{0, 8}}}});
1055+
const struct_exprt expected_empty{{}, type_reference};
1056+
CHECK(test.procedure.get(foo.symbol_expr()) == expected_empty);
1057+
const std::vector<smt_commandt> expected_get_commands{
1058+
smt_get_value_commandt{expected_handle},
1059+
smt_get_value_commandt{expected_foo}};
1060+
CHECK(test.sent_commands == expected_get_commands);
1061+
}

0 commit comments

Comments
 (0)