Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions regression/cbmc/smt2_logic_with_quantifiers/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
int main()
{
unsigned n;
__CPROVER_assume(n > 0 && n < 8);
int a[n];
__CPROVER_array_set(a, 0);
unsigned i;
__CPROVER_assume(i < n);
__CPROVER_assert(a[i] == 0, "zero");
return 0;
}
6 changes: 6 additions & 0 deletions regression/cbmc/smt2_logic_with_quantifiers/quantifier_free.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
int main()
{
unsigned x;
__CPROVER_assert(x + 1 != 0 || x == 0xffffffffu, "p");
return 0;
}
15 changes: 15 additions & 0 deletions regression/cbmc/smt2_logic_with_quantifiers/test.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CORE broken-smt-backend no-new-smt
main.c
--smt2 --outfile -
^\(set-logic AUFBV\)$
^\(assert \(forall
^EXIT=0$
^SIGNAL=0$
--
QF_AUFBV
--
The generic SMT2 encoding writes a zero-initialised array of symbolic size
(array_of without `as const') as a universally quantified assertion. A
logic named QF_... admits no quantifiers, so the declared logic is AUFBV;
z3 and cvc5 rejected the file ("logic does not support quantifiers").
broken-smt-backend: the test inspects the generic output only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CORE broken-smt-backend no-new-smt paths-lifo-expected-failure
quantifier_free.c
--smt2 --outfile -
^\(set-logic QF_AUFBV\)$
^EXIT=0$
^SIGNAL=0$
--
forall
--
Without a quantifier the declared logic stays quantifier-free.
With --paths lifo the assertion is decided during symbolic execution and no
formula is written, hence paths-lifo-expected-failure.
2 changes: 2 additions & 0 deletions regression/validate-trace-xml-schema/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# these tests dump the raw SMT2 output (using --outfile)
['array_of_bool_as_bitvec', 'test-smt2-outfile.desc'],
['deterministic-smt-output', 'test.desc'],
['smt2_logic_with_quantifiers', 'test.desc'],
['smt2_logic_with_quantifiers', 'test_quantifier_free.desc'],
['z3-lambda-unflatten', 'test.desc'],
# these tests expect input from stdin
['json-interface1', 'test_wrong_option.desc'],
Expand Down
6 changes: 4 additions & 2 deletions src/goto-checker/solver_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ solver_factoryt::get_smt2(smt2_dect::solvert solver)
std::string("Generated by CBMC ") + CBMC_VERSION,
"QF_AUFBV",
solver,
std::cout);
std::cout,
/*complete_problem=*/true);

if(options.get_bool_option("fpa"))
smt2_conv->use_FPA_theory = true;
Expand All @@ -565,7 +566,8 @@ solver_factoryt::get_smt2(smt2_dect::solvert solver)
std::string("Generated by CBMC ") + CBMC_VERSION,
"QF_AUFBV",
solver,
*out);
*out,
/*complete_problem=*/true);

if(options.get_bool_option("fpa"))
smt2_conv->use_FPA_theory = true;
Expand Down
36 changes: 32 additions & 4 deletions src/solvers/smt2/smt2_conv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,16 @@ smt2_convt::smt2_convt(
const std::string &_notes,
const std::string &_logic,
solvert _solver,
std::ostream &_out)
std::ostream &_out,
bool _complete_problem)
: ns(_ns),
out(_out),
final_out(_out),
// only solvers whose header declares the (quantifier-free) logic
defer_output(
_complete_problem && _solver != solvert::BITWUZLA &&
_solver != solvert::CPROVER_SMT2 && _solver != solvert::Z3 &&
_solver != solvert::CVC5 && _logic.rfind("QF_", 0) == 0),
out(defer_output ? deferred_output : _out),
benchmark(_benchmark),
notes(_notes),
logic(_logic),
Expand Down Expand Up @@ -133,7 +140,8 @@ smt2_convt::smt2_convt(
break;
}

write_header();
if(!defer_output)
write_header();
}

std::string smt2_convt::decision_procedure_text() const
Expand Down Expand Up @@ -164,8 +172,24 @@ tvt smt2_convt::l_get(literalt l) const
return tvt(boolean_assignment[l.var_no()]^l.sign());
}

void smt2_convt::flush_deferred_output()
{
if(!defer_output)
return;
if(!header_written)
{
if(quantifier_emitted && logic.rfind("QF_", 0) == 0)
logic = logic.substr(3);
write_header();
}
final_out << deferred_output.str();
deferred_output.str(std::string{});
}

void smt2_convt::write_header()
{
header_written = true;
std::ostream &out = final_out;
out << "; SMT 2" << "\n";

switch(solver)
Expand Down Expand Up @@ -297,7 +321,8 @@ decision_proceduret::resultt smt2_convt::dec_solve(const exprt &assumption)
assumptions.pop_back();
}

out.flush();
flush_deferred_output();
final_out.flush();
return decision_proceduret::resultt::D_ERROR;
}

Expand Down Expand Up @@ -2610,6 +2635,7 @@ void smt2_convt::convert_expr(const exprt &expr)
// NOLINTNEXTLINE(readability/throw)
throw "MathSAT does not support quantifiers";

quantifier_emitted = true;
if(quantifier_expr.id() == ID_forall)
out << "(forall ";
else if(quantifier_expr.id() == ID_exists)
Expand Down Expand Up @@ -5888,6 +5914,7 @@ void smt2_convt::find_symbols(const exprt &expr)
if(!is_zero_width(array_type.element_type(), ns))
{
// use a quantifier-based initialization instead of lambda
quantifier_emitted = true;
out << "(assert (forall ((i ";
convert_type(array_type.index_type());
out << ")) (= (select " << id << " i) ";
Expand Down Expand Up @@ -5926,6 +5953,7 @@ void smt2_convt::find_symbols(const exprt &expr)

out << "; the following is a substitute for lambda i . x(i)\n";
out << "; universally quantified initialization of the array\n";
quantifier_emitted = true;
out << "(assert (forall ((";
convert_expr(array_comprehension.arg());
out << " ";
Expand Down
36 changes: 34 additions & 2 deletions src/solvers/smt2/smt2_conv.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,32 @@ class smt2_convt : public stack_decision_proceduret
Z3
};

/// \param _ns: namespace for looking up symbols and types
/// \param _benchmark: benchmark name, for the header
/// \param _notes: notes, for the header
/// \param _logic: the SMT-LIB logic to declare
/// \param _solver: the solver the output is meant for
/// \param _out: the output stream
/// \param _complete_problem: the output is a complete problem, written
/// in one go when solving (and on destruction): a quantifier-free
/// logic declared in the header (`QF_...`) is then replaced by its
/// quantified form if the encoding emits a quantifier. Otherwise the
/// output is streamed as it is produced.
smt2_convt(
const namespacet &_ns,
const std::string &_benchmark,
const std::string &_notes,
const std::string &_logic,
solvert _solver,
std::ostream &_out);
std::ostream &_out,
bool _complete_problem = false);

~smt2_convt() override = default;
~smt2_convt() override
{
// output held back but never solved (e.g. every property simplified
// away) is still written
flush_deferred_output();
}

bool use_FPA_theory = false;
bool use_array_of_bool = false;
Expand Down Expand Up @@ -97,6 +114,17 @@ class smt2_convt : public stack_decision_proceduret

protected:
const namespacet &ns;
/// The stream given to the constructor. When the declared logic is
/// quantifier-free (`QF_...`) the output is held back in
/// `deferred_output` until it is complete enough to know whether a
/// quantifier was emitted (see `flush_deferred_output`).
std::ostream &final_out;
std::stringstream deferred_output;
const bool defer_output;
bool header_written = false;
/// A `forall` or `exists` was emitted (also as the substitute for
/// `array_of` / `array_comprehension` without `as const` or lambdas).
bool quantifier_emitted = false;
std::ostream &out;
std::string benchmark, notes, logic;
solvert solver;
Expand All @@ -112,6 +140,10 @@ class smt2_convt : public stack_decision_proceduret
resultt dec_solve(const exprt &) override;

void write_header();
/// Write the header -- with the logic made quantified if a quantifier was
/// emitted (an SMT-LIB logic named `QF_...` admits no quantifiers) -- and
/// the output held back so far to `final_out`.
void flush_deferred_output();
/// Writes the end of the SMT file to the `smt_convt::out` stream. These parts
/// of the output may be changed when using multiple rounds of solving. They
/// include the following parts of the output file -
Expand Down
2 changes: 2 additions & 0 deletions src/solvers/smt2/smt2_dec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ decision_proceduret::resultt smt2_dect::dec_solve(const exprt &assumption)
if(assumption.is_not_nil())
assumptions.push_back(convert(assumption));

flush_deferred_output();
cached_output << stringstream.str();
stringstream.str(std::string{});

write_footer();
flush_deferred_output();

if(assumption.is_not_nil())
assumptions.pop_back();
Expand Down
9 changes: 8 additions & 1 deletion src/solvers/smt2/smt2_dec.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ class smt2_dect : protected smt2_stringstreamt, public smt2_convt
solvert _solver,
const std::string &_solver_binary_or_empty,
message_handlert &_message_handler)
: smt2_convt(_ns, _benchmark, _notes, _logic, _solver, stringstream),
: smt2_convt(
_ns,
_benchmark,
_notes,
_logic,
_solver,
stringstream,
/*complete_problem=*/true),
solver_binary_or_empty(_solver_binary_or_empty),
message_handler(_message_handler)
{
Expand Down
Loading