Allow running Eldarica from the command line

This commit is contained in:
Leo Alt
2022-11-22 21:16:45 +01:00
parent be8ecb17d8
commit 24df40de9a
67 changed files with 386 additions and 3187 deletions
+4 -1
View File
@@ -149,10 +149,13 @@ set(sources
interface/Natspec.h
interface/OptimiserSettings.h
interface/ReadFile.h
interface/SMTSolverCommand.cpp
interface/SMTSolverCommand.h
interface/StandardCompiler.cpp
interface/StandardCompiler.h
interface/StorageLayout.cpp
interface/StorageLayout.h
interface/UniversalCallback.h
interface/Version.cpp
interface/Version.h
lsp/DocumentHoverHandler.cpp
@@ -181,5 +184,5 @@ set(sources
)
add_library(solidity ${sources})
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only)
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only Threads::Threads)
+5 -5
View File
@@ -18,6 +18,7 @@
#include <libsolidity/formal/BMC.h>
#include <libsolidity/formal/ModelChecker.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <libsmtutil/SMTPortfolio.h>
@@ -40,7 +41,7 @@ BMC::BMC(
UniqueErrorReporter& _errorReporter,
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
CharStreamProvider const& _charStreamProvider
):
SMTEncoder(_context, _settings, _errorReporter, _charStreamProvider),
@@ -61,15 +62,14 @@ BMC::BMC(
void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<VerificationTargetType>, smt::EncodingContext::IdCompare> _solvedTargets)
{
if (m_interface->solvers() == 0)
// At this point every enabled solver is available.
if (!m_settings.solvers.cvc4 && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3)
{
m_errorReporter.warning(
7710_error,
SourceLocation(),
"BMC analysis was not possible since no SMT solver was found and enabled."
#ifdef HAVE_Z3_DLOPEN
" Install libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION) + " to enable Z3."
#endif
" The accepted solvers for BMC are cvc4 and z3."
);
return;
}
+1 -1
View File
@@ -62,7 +62,7 @@ public:
langutil::UniqueErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
langutil::CharStreamProvider const& _charStreamProvider
);
+37 -30
View File
@@ -18,6 +18,8 @@
#include <libsolidity/formal/CHC.h>
#include <libsolidity/formal/ModelChecker.h>
#ifdef HAVE_Z3
#include <libsmtutil/Z3CHCInterface.h>
#endif
@@ -62,7 +64,7 @@ CHC::CHC(
UniqueErrorReporter& _errorReporter,
map<util::h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
CharStreamProvider const& _charStreamProvider
):
SMTEncoder(_context, _settings, _errorReporter, _charStreamProvider),
@@ -73,32 +75,30 @@ CHC::CHC(
void CHC::analyze(SourceUnit const& _source)
{
if (!shouldAnalyze(_source))
return;
bool usesZ3 = m_settings.solvers.z3;
#ifdef HAVE_Z3_DLOPEN
if (m_settings.solvers.z3 && !Z3Interface::available())
{
usesZ3 = false;
m_errorReporter.warning(
8158_error,
SourceLocation(),
"z3 was selected as a Horn solver for CHC analysis but libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION) + " was not found."
);
}
#endif
if (!usesZ3 && !m_settings.solvers.smtlib2)
// At this point every enabled solver is available.
if (!m_settings.solvers.eld && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3)
{
m_errorReporter.warning(
7649_error,
SourceLocation(),
"CHC analysis was not possible since no Horn solver was found and enabled."
" The accepted solvers for CHC are Eldarica and z3."
);
return;
}
if (m_settings.solvers.eld && m_settings.solvers.z3)
m_errorReporter.warning(
5798_error,
SourceLocation(),
"Multiple Horn solvers were selected for CHC."
" CHC only supports one solver at a time, therefore only z3 will be used."
" If you wish to use Eldarica, please enable Eldarica only."
);
if (!shouldAnalyze(_source))
return;
resetSourceAnalysis();
auto sources = sourceDependencies(_source);
@@ -115,7 +115,8 @@ void CHC::analyze(SourceUnit const& _source)
bool ranSolver = true;
// If ranSolver is true here it's because an SMT solver callback was
// actually given and the queries were solved.
// actually given and the queries were solved,
// or Eldarica was chosen and was present in the system.
if (auto const* smtLibInterface = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get()))
ranSolver = smtLibInterface->unhandledQueries().empty();
if (!ranSolver)
@@ -994,24 +995,27 @@ void CHC::resetSourceAnalysis()
ArraySlicePredicate::reset();
m_blockCounter = 0;
bool usesZ3 = false;
#ifdef HAVE_Z3
usesZ3 = m_settings.solvers.z3 && Z3Interface::available();
if (usesZ3)
// At this point every enabled solver is available.
// If more than one Horn solver is selected we go with z3.
// We still need the ifdef because of Z3CHCInterface.
if (m_settings.solvers.z3)
{
/// z3::fixedpoint does not have a reset mechanism, so we need to create another.
#ifdef HAVE_Z3
// z3::fixedpoint does not have a reset mechanism, so we need to create another.
m_interface = std::make_unique<Z3CHCInterface>(m_settings.timeout);
auto z3Interface = dynamic_cast<Z3CHCInterface const*>(m_interface.get());
solAssert(z3Interface, "");
m_context.setSolver(z3Interface->z3Interface());
}
#else
solAssert(false);
#endif
if (!usesZ3)
}
if (!m_settings.solvers.z3)
{
solAssert(m_settings.solvers.smtlib2);
solAssert(m_settings.solvers.smtlib2 || m_settings.solvers.eld);
if (!m_interface)
m_interface = make_unique<CHCSmtLib2Interface>(m_smtlib2Responses, m_smtCallback, m_settings.timeout);
m_interface = make_unique<CHCSmtLib2Interface>(m_smtlib2Responses, m_smtCallback, m_settings.solvers, m_settings.timeout);
auto smtlib2Interface = dynamic_cast<CHCSmtLib2Interface*>(m_interface.get());
solAssert(smtlib2Interface, "");
@@ -1551,9 +1555,10 @@ tuple<CheckResult, smtutil::Expression, CHCSolverInterface::CexGraph> CHC::query
{
case CheckResult::SATISFIABLE:
{
#ifdef HAVE_Z3
// We still need the ifdef because of Z3CHCInterface.
if (m_settings.solvers.z3)
{
#ifdef HAVE_Z3
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
// We now disable those optimizations and check whether we can still solve the problem.
auto* spacer = dynamic_cast<Z3CHCInterface*>(m_interface.get());
@@ -1569,8 +1574,10 @@ tuple<CheckResult, smtutil::Expression, CHCSolverInterface::CexGraph> CHC::query
cex = std::move(cexNoOpt);
spacer->setSpacerOptions(true);
}
#else
solAssert(false);
#endif
}
break;
}
case CheckResult::UNSATISFIABLE:
+1 -1
View File
@@ -59,7 +59,7 @@ public:
langutil::UniqueErrorReporter& _errorReporter,
std::map<util::h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
langutil::CharStreamProvider const& _charStreamProvider
);
+58 -5
View File
@@ -20,6 +20,13 @@
#ifdef HAVE_Z3
#include <libsmtutil/Z3Interface.h>
#endif
#ifdef HAVE_Z3_DLOPEN
#include <z3_version.h>
#endif
#if defined(__linux) || defined(__APPLE__)
#include <boost/process.hpp>
#endif
#include <range/v3/algorithm/any_of.hpp>
#include <range/v3/view.hpp>
@@ -29,6 +36,7 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;
using namespace solidity::smtutil;
ModelChecker::ModelChecker(
ErrorReporter& _errorReporter,
@@ -46,13 +54,11 @@ ModelChecker::ModelChecker(
}
// TODO This should be removed for 0.9.0.
void ModelChecker::enableAllEnginesIfPragmaPresent(vector<shared_ptr<SourceUnit>> const& _sources)
bool ModelChecker::isPragmaPresent(vector<shared_ptr<SourceUnit>> const& _sources)
{
bool hasPragma = ranges::any_of(_sources, [](auto _source) {
return ranges::any_of(_sources, [](auto _source) {
return _source && _source->annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker);
});
if (hasPragma)
m_settings.engine = ModelCheckerEngine::All();
}
void ModelChecker::checkRequestedSourcesAndContracts(vector<shared_ptr<SourceUnit>> const& _sources)
@@ -135,9 +141,12 @@ vector<string> ModelChecker::unhandledQueries()
return m_bmc.unhandledQueries() + m_chc.unhandledQueries();
}
solidity::smtutil::SMTSolverChoice ModelChecker::availableSolvers()
SMTSolverChoice ModelChecker::availableSolvers()
{
smtutil::SMTSolverChoice available = smtutil::SMTSolverChoice::SMTLIB2();
#if defined(__linux) || defined(__APPLE__)
available.eld = !boost::process::search_path("eld").empty();
#endif
#ifdef HAVE_Z3
available.z3 = solidity::smtutil::Z3Interface::available();
#endif
@@ -146,3 +155,47 @@ solidity::smtutil::SMTSolverChoice ModelChecker::availableSolvers()
#endif
return available;
}
SMTSolverChoice ModelChecker::checkRequestedSolvers(SMTSolverChoice _enabled, ErrorReporter& _errorReporter)
{
SMTSolverChoice availableSolvers{ModelChecker::availableSolvers()};
if (_enabled.cvc4 && !availableSolvers.cvc4)
{
_enabled.cvc4 = false;
_errorReporter.warning(
4902_error,
SourceLocation(),
"Solver CVC4 was selected for SMTChecker but it is not available."
);
}
if (_enabled.eld && !availableSolvers.eld)
{
_enabled.eld = false;
_errorReporter.warning(
4458_error,
SourceLocation(),
#if defined(__linux) || defined(__APPLE__)
"Solver Eldarica was selected for SMTChecker but it was not found in the system."
#else
"Solver Eldarica was selected for SMTChecker but it is only supported on Linux and MacOS."
#endif
);
}
if (_enabled.z3 && !availableSolvers.z3)
{
_enabled.z3 = false;
_errorReporter.warning(
8158_error,
SourceLocation(),
"Solver z3 was selected for SMTChecker but it is not available."
#ifdef HAVE_Z3_DLOPEN
" libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION) + " was not found."
#endif
);
}
return _enabled;
}
+5 -1
View File
@@ -58,7 +58,7 @@ public:
);
// TODO This should be removed for 0.9.0.
void enableAllEnginesIfPragmaPresent(std::vector<std::shared_ptr<SourceUnit>> const& _sources);
static bool isPragmaPresent(std::vector<std::shared_ptr<SourceUnit>> const& _sources);
/// Generates error messages if the requested sources and contracts
/// do not exist.
@@ -74,6 +74,10 @@ public:
/// @returns SMT solvers that are available via the C++ API.
static smtutil::SMTSolverChoice availableSolvers();
/// @returns the intersection of the enabled and available solvers,
/// reporting warnings when a solver is enabled but not available.
static smtutil::SMTSolverChoice checkRequestedSolvers(smtutil::SMTSolverChoice _enabled, langutil::ErrorReporter& _errorReporter);
private:
/// Error reporter from CompilerStack.
/// We need to append m_uniqueErrorReporter
+1 -1
View File
@@ -153,7 +153,7 @@ struct ModelCheckerSettings
ModelCheckerEngine engine = ModelCheckerEngine::None();
ModelCheckerInvariants invariants = ModelCheckerInvariants::Default();
bool showUnproved = false;
smtutil::SMTSolverChoice solvers = smtutil::SMTSolverChoice::All();
smtutil::SMTSolverChoice solvers = smtutil::SMTSolverChoice::Z3();
ModelCheckerTargets targets = ModelCheckerTargets::Default();
std::optional<unsigned> timeout;
+2 -2
View File
@@ -47,13 +47,13 @@ using namespace solidity::frontend;
SMTEncoder::SMTEncoder(
smt::EncodingContext& _context,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
UniqueErrorReporter& _errorReporter,
langutil::CharStreamProvider const& _charStreamProvider
):
m_errorReporter(_errorReporter),
m_context(_context),
m_settings(_settings),
m_settings(std::move(_settings)),
m_charStreamProvider(_charStreamProvider)
{
}
+2 -2
View File
@@ -54,7 +54,7 @@ class SMTEncoder: public ASTConstVisitor
public:
SMTEncoder(
smt::EncodingContext& _context,
ModelCheckerSettings const& _settings,
ModelCheckerSettings _settings,
langutil::UniqueErrorReporter& _errorReporter,
langutil::CharStreamProvider const& _charStreamProvider
);
@@ -483,7 +483,7 @@ protected:
/// Stores the context of the encoding.
smt::EncodingContext& m_context;
ModelCheckerSettings const& m_settings;
ModelCheckerSettings m_settings;
/// Character stream for each source,
/// used for retrieving source text of expressions for e.g. counter-examples.
+11 -2
View File
@@ -577,9 +577,18 @@ bool CompilerStack::analyze()
if (noErrors)
{
ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile);
// Run SMTChecker
auto allSources = util::applyMap(m_sourceOrder, [](Source const* _source) { return _source->ast; });
modelChecker.enableAllEnginesIfPragmaPresent(allSources);
if (ModelChecker::isPragmaPresent(allSources))
m_modelCheckerSettings.engine = ModelCheckerEngine::All();
// m_modelCheckerSettings is spread to engines and solver interfaces,
// so we need to check whether the enabled ones are available before building the classes.
if (m_modelCheckerSettings.engine.any())
m_modelCheckerSettings.solvers = ModelChecker::checkRequestedSolvers(m_modelCheckerSettings.solvers, m_errorReporter);
ModelChecker modelChecker(m_errorReporter, *this, m_smtlib2Responses, m_modelCheckerSettings, m_readFile);
modelChecker.checkRequestedSourcesAndContracts(allSources);
for (Source const* source: m_sourceOrder)
if (source->ast)
@@ -0,0 +1,85 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <libsolidity/interface/SMTSolverCommand.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/Exceptions.h>
#include <libsolutil/Keccak256.h>
#include <libsolutil/TemporaryDirectory.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/process.hpp>
using solidity::langutil::InternalCompilerError;
using solidity::util::errinfo_comment;
using namespace std;
namespace solidity::frontend
{
SMTSolverCommand::SMTSolverCommand(string _solverCmd) : m_solverCmd(_solverCmd) {}
ReadCallback::Result SMTSolverCommand::solve(string const& _kind, string const& _query)
{
try
{
if (_kind != ReadCallback::kindString(ReadCallback::Kind::SMTQuery))
solAssert(false, "SMTQuery callback used as callback kind " + _kind);
auto tempDir = solidity::util::TemporaryDirectory("smt");
util::h256 queryHash = util::keccak256(_query);
auto queryFileName = tempDir.path() / ("query_" + queryHash.hex() + ".smt2");
auto queryFile = boost::filesystem::ofstream(queryFileName);
queryFile << _query;
auto eldBin = boost::process::search_path(m_solverCmd);
if (eldBin.empty())
return ReadCallback::Result{false, m_solverCmd + " binary not found."};
boost::process::ipstream pipe;
boost::process::child eld(
eldBin,
queryFileName,
boost::process::std_out > pipe
);
vector<string> data;
string line;
while (eld.running() && std::getline(pipe, line))
if (!line.empty())
data.push_back(line);
eld.wait();
return ReadCallback::Result{true, boost::join(data, "\n")};
}
catch (...)
{
return ReadCallback::Result{false, "Unknown exception in SMTQuery callback: " + boost::current_exception_diagnostic_information()};
}
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libsolidity/interface/ReadFile.h>
#include <boost/filesystem.hpp>
namespace solidity::frontend
{
/// SMTSolverCommand wraps an SMT solver called via its binary in the OS.
class SMTSolverCommand
{
public:
SMTSolverCommand(std::string _solverCmd);
/// Calls an SMT solver with the given query.
frontend::ReadCallback::Result solve(std::string const& _kind, std::string const& _query);
frontend::ReadCallback::Callback solver()
{
return [this](std::string const& _kind, std::string const& _query) { return solve(_kind, _query); };
}
private:
/// The name of the solver's binary.
std::string const m_solverCmd;
};
}
+53
View File
@@ -0,0 +1,53 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#pragma once
#include <libsolidity/interface/FileReader.h>
#include <libsolidity/interface/SMTSolverCommand.h>
namespace solidity::frontend
{
/// UniversalCallback is used to wrap both FileReader and SMTSolverCommand
/// callbacks in a single callback. It uses the Kind of the callback to
/// determine which to call internally.
class UniversalCallback
{
public:
UniversalCallback(FileReader& _fileReader, SMTSolverCommand& _solver) :
m_fileReader{_fileReader},
m_solver{_solver}
{}
frontend::ReadCallback::Callback callback()
{
return [this](std::string const& _kind, std::string const& _data) -> ReadCallback::Result {
if (_kind == ReadCallback::kindString(ReadCallback::Kind::ReadFile))
return m_fileReader.readFile(_kind, _data);
else if (_kind == ReadCallback::kindString(ReadCallback::Kind::SMTQuery))
return m_solver.solve(_kind, _data);
solAssert(false, "Unknown callback kind.");
};
}
private:
FileReader& m_fileReader;
SMTSolverCommand& m_solver;
};
}