Add CallbackKind and use it for the SMT solver

This commit is contained in:
Leonardo Alt 2019-09-17 16:06:43 +02:00 committed by Alex Beregszaszi
parent 0201ff5a02
commit ddc478e3e4
24 changed files with 132 additions and 42 deletions

View File

@ -583,13 +583,18 @@ jobs:
t_ems_solcjs: t_ems_solcjs:
docker: docker:
- image: circleci/node:10 - image: ethereum/solidity-buildpack-deps:ubuntu1904
environment: environment:
TERM: xterm TERM: xterm
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
at: /tmp/workspace at: /tmp/workspace
- run:
name: Install test dependencies
command: |
apt-get update
apt-get install -qqy --no-install-recommends nodejs npm cvc4
- run: - run:
name: Test solcjs name: Test solcjs
command: | command: |

View File

@ -3,6 +3,7 @@
Breaking changes: Breaking changes:
* ABI: remove the deprecated ``constant`` and ``payable`` fields. * ABI: remove the deprecated ``constant`` and ``payable`` fields.
* ABI: the ``type`` field is now required and no longer specified to default to ``function``. * ABI: the ``type`` field is now required and no longer specified to default to ``function``.
* C API (``libsolc``): the provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries.
* Commandline interface: remove the text-based ast printer (``--ast``). * Commandline interface: remove the text-based ast printer (``--ast``).
* Command line interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter. * Command line interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter.
* Command line interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata. * Command line interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.

View File

@ -43,11 +43,11 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = n
ReadCallback::Callback readCallback; ReadCallback::Callback readCallback;
if (_readCallback) if (_readCallback)
{ {
readCallback = [=](string const& _path) readCallback = [=](string const& _kind, string const& _data)
{ {
char* contents_c = nullptr; char* contents_c = nullptr;
char* error_c = nullptr; char* error_c = nullptr;
_readCallback(_path.c_str(), &contents_c, &error_c); _readCallback(_kind.c_str(), _data.c_str(), &contents_c, &error_c);
ReadCallback::Result result; ReadCallback::Result result;
result.success = true; result.success = true;
if (!contents_c && !error_c) if (!contents_c && !error_c)

View File

@ -34,16 +34,17 @@
extern "C" { extern "C" {
#endif #endif
/// Callback used to retrieve additional source files. /// Callback used to retrieve additional source files or data.
/// ///
/// @param _path The path for loading. /// @param _kind The kind of callback (a string).
/// @param _data The data for the callback.
/// @param o_contents A pointer to the contents of the file, if found. /// @param o_contents A pointer to the contents of the file, if found.
/// @param o_error A pointer to an error message, if there is one. /// @param o_error A pointer to an error message, if there is one.
/// ///
/// If the callback is not supported, o_contents and o_error should be set to NULL. /// If the callback is not supported, o_contents and o_error should be set to NULL.
/// ///
/// The two pointers (o_contents and o_error) should be heap-allocated and are free'd by the caller. /// The two pointers (o_contents and o_error) should be heap-allocated and are free'd by the caller.
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); typedef void (*CStyleReadFileCallback)(char const* _kind, char const* _data, char** o_contents, char** o_error);
/// Returns the complete license document. /// Returns the complete license document.
/// ///
@ -58,8 +59,8 @@ char const* solidity_version() SOLC_NOEXCEPT;
/// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns /// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns
/// a "Standard Output JSON". Both are to be UTF-8 encoded. /// a "Standard Output JSON". Both are to be UTF-8 encoded.
/// ///
/// @param _input /// @param _input The input JSON to process.
/// @param _readCallback /// @param _readCallback The optional callback pointer. Can be NULL.
/// ///
/// @returns A pointer to the result. The pointer returned must not be freed by the caller. /// @returns A pointer to the result. The pointer returned must not be freed by the caller.
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT; char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT;

View File

@ -27,10 +27,14 @@ using namespace dev;
using namespace langutil; using namespace langutil;
using namespace dev::solidity; using namespace dev::solidity;
BMC::BMC(smt::EncodingContext& _context, ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses): BMC::BMC(
smt::EncodingContext& _context,
ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
):
SMTEncoder(_context), SMTEncoder(_context),
m_outerErrorReporter(_errorReporter), m_outerErrorReporter(_errorReporter),
m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses)) m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses, _smtCallback))
{ {
#if defined (HAVE_Z3) || defined (HAVE_CVC4) #if defined (HAVE_Z3) || defined (HAVE_CVC4)
if (!_smtlib2Responses.empty()) if (!_smtlib2Responses.empty())

View File

@ -53,7 +53,12 @@ namespace solidity
class BMC: public SMTEncoder class BMC: public SMTEncoder
{ {
public: public:
BMC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map<h256, std::string> const& _smtlib2Responses); BMC(
smt::EncodingContext& _context,
langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
);
void analyze(SourceUnit const& _sources, std::set<Expression const*> _safeAssertions); void analyze(SourceUnit const& _sources, std::set<Expression const*> _safeAssertions);

View File

@ -35,17 +35,19 @@ using namespace dev::solidity;
CHC::CHC( CHC::CHC(
smt::EncodingContext& _context, smt::EncodingContext& _context,
ErrorReporter& _errorReporter, ErrorReporter& _errorReporter,
map<h256, string> const& _smtlib2Responses map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
): ):
SMTEncoder(_context), SMTEncoder(_context),
#ifdef HAVE_Z3 #ifdef HAVE_Z3
m_interface(make_shared<smt::Z3CHCInterface>()), m_interface(make_shared<smt::Z3CHCInterface>()),
#else #else
m_interface(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses)), m_interface(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback)),
#endif #endif
m_outerErrorReporter(_errorReporter) m_outerErrorReporter(_errorReporter)
{ {
(void)_smtlib2Responses; (void)_smtlib2Responses;
(void)_smtCallback;
} }
void CHC::analyze(SourceUnit const& _source) void CHC::analyze(SourceUnit const& _source)

View File

@ -34,6 +34,8 @@
#include <libsolidity/formal/CHCSolverInterface.h> #include <libsolidity/formal/CHCSolverInterface.h>
#include <libsolidity/interface/ReadFile.h>
#include <set> #include <set>
namespace dev namespace dev
@ -47,7 +49,8 @@ public:
CHC( CHC(
smt::EncodingContext& _context, smt::EncodingContext& _context,
langutil::ErrorReporter& _errorReporter, langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
); );
void analyze(SourceUnit const& _sources); void analyze(SourceUnit const& _sources);

View File

@ -34,9 +34,13 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
CHCSmtLib2Interface::CHCSmtLib2Interface(map<h256, string> const& _queryResponses): CHCSmtLib2Interface::CHCSmtLib2Interface(
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses)), map<h256, string> const& _queryResponses,
m_queryResponses(_queryResponses) ReadCallback::Callback const& _smtCallback
):
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses, _smtCallback)),
m_queryResponses(_queryResponses),
m_smtCallback(_smtCallback)
{ {
reset(); reset();
} }
@ -152,9 +156,12 @@ string CHCSmtLib2Interface::querySolver(string const& _input)
h256 inputHash = dev::keccak256(_input); h256 inputHash = dev::keccak256(_input);
if (m_queryResponses.count(inputHash)) if (m_queryResponses.count(inputHash))
return m_queryResponses.at(inputHash); return m_queryResponses.at(inputHash);
else if (m_smtCallback)
{ {
m_unhandledQueries.push_back(_input); auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
return "unknown\n"; if (result.success)
return result.responseOrErrorMessage;
} }
m_unhandledQueries.push_back(_input);
return "unknown\n";
} }

View File

@ -35,7 +35,10 @@ namespace smt
class CHCSmtLib2Interface: public CHCSolverInterface class CHCSmtLib2Interface: public CHCSolverInterface
{ {
public: public:
explicit CHCSmtLib2Interface(std::map<h256, std::string> const& _queryResponses); explicit CHCSmtLib2Interface(
std::map<h256, std::string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
);
void reset(); void reset();
@ -68,6 +71,8 @@ private:
std::map<h256, std::string> const& m_queryResponses; std::map<h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries; std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
}; };
} }

View File

@ -22,9 +22,13 @@ using namespace dev;
using namespace langutil; using namespace langutil;
using namespace dev::solidity; using namespace dev::solidity;
ModelChecker::ModelChecker(ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses): ModelChecker::ModelChecker(
m_bmc(m_context, _errorReporter, _smtlib2Responses), ErrorReporter& _errorReporter,
m_chc(m_context, _errorReporter, _smtlib2Responses), map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
):
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback),
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback),
m_context() m_context()
{ {
} }

View File

@ -43,7 +43,11 @@ namespace solidity
class ModelChecker class ModelChecker
{ {
public: public:
ModelChecker(langutil::ErrorReporter& _errorReporter, std::map<h256, std::string> const& _smtlib2Responses); ModelChecker(
langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback = ReadCallback::Callback()
);
void analyze(SourceUnit const& _sources); void analyze(SourceUnit const& _sources);

View File

@ -34,8 +34,12 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
SMTLib2Interface::SMTLib2Interface(map<h256, string> const& _queryResponses): SMTLib2Interface::SMTLib2Interface(
m_queryResponses(_queryResponses) map<h256, string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
):
m_queryResponses(_queryResponses),
m_smtCallback(_smtCallback)
{ {
reset(); reset();
} }
@ -230,9 +234,12 @@ string SMTLib2Interface::querySolver(string const& _input)
h256 inputHash = dev::keccak256(_input); h256 inputHash = dev::keccak256(_input);
if (m_queryResponses.count(inputHash)) if (m_queryResponses.count(inputHash))
return m_queryResponses.at(inputHash); return m_queryResponses.at(inputHash);
else if (m_smtCallback)
{ {
m_unhandledQueries.push_back(_input); auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
return "unknown\n"; if (result.success)
return result.responseOrErrorMessage;
} }
m_unhandledQueries.push_back(_input);
return "unknown\n";
} }

View File

@ -41,7 +41,10 @@ namespace smt
class SMTLib2Interface: public SolverInterface, public boost::noncopyable class SMTLib2Interface: public SolverInterface, public boost::noncopyable
{ {
public: public:
explicit SMTLib2Interface(std::map<h256, std::string> const& _queryResponses); explicit SMTLib2Interface(
std::map<h256, std::string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
);
void reset() override; void reset() override;
@ -78,6 +81,8 @@ private:
std::map<h256, std::string> const& m_queryResponses; std::map<h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries; std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
}; };
} }

View File

@ -30,9 +30,12 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
SMTPortfolio::SMTPortfolio(map<h256, string> const& _smtlib2Responses) SMTPortfolio::SMTPortfolio(
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
)
{ {
m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses)); m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses, _smtCallback));
#ifdef HAVE_Z3 #ifdef HAVE_Z3
m_solvers.emplace_back(make_unique<smt::Z3Interface>()); m_solvers.emplace_back(make_unique<smt::Z3Interface>());
#endif #endif

View File

@ -42,7 +42,10 @@ namespace smt
class SMTPortfolio: public SolverInterface, public boost::noncopyable class SMTPortfolio: public SolverInterface, public boost::noncopyable
{ {
public: public:
SMTPortfolio(std::map<h256, std::string> const& _smtlib2Responses); SMTPortfolio(
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback
);
void reset() override; void reset() override;

View File

@ -389,7 +389,7 @@ bool CompilerStack::analyze()
if (noErrors) if (noErrors)
{ {
ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses); ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses, m_readFile);
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
modelChecker.analyze(*source->ast); modelChecker.analyze(*source->ast);
m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); m_unhandledSMTLib2Queries += modelChecker.unhandledQueries();
@ -886,7 +886,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
ReadCallback::Result result{false, string("File not supplied initially.")}; ReadCallback::Result result{false, string("File not supplied initially.")};
if (m_readFile) if (m_readFile)
result = m_readFile(importPath); result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), importPath);
if (result.success) if (result.success)
newSources[importPath] = result.responseOrErrorMessage; newSources[importPath] = result.responseOrErrorMessage;

View File

@ -107,7 +107,7 @@ public:
}; };
/// Creates a new compiler stack. /// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Must return /// @param _readFile callback used to read files for import statements. Must return
/// and must not emit exceptions. /// and must not emit exceptions.
explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()); explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback());

View File

@ -17,6 +17,8 @@
#pragma once #pragma once
#include <liblangutil/Exceptions.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <functional> #include <functional>
#include <string> #include <string>
@ -37,8 +39,27 @@ public:
std::string responseOrErrorMessage; std::string responseOrErrorMessage;
}; };
enum class Kind
{
ReadFile,
SMTQuery
};
static std::string kindString(Kind _kind)
{
switch (_kind)
{
case Kind::ReadFile:
return "source";
case Kind::SMTQuery:
return "smt-query";
default:
solAssert(false, "");
}
}
/// File reading or generic query callback. /// File reading or generic query callback.
using Callback = std::function<Result(std::string const&)>; using Callback = std::function<Result(std::string const&, std::string const&)>;
}; };
} }

View File

@ -555,7 +555,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
{ {
if (!url.isString()) if (!url.isString())
return formatFatalError("JSONError", "URL must be a string."); return formatFatalError("JSONError", "URL must be a string.");
ReadCallback::Result result = m_readFile(url.asString()); ReadCallback::Result result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), url.asString());
if (result.success) if (result.success)
{ {
if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage))

View File

@ -41,7 +41,7 @@ class StandardCompiler: boost::noncopyable
{ {
public: public:
/// Creates a new StandardCompiler. /// Creates a new StandardCompiler.
/// @param _readFile callback to used to read files for import statements. Must return /// @param _readFile callback used to read files for import statements. Must return
/// and must not emit exceptions. /// and must not emit exceptions.
explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()):
m_readFile(_readFile) m_readFile(_readFile)

View File

@ -848,10 +848,15 @@ Allowed options)",
bool CommandLineInterface::processInput() bool CommandLineInterface::processInput()
{ {
ReadCallback::Callback fileReader = [this](string const& _path) ReadCallback::Callback fileReader = [this](string const& _kind, string const& _path)
{ {
try try
{ {
if (_kind != ReadCallback::kindString(ReadCallback::Kind::ReadFile))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment(
"ReadFile callback used as callback kind " +
_kind
));
auto path = boost::filesystem::path(_path); auto path = boost::filesystem::path(_path);
auto canonicalPath = boost::filesystem::weakly_canonical(path); auto canonicalPath = boost::filesystem::weakly_canonical(path);
bool isAllowed = false; bool isAllowed = false;

View File

@ -46,6 +46,9 @@ function solcjs_test
printLog "Copying contracts..." printLog "Copying contracts..."
cp -Rf $SOLCJS_INPUT_DIR/DAO test/ cp -Rf $SOLCJS_INPUT_DIR/DAO test/
printLog "Copying SMTChecker tests..."
cp -Rf "$TEST_DIR"/test/libsolidity/smtCheckerTests test/
run_install install_fn run_install install_fn
# Update version (needed for some tests) # Update version (needed for some tests)

View File

@ -22,6 +22,7 @@
#include <string> #include <string>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <libdevcore/JSON.h> #include <libdevcore/JSON.h>
#include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <libsolc/libsolc.h> #include <libsolc/libsolc.h>
@ -137,9 +138,10 @@ BOOST_AUTO_TEST_CASE(with_callback)
)"; )";
CStyleReadFileCallback callback{ CStyleReadFileCallback callback{
[](char const* _path, char** o_contents, char** o_error) [](char const* _kind, char const* _path, char** o_contents, char** o_error)
{ {
// Caller frees the pointers. // Caller frees the pointers.
BOOST_CHECK(string(_kind) == ReadCallback::kindString(ReadCallback::Kind::ReadFile));
if (string(_path) == "found.sol") if (string(_path) == "found.sol")
{ {
static string content{"import \"missing.sol\"; contract B {}"}; static string content{"import \"missing.sol\"; contract B {}"};