Skip to content

Commit 57092b2

Browse files
author
Leonardo
authored
Merge pull request #11421 from ethereum/smt_solver_option
[SMTChecker] Solver option
2 parents 3349720 + 6c8ecfa commit 57092b2

File tree

53 files changed

+650
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+650
-92
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ jobs:
811811

812812
t_ems_solcjs:
813813
docker:
814-
- image: buildpack-deps:latest
814+
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
815815
environment:
816816
TERM: xterm
817817
steps:
@@ -822,7 +822,7 @@ jobs:
822822
name: Install test dependencies
823823
command: |
824824
apt-get update
825-
apt-get install -qqy --no-install-recommends nodejs npm cvc4
825+
apt-get install -qqy --no-install-recommends nodejs npm
826826
- run:
827827
name: Test solcjs
828828
no_output_timeout: 30m

docs/smtchecker.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,39 @@ calls assume the called code is unknown and can do anything.
558558
The CHC engine is much more powerful than BMC in terms of what it can prove,
559559
and might require more computing resources.
560560

561+
SMT and Horn solvers
562+
====================
563+
564+
The two engines detailed above use automated theorem provers as their logical
565+
backends. BMC uses an SMT solver, whereas CHC uses a Horn solver. Often the
566+
same tool can act as both, as seen in `z3 <https://github.com/Z3Prover/z3>`_,
567+
which is primarily an SMT solver and makes `Spacer
568+
<https://spacer.bitbucket.io/>`_ available as a Horn solver, and `Eldarica
569+
<https://github.com/uuverifiers/eldarica>`_ which does both.
570+
571+
The user can choose which solvers should be used, if available, via the CLI
572+
option ``--model-checker-solvers {all,cvc4,smtlib2,z3}`` or the JSON option
573+
``settings.modelChecker.solvers=[smtlib2,z3]``, where:
574+
575+
- ``cvc4`` is only available if the ``solc`` binary is compiled with it. Only BMC uses ``cvc4``.
576+
- ``smtlib2`` outputs SMT/Horn queries in the `smtlib2 <http://smtlib.cs.uiowa.edu/>`_ format.
577+
These can be used together with the compiler's `callback mechanism <https://github.com/ethereum/solc-js>`_ so that
578+
any solver binary from the system can be employed to synchronously return the results of the queries to the compiler.
579+
This is currently the only way to use Eldarica, for example, since it does not have a C++ API.
580+
This can be used by both BMC and CHC depending on which solvers are called.
581+
- ``z3`` is available
582+
583+
- if ``solc`` is compiled with it;
584+
- if a dynamic ``z3`` library of version 4.8.x is installed in a Linux system (from Solidity 0.7.6);
585+
- statically in ``soljson.js`` (from Solidity 0.6.9), that is, the Javascript binary of the compiler.
586+
587+
Since both BMC and CHC use ``z3``, and ``z3`` is available in a greater variety
588+
of environments, including in the browser, most users will almost never need to be
589+
concerned about this option. More advanced users might apply this option to try
590+
alternative solvers on more complex problems.
591+
592+
Please note that certain combinations of chosen engine and solver will lead to
593+
the SMTChecker doing nothing, for example choosing CHC and ``cvc4``.
561594

562595
*******************************
563596
Abstraction and False Positives

libsmtutil/SMTPortfolio.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ SMTPortfolio::SMTPortfolio(
4040
):
4141
SolverInterface(_queryTimeout)
4242
{
43-
m_solvers.emplace_back(make_unique<SMTLib2Interface>(move(_smtlib2Responses), move(_smtCallback), m_queryTimeout));
43+
if (_enabledSolvers.smtlib2)
44+
m_solvers.emplace_back(make_unique<SMTLib2Interface>(move(_smtlib2Responses), move(_smtCallback), m_queryTimeout));
4445
#ifdef HAVE_Z3
4546
if (_enabledSolvers.z3 && Z3Interface::available())
4647
m_solvers.emplace_back(make_unique<Z3Interface>(m_queryTimeout));
@@ -143,10 +144,11 @@ pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const&
143144
vector<string> SMTPortfolio::unhandledQueries()
144145
{
145146
// This code assumes that the constructor guarantees that
146-
// SmtLib2Interface is in position 0.
147-
smtAssert(!m_solvers.empty(), "");
148-
smtAssert(dynamic_cast<SMTLib2Interface*>(m_solvers.front().get()), "");
149-
return m_solvers.front()->unhandledQueries();
147+
// SmtLib2Interface is in position 0, if enabled.
148+
if (!m_solvers.empty())
149+
if (auto smtlib2 = dynamic_cast<SMTLib2Interface*>(m_solvers.front().get()))
150+
return smtlib2->unhandledQueries();
151+
return {};
150152
}
151153

152154
bool SMTPortfolio::solverAnswered(CheckResult result)

libsmtutil/SolverInterface.h

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
#include <libsolutil/Common.h>
2525

26+
#include <range/v3/view.hpp>
27+
2628
#include <cstdio>
2729
#include <map>
2830
#include <memory>
2931
#include <optional>
32+
#include <set>
3033
#include <string>
3134
#include <vector>
3235

@@ -36,16 +39,71 @@ namespace solidity::smtutil
3639
struct SMTSolverChoice
3740
{
3841
bool cvc4 = false;
42+
bool smtlib2 = false;
3943
bool z3 = false;
4044

41-
static constexpr SMTSolverChoice All() { return {true, true}; }
42-
static constexpr SMTSolverChoice CVC4() { return {true, false}; }
43-
static constexpr SMTSolverChoice Z3() { return {false, true}; }
44-
static constexpr SMTSolverChoice None() { return {false, false}; }
45+
static constexpr SMTSolverChoice All() { return {true, true, true}; }
46+
static constexpr SMTSolverChoice CVC4() { return {true, false, false}; }
47+
static constexpr SMTSolverChoice SMTLIB2() { return {false, true, false}; }
48+
static constexpr SMTSolverChoice Z3() { return {false, false, true}; }
49+
static constexpr SMTSolverChoice None() { return {false, false, false}; }
50+
51+
static std::optional<SMTSolverChoice> fromString(std::string const& _solvers)
52+
{
53+
SMTSolverChoice solvers;
54+
if (_solvers == "all")
55+
{
56+
smtAssert(solvers.setSolver("cvc4"), "");
57+
smtAssert(solvers.setSolver("smtlib2"), "");
58+
smtAssert(solvers.setSolver("z3"), "");
59+
}
60+
else
61+
for (auto&& s: _solvers | ranges::views::split(',') | ranges::to<std::vector<std::string>>())
62+
if (!solvers.setSolver(s))
63+
return {};
64+
65+
return solvers;
66+
}
67+
68+
SMTSolverChoice& operator&(SMTSolverChoice const& _other)
69+
{
70+
cvc4 &= _other.cvc4;
71+
smtlib2 &= _other.smtlib2;
72+
z3 &= _other.z3;
73+
return *this;
74+
}
75+
76+
SMTSolverChoice& operator&=(SMTSolverChoice const& _other)
77+
{
78+
return *this & _other;
79+
}
80+
81+
bool operator!=(SMTSolverChoice const& _other) const noexcept { return !(*this == _other); }
82+
83+
bool operator==(SMTSolverChoice const& _other) const noexcept
84+
{
85+
return cvc4 == _other.cvc4 &&
86+
smtlib2 == _other.smtlib2 &&
87+
z3 == _other.z3;
88+
}
89+
90+
bool setSolver(std::string const& _solver)
91+
{
92+
static std::set<std::string> const solvers{"cvc4", "smtlib2", "z3"};
93+
if (!solvers.count(_solver))
94+
return false;
95+
if (_solver == "cvc4")
96+
cvc4 = true;
97+
else if (_solver == "smtlib2")
98+
smtlib2 = true;
99+
else if (_solver == "z3")
100+
z3 = true;
101+
return true;
102+
}
45103

46104
bool none() { return !some(); }
47-
bool some() { return cvc4 || z3; }
48-
bool all() { return cvc4 && z3; }
105+
bool some() { return cvc4 || smtlib2 || z3; }
106+
bool all() { return cvc4 && smtlib2 && z3; }
49107
};
50108

51109
enum class CheckResult

libsolidity/formal/BMC.cpp

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,15 @@ BMC::BMC(
4040
ErrorReporter& _errorReporter,
4141
map<h256, string> const& _smtlib2Responses,
4242
ReadCallback::Callback const& _smtCallback,
43-
smtutil::SMTSolverChoice _enabledSolvers,
4443
ModelCheckerSettings const& _settings,
4544
CharStreamProvider const& _charStreamProvider
4645
):
4746
SMTEncoder(_context, _settings, _charStreamProvider),
48-
m_interface(make_unique<smtutil::SMTPortfolio>(_smtlib2Responses, _smtCallback, _enabledSolvers, _settings.timeout)),
47+
m_interface(make_unique<smtutil::SMTPortfolio>(_smtlib2Responses, _smtCallback, _settings.solvers, _settings.timeout)),
4948
m_outerErrorReporter(_errorReporter)
5049
{
5150
#if defined (HAVE_Z3) || defined (HAVE_CVC4)
52-
if (_enabledSolvers.some())
51+
if (m_settings.solvers.cvc4 || m_settings.solvers.z3)
5352
if (!_smtlib2Responses.empty())
5453
m_errorReporter.warning(
5554
5622_error,
@@ -63,6 +62,20 @@ BMC::BMC(
6362

6463
void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<VerificationTargetType>> _solvedTargets)
6564
{
65+
if (m_interface->solvers() == 0)
66+
{
67+
if (!m_noSolverWarning)
68+
{
69+
m_noSolverWarning = true;
70+
m_outerErrorReporter.warning(
71+
7710_error,
72+
SourceLocation(),
73+
"BMC analysis was not possible since no SMT solver was found and enabled."
74+
);
75+
}
76+
return;
77+
}
78+
6679
if (SMTEncoder::analyze(_source))
6780
{
6881
m_solvedTargets = move(_solvedTargets);
@@ -75,19 +88,23 @@ void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<Verificatio
7588
_source.accept(*this);
7689
}
7790

78-
solAssert(m_interface->solvers() > 0, "");
7991
// If this check is true, Z3 and CVC4 are not available
8092
// and the query answers were not provided, since SMTPortfolio
81-
// guarantees that SmtLib2Interface is the first solver.
82-
if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1)
93+
// guarantees that SmtLib2Interface is the first solver, if enabled.
94+
if (
95+
!m_interface->unhandledQueries().empty() &&
96+
m_interface->solvers() == 1 &&
97+
m_settings.solvers.smtlib2
98+
)
8399
{
84100
if (!m_noSolverWarning)
85101
{
86102
m_noSolverWarning = true;
87103
m_outerErrorReporter.warning(
88104
8084_error,
89105
SourceLocation(),
90-
"BMC analysis was not possible since no SMT solver (Z3 or CVC4) was found."
106+
"BMC analysis was not possible. No SMT solver (Z3 or CVC4) was available."
107+
" None of the installed solvers was enabled."
91108
#ifdef HAVE_Z3_DLOPEN
92109
" Install libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION) + " to enable Z3."
93110
#endif
@@ -916,16 +933,20 @@ void BMC::checkCondition(
916933
solAssert(!_callStack.empty(), "");
917934
std::ostringstream message;
918935
message << "BMC: " << _description << " happens here.";
936+
919937
std::ostringstream modelMessage;
920-
modelMessage << "Counterexample:\n";
921-
solAssert(values.size() == expressionNames.size(), "");
922-
map<string, string> sortedModel;
923-
for (size_t i = 0; i < values.size(); ++i)
924-
if (expressionsToEvaluate.at(i).name != values.at(i))
925-
sortedModel[expressionNames.at(i)] = values.at(i);
926-
927-
for (auto const& eval: sortedModel)
928-
modelMessage << " " << eval.first << " = " << eval.second << "\n";
938+
// Sometimes models have complex smtlib2 expressions that SMTLib2Interface fails to parse.
939+
if (values.size() == expressionNames.size())
940+
{
941+
modelMessage << "Counterexample:\n";
942+
map<string, string> sortedModel;
943+
for (size_t i = 0; i < values.size(); ++i)
944+
if (expressionsToEvaluate.at(i).name != values.at(i))
945+
sortedModel[expressionNames.at(i)] = values.at(i);
946+
947+
for (auto const& eval: sortedModel)
948+
modelMessage << " " << eval.first << " = " << eval.second << "\n";
949+
}
929950

930951
m_errorReporter.warning(
931952
_errorHappens,

libsolidity/formal/BMC.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class BMC: public SMTEncoder
6262
langutil::ErrorReporter& _errorReporter,
6363
std::map<h256, std::string> const& _smtlib2Responses,
6464
ReadCallback::Callback const& _smtCallback,
65-
smtutil::SMTSolverChoice _enabledSolvers,
6665
ModelCheckerSettings const& _settings,
6766
langutil::CharStreamProvider const& _charStreamProvider
6867
);

libsolidity/formal/CHC.cpp

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,26 +56,38 @@ CHC::CHC(
5656
ErrorReporter& _errorReporter,
5757
[[maybe_unused]] map<util::h256, string> const& _smtlib2Responses,
5858
[[maybe_unused]] ReadCallback::Callback const& _smtCallback,
59-
SMTSolverChoice _enabledSolvers,
6059
ModelCheckerSettings const& _settings,
6160
CharStreamProvider const& _charStreamProvider
6261
):
6362
SMTEncoder(_context, _settings, _charStreamProvider),
64-
m_outerErrorReporter(_errorReporter),
65-
m_enabledSolvers(_enabledSolvers)
63+
m_outerErrorReporter(_errorReporter)
6664
{
67-
bool usesZ3 = _enabledSolvers.z3;
65+
bool usesZ3 = m_settings.solvers.z3;
6866
#ifdef HAVE_Z3
6967
usesZ3 = usesZ3 && Z3Interface::available();
7068
#else
7169
usesZ3 = false;
7270
#endif
73-
if (!usesZ3)
71+
if (!usesZ3 && m_settings.solvers.smtlib2)
7472
m_interface = make_unique<CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback, m_settings.timeout);
7573
}
7674

7775
void CHC::analyze(SourceUnit const& _source)
7876
{
77+
if (!m_settings.solvers.z3 && !m_settings.solvers.smtlib2)
78+
{
79+
if (!m_noSolverWarning)
80+
{
81+
m_noSolverWarning = true;
82+
m_outerErrorReporter.warning(
83+
7649_error,
84+
SourceLocation(),
85+
"CHC analysis was not possible since no Horn solver was enabled."
86+
);
87+
}
88+
return;
89+
}
90+
7991
if (SMTEncoder::analyze(_source))
8092
{
8193
resetSourceAnalysis();
@@ -92,6 +104,8 @@ void CHC::analyze(SourceUnit const& _source)
92104
}
93105

94106
bool ranSolver = true;
107+
// If ranSolver is true here it's because an SMT solver callback was
108+
// actually given and the queries were solved.
95109
if (auto const* smtLibInterface = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get()))
96110
ranSolver = smtLibInterface->unhandledQueries().empty();
97111
if (!ranSolver && !m_noSolverWarning)
@@ -103,7 +117,8 @@ void CHC::analyze(SourceUnit const& _source)
103117
#ifdef HAVE_Z3_DLOPEN
104118
"CHC analysis was not possible since libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION) + " was not found."
105119
#else
106-
"CHC analysis was not possible since no integrated z3 SMT solver was found."
120+
"CHC analysis was not possible. No Horn solver was available."
121+
" None of the installed solvers was enabled."
107122
#endif
108123
);
109124
}
@@ -933,7 +948,7 @@ void CHC::resetSourceAnalysis()
933948

934949
bool usesZ3 = false;
935950
#ifdef HAVE_Z3
936-
usesZ3 = m_enabledSolvers.z3 && Z3Interface::available();
951+
usesZ3 = m_settings.solvers.z3 && Z3Interface::available();
937952
if (usesZ3)
938953
{
939954
/// z3::fixedpoint does not have a reset mechanism, so we need to create another.
@@ -1427,20 +1442,23 @@ pair<CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression c
14271442
case CheckResult::SATISFIABLE:
14281443
{
14291444
#ifdef HAVE_Z3
1430-
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
1431-
// We now disable those optimizations and check whether we can still solve the problem.
1432-
auto* spacer = dynamic_cast<Z3CHCInterface*>(m_interface.get());
1433-
solAssert(spacer, "");
1434-
spacer->setSpacerOptions(false);
1445+
if (m_settings.solvers.z3)
1446+
{
1447+
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
1448+
// We now disable those optimizations and check whether we can still solve the problem.
1449+
auto* spacer = dynamic_cast<Z3CHCInterface*>(m_interface.get());
1450+
solAssert(spacer, "");
1451+
spacer->setSpacerOptions(false);
14351452

1436-
CheckResult resultNoOpt;
1437-
CHCSolverInterface::CexGraph cexNoOpt;
1438-
tie(resultNoOpt, cexNoOpt) = m_interface->query(_query);
1453+
CheckResult resultNoOpt;
1454+
CHCSolverInterface::CexGraph cexNoOpt;
1455+
tie(resultNoOpt, cexNoOpt) = m_interface->query(_query);
14391456

1440-
if (resultNoOpt == CheckResult::SATISFIABLE)
1441-
cex = move(cexNoOpt);
1457+
if (resultNoOpt == CheckResult::SATISFIABLE)
1458+
cex = move(cexNoOpt);
14421459

1443-
spacer->setSpacerOptions(true);
1460+
spacer->setSpacerOptions(true);
1461+
}
14441462
#endif
14451463
break;
14461464
}

0 commit comments

Comments
 (0)