2019-09-24 15:35:31 +00:00
|
|
|
/*
|
|
|
|
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/>.
|
|
|
|
*/
|
2020-07-17 14:54:12 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
2019-09-24 15:35:31 +00:00
|
|
|
|
2020-05-18 15:42:24 +00:00
|
|
|
#include <libsmtutil/CHCSmtLib2Interface.h>
|
2019-09-24 15:35:31 +00:00
|
|
|
|
2020-01-06 10:52:23 +00:00
|
|
|
#include <libsolutil/Keccak256.h>
|
2023-07-16 10:56:40 +00:00
|
|
|
#include <libsolutil/StringUtils.h>
|
|
|
|
#include <libsolutil/Visitor.h>
|
|
|
|
|
2023-07-21 15:14:29 +00:00
|
|
|
#include <liblangutil/Common.h>
|
2023-07-16 10:56:40 +00:00
|
|
|
|
2019-09-24 15:35:31 +00:00
|
|
|
|
|
|
|
#include <boost/algorithm/string/join.hpp>
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
|
2021-05-20 16:07:40 +00:00
|
|
|
#include <range/v3/view.hpp>
|
2023-07-21 13:40:00 +00:00
|
|
|
#include <range/v3/algorithm/find.hpp>
|
|
|
|
#include <range/v3/algorithm/find_if.hpp>
|
2021-05-20 16:07:40 +00:00
|
|
|
|
2019-09-24 15:35:31 +00:00
|
|
|
#include <array>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <memory>
|
2023-07-16 10:56:40 +00:00
|
|
|
#include <stack>
|
2019-09-24 15:35:31 +00:00
|
|
|
#include <stdexcept>
|
2023-07-16 10:56:40 +00:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <variant>
|
2019-09-24 15:35:31 +00:00
|
|
|
|
2019-12-11 16:31:36 +00:00
|
|
|
using namespace solidity;
|
|
|
|
using namespace solidity::util;
|
|
|
|
using namespace solidity::frontend;
|
2023-07-16 10:56:40 +00:00
|
|
|
using namespace solidity::langutil;
|
2020-05-19 12:14:46 +00:00
|
|
|
using namespace solidity::smtutil;
|
2019-09-24 15:35:31 +00:00
|
|
|
|
2019-09-17 14:06:43 +00:00
|
|
|
CHCSmtLib2Interface::CHCSmtLib2Interface(
|
2023-07-12 12:09:19 +00:00
|
|
|
std::map<h256, std::string> const& _queryResponses,
|
2020-11-02 20:20:20 +00:00
|
|
|
ReadCallback::Callback _smtCallback,
|
2022-05-15 15:45:43 +00:00
|
|
|
SMTSolverChoice _enabledSolvers,
|
2023-07-12 12:09:19 +00:00
|
|
|
std::optional<unsigned> _queryTimeout
|
2019-09-17 14:06:43 +00:00
|
|
|
):
|
2020-11-02 20:20:20 +00:00
|
|
|
CHCSolverInterface(_queryTimeout),
|
2023-06-26 08:31:02 +00:00
|
|
|
m_smtlib2(std::make_unique<SMTLib2Interface>(_queryResponses, _smtCallback, _enabledSolvers, m_queryTimeout)),
|
2022-08-23 17:28:45 +00:00
|
|
|
m_queryResponses(std::move(_queryResponses)),
|
2022-05-15 15:45:43 +00:00
|
|
|
m_smtCallback(_smtCallback),
|
|
|
|
m_enabledSolvers(_enabledSolvers)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CHCSmtLib2Interface::reset()
|
|
|
|
{
|
|
|
|
m_accumulatedOutput.clear();
|
|
|
|
m_variables.clear();
|
2020-09-02 08:45:47 +00:00
|
|
|
m_unhandledQueries.clear();
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
|
|
|
|
2020-05-19 12:14:46 +00:00
|
|
|
void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2021-09-23 15:18:13 +00:00
|
|
|
smtAssert(_expr.sort);
|
|
|
|
smtAssert(_expr.sort->kind == Kind::Function);
|
2019-09-24 15:35:31 +00:00
|
|
|
if (!m_variables.count(_expr.name))
|
|
|
|
{
|
2023-07-12 12:09:19 +00:00
|
|
|
auto fSort = std::dynamic_pointer_cast<FunctionSort>(_expr.sort);
|
|
|
|
std::string domain = toSmtLibSort(fSort->domain);
|
2019-09-24 15:35:31 +00:00
|
|
|
// Relations are predicates which have implicit codomain Bool.
|
|
|
|
m_variables.insert(_expr.name);
|
|
|
|
write(
|
2021-05-18 17:12:06 +00:00
|
|
|
"(declare-fun |" +
|
2019-09-24 15:35:31 +00:00
|
|
|
_expr.name +
|
|
|
|
"| " +
|
|
|
|
domain +
|
2021-05-18 17:12:06 +00:00
|
|
|
" Bool)"
|
2019-09-24 15:35:31 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-18 17:12:06 +00:00
|
|
|
void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& /*_name*/)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
|
|
|
write(
|
2021-05-18 17:12:06 +00:00
|
|
|
"(assert\n(forall " + forall() + "\n" +
|
2019-09-24 15:35:31 +00:00
|
|
|
m_smtlib2->toSExpr(_expr) +
|
2021-05-18 17:12:06 +00:00
|
|
|
"))\n\n"
|
2019-09-24 15:35:31 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
std::tuple<CheckResult, Expression, CHCSolverInterface::CexGraph> CHCSmtLib2Interface::query(Expression const& _block)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string query = dumpQuery(_block);
|
|
|
|
std::string response = querySolver(query);
|
2019-09-24 15:35:31 +00:00
|
|
|
|
|
|
|
CheckResult result;
|
|
|
|
// TODO proper parsing
|
2021-05-18 17:12:06 +00:00
|
|
|
if (boost::starts_with(response, "sat"))
|
2019-09-24 15:35:31 +00:00
|
|
|
result = CheckResult::UNSATISFIABLE;
|
2023-07-21 15:14:29 +00:00
|
|
|
else if (boost::starts_with(response, "unsat"))
|
|
|
|
{
|
2021-05-18 17:12:06 +00:00
|
|
|
result = CheckResult::SATISFIABLE;
|
2023-07-16 10:56:40 +00:00
|
|
|
return {result, Expression(true), graphFromZ3Proof(response)};
|
|
|
|
}
|
2021-05-18 17:12:06 +00:00
|
|
|
else if (boost::starts_with(response, "unknown"))
|
2019-09-24 15:35:31 +00:00
|
|
|
result = CheckResult::UNKNOWN;
|
|
|
|
else
|
|
|
|
result = CheckResult::ERROR;
|
|
|
|
|
2021-10-06 09:44:33 +00:00
|
|
|
return {result, Expression(true), {}};
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
void CHCSmtLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2021-09-23 15:18:13 +00:00
|
|
|
smtAssert(_sort);
|
2019-09-24 15:35:31 +00:00
|
|
|
if (_sort->kind == Kind::Function)
|
|
|
|
declareFunction(_name, _sort);
|
|
|
|
else if (!m_variables.count(_name))
|
|
|
|
{
|
|
|
|
m_variables.insert(_name);
|
2021-05-18 17:12:06 +00:00
|
|
|
write("(declare-var |" + _name + "| " + toSmtLibSort(*_sort) + ')');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string CHCSmtLib2Interface::toSmtLibSort(Sort const& _sort)
|
2021-05-18 17:12:06 +00:00
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
return m_smtlib2->toSmtLibSort(_sort);
|
2021-05-18 17:12:06 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string CHCSmtLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts)
|
2021-05-18 17:12:06 +00:00
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
return m_smtlib2->toSmtLibSort(_sorts);
|
2021-05-18 17:12:06 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string CHCSmtLib2Interface::forall()
|
2021-05-18 17:12:06 +00:00
|
|
|
{
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string vars("(");
|
2021-05-18 17:12:06 +00:00
|
|
|
for (auto const& [name, sort]: m_smtlib2->variables())
|
|
|
|
{
|
|
|
|
solAssert(sort, "");
|
|
|
|
if (sort->kind != Kind::Function)
|
|
|
|
vars += " (" + name + " " + toSmtLibSort(*sort) + ")";
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
2021-05-18 17:12:06 +00:00
|
|
|
vars += ")";
|
|
|
|
return vars;
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
void CHCSmtLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2021-09-23 15:18:13 +00:00
|
|
|
smtAssert(_sort);
|
|
|
|
smtAssert(_sort->kind == Kind::Function);
|
2019-09-24 15:35:31 +00:00
|
|
|
// TODO Use domain and codomain as key as well
|
|
|
|
if (!m_variables.count(_name))
|
|
|
|
{
|
2023-07-12 12:09:19 +00:00
|
|
|
auto fSort = std::dynamic_pointer_cast<FunctionSort>(_sort);
|
2021-09-23 15:18:13 +00:00
|
|
|
smtAssert(fSort->codomain);
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string domain = toSmtLibSort(fSort->domain);
|
|
|
|
std::string codomain = toSmtLibSort(*fSort->codomain);
|
2019-09-24 15:35:31 +00:00
|
|
|
m_variables.insert(_name);
|
|
|
|
write(
|
|
|
|
"(declare-fun |" +
|
|
|
|
_name +
|
|
|
|
"| " +
|
|
|
|
domain +
|
|
|
|
" " +
|
|
|
|
codomain +
|
|
|
|
")"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
void CHCSmtLib2Interface::write(std::string _data)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2022-08-23 17:28:45 +00:00
|
|
|
m_accumulatedOutput += std::move(_data) + "\n";
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:09:19 +00:00
|
|
|
std::string CHCSmtLib2Interface::querySolver(std::string const& _input)
|
2019-09-24 15:35:31 +00:00
|
|
|
{
|
2019-12-11 16:31:36 +00:00
|
|
|
util::h256 inputHash = util::keccak256(_input);
|
2019-09-24 15:35:31 +00:00
|
|
|
if (m_queryResponses.count(inputHash))
|
|
|
|
return m_queryResponses.at(inputHash);
|
2022-05-15 15:45:43 +00:00
|
|
|
|
2023-06-26 11:41:17 +00:00
|
|
|
smtAssert(m_enabledSolvers.eld || m_enabledSolvers.z3);
|
2023-06-20 14:10:45 +00:00
|
|
|
smtAssert(m_smtCallback, "Callback must be set!");
|
|
|
|
std::string solverBinary = [&](){
|
|
|
|
if (m_enabledSolvers.eld)
|
|
|
|
return "eld";
|
|
|
|
if (m_enabledSolvers.z3)
|
2023-07-03 14:09:25 +00:00
|
|
|
return "z3 rlimit=1000000 fp.spacer.q3.use_qgen=true fp.spacer.mbqi=false fp.spacer.ground_pobs=false";
|
2023-06-20 14:10:45 +00:00
|
|
|
return "";
|
|
|
|
}();
|
|
|
|
auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery) + " " + solverBinary, _input);
|
|
|
|
if (result.success)
|
2023-07-03 14:47:59 +00:00
|
|
|
{
|
2023-07-21 15:14:29 +00:00
|
|
|
if (m_enabledSolvers.z3 and boost::starts_with(result.responseOrErrorMessage, "unsat"))
|
|
|
|
{
|
2023-07-03 14:47:59 +00:00
|
|
|
solverBinary += " fp.xform.slice=false fp.xform.inline_linear=false fp.xform.inline_eager=false";
|
2023-07-15 12:56:18 +00:00
|
|
|
std::string extendedQuery = "(set-option :produce-proofs true)" + _input + "\n(get-proof)";
|
2023-07-03 14:47:59 +00:00
|
|
|
auto secondResult = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery) + " " + solverBinary, extendedQuery);
|
|
|
|
if (secondResult.success)
|
|
|
|
return secondResult.responseOrErrorMessage;
|
|
|
|
}
|
2023-06-20 14:10:45 +00:00
|
|
|
return result.responseOrErrorMessage;
|
2023-07-03 14:47:59 +00:00
|
|
|
}
|
2022-05-15 15:45:43 +00:00
|
|
|
|
2019-09-17 14:06:43 +00:00
|
|
|
m_unhandledQueries.push_back(_input);
|
|
|
|
return "unknown\n";
|
2019-09-24 15:35:31 +00:00
|
|
|
}
|
2023-04-26 10:50:36 +00:00
|
|
|
|
|
|
|
std::string CHCSmtLib2Interface::dumpQuery(Expression const& _expr)
|
|
|
|
{
|
|
|
|
std::stringstream s;
|
|
|
|
|
|
|
|
s
|
|
|
|
<< createHeaderAndDeclarations()
|
|
|
|
<< m_accumulatedOutput << std::endl
|
|
|
|
<< createQueryAssertion(_expr.name) << std::endl
|
|
|
|
<< "(check-sat)" << std::endl;
|
|
|
|
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CHCSmtLib2Interface::createHeaderAndDeclarations() {
|
|
|
|
std::stringstream s;
|
|
|
|
if (m_queryTimeout)
|
2023-07-12 12:09:19 +00:00
|
|
|
s << "(set-option :timeout " + std::to_string(*m_queryTimeout) + ")\n";
|
2023-04-26 10:50:36 +00:00
|
|
|
s << "(set-logic HORN)" << std::endl;
|
|
|
|
|
|
|
|
for (auto const& decl: m_smtlib2->userSorts() | ranges::views::values)
|
|
|
|
s << decl << std::endl;
|
|
|
|
|
|
|
|
return s.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CHCSmtLib2Interface::createQueryAssertion(std::string name) {
|
|
|
|
return "(assert\n(forall " + forall() + "\n" + "(=> " + name + " false)))";
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
|
|
|
|
std::string CHCSmtLib2Interface::SMTLib2Expression::toString() const
|
|
|
|
{
|
|
|
|
return std::visit(GenericVisitor{
|
|
|
|
[](std::string const& _sv) { return _sv; },
|
|
|
|
[](std::vector<SMTLib2Expression> const& _subExpr) {
|
|
|
|
std::vector<std::string> formatted;
|
|
|
|
for (auto const& item: _subExpr)
|
|
|
|
formatted.emplace_back(item.toString());
|
|
|
|
return "(" + joinHumanReadable(formatted, " ") + ")";
|
|
|
|
}
|
|
|
|
}, data);
|
|
|
|
}
|
|
|
|
|
2023-07-21 15:14:29 +00:00
|
|
|
namespace
|
|
|
|
{
|
2023-07-16 10:56:40 +00:00
|
|
|
using SMTLib2Expression = CHCSmtLib2Interface::SMTLib2Expression;
|
|
|
|
bool isNumber(std::string const& _expr)
|
|
|
|
{
|
|
|
|
for (char c: _expr)
|
|
|
|
if (!isDigit(c) && c != '.')
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isAtom(SMTLib2Expression const & expr)
|
|
|
|
{
|
|
|
|
return std::holds_alternative<std::string>(expr.data);
|
2023-07-21 15:14:29 +00:00
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
|
|
|
|
std::string const& asAtom(SMTLib2Expression const& expr)
|
|
|
|
{
|
|
|
|
assert(isAtom(expr));
|
|
|
|
return std::get<std::string>(expr.data);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto const& asSubExpressions(SMTLib2Expression const& expr)
|
|
|
|
{
|
|
|
|
assert(!isAtom(expr));
|
|
|
|
return std::get<SMTLib2Expression::args_t>(expr.data);
|
|
|
|
}
|
|
|
|
|
2023-07-21 13:40:00 +00:00
|
|
|
class SMTLibTranslationContext
|
2023-07-16 10:56:40 +00:00
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
SMTLib2Interface const& m_smtlib2Interface;
|
|
|
|
|
|
|
|
public:
|
|
|
|
SMTLibTranslationContext(SMTLib2Interface const& _smtlib2Interface) : m_smtlib2Interface(_smtlib2Interface) {}
|
|
|
|
|
|
|
|
SortPointer toSort(SMTLib2Expression const& expr)
|
|
|
|
{
|
2023-07-21 15:14:29 +00:00
|
|
|
if (isAtom(expr))
|
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
auto const& name = asAtom(expr);
|
|
|
|
if (name == "Int")
|
|
|
|
return SortProvider::sintSort;
|
|
|
|
if (name == "Bool")
|
|
|
|
return SortProvider::boolSort;
|
|
|
|
std::string quotedName = "|" + name + "|";
|
|
|
|
auto it = ranges::find_if(m_smtlib2Interface.sortNames(), [&](auto const& entry) {
|
|
|
|
return entry.second == name || entry.second == quotedName;
|
|
|
|
});
|
2023-07-26 16:28:04 +00:00
|
|
|
if (it != m_smtlib2Interface.sortNames().end()) {
|
|
|
|
if (it->first->kind == Kind::Tuple) {
|
|
|
|
auto const* tupleSort = dynamic_cast<TupleSort const*>(it->first);
|
|
|
|
smtAssert(tupleSort);
|
|
|
|
// TODO: This is cumbersome, we should really store shared_pointer instead of raw pointer in the sortNames
|
|
|
|
return std::make_shared<TupleSort>(tupleSort->name, tupleSort->members, tupleSort->components);
|
|
|
|
}
|
|
|
|
}
|
2023-07-21 13:40:00 +00:00
|
|
|
} else {
|
|
|
|
auto const& args = asSubExpressions(expr);
|
2023-07-21 15:14:29 +00:00
|
|
|
if (asAtom(args[0]) == "Array")
|
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
assert(args.size() == 3);
|
|
|
|
auto domainSort = toSort(args[1]);
|
|
|
|
auto codomainSort = toSort(args[2]);
|
|
|
|
return std::make_shared<ArraySort>(std::move(domainSort), std::move(codomainSort));
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
2023-07-21 13:40:00 +00:00
|
|
|
// FIXME: This is not correct, we need to track sorts properly!
|
|
|
|
// return SortProvider::boolSort;
|
2023-07-21 15:14:29 +00:00
|
|
|
smtAssert(false, "Unknown sort encountered");
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 13:40:00 +00:00
|
|
|
smtutil::Expression toSMTUtilExpression(SMTLib2Expression const& _expr)
|
|
|
|
{
|
|
|
|
return std::visit(GenericVisitor{
|
|
|
|
[&](std::string const& _atom) {
|
|
|
|
if (_atom == "true" || _atom == "false")
|
|
|
|
return smtutil::Expression(_atom == "true");
|
|
|
|
else if (isNumber(_atom))
|
|
|
|
return smtutil::Expression(_atom, {}, SortProvider::sintSort);
|
|
|
|
else
|
|
|
|
return smtutil::Expression(_atom, {}, SortProvider::boolSort);
|
|
|
|
},
|
|
|
|
[&](std::vector<SMTLib2Expression> const& _subExpr) {
|
|
|
|
SortPointer sort;
|
|
|
|
std::vector<smtutil::Expression> arguments;
|
2023-07-21 15:14:29 +00:00
|
|
|
if (isAtom(_subExpr.front()))
|
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
for (size_t i = 1; i < _subExpr.size(); i++)
|
|
|
|
arguments.emplace_back(toSMTUtilExpression(_subExpr[i]));
|
2023-07-26 16:28:04 +00:00
|
|
|
std::string const& op = asAtom(_subExpr.front());
|
|
|
|
if (boost::starts_with(op, "struct")) {
|
|
|
|
auto sort = toSort(_subExpr.front());
|
|
|
|
auto sortSort = std::make_shared<SortSort>(sort);
|
|
|
|
return Expression::tuple_constructor(Expression(sortSort), arguments);
|
|
|
|
} else {
|
|
|
|
std::set<std::string> boolOperators{"and", "or", "not", "=", "<", ">", "<=", ">=",
|
|
|
|
"=>"};
|
|
|
|
sort = contains(boolOperators, op) ? SortProvider::boolSort : arguments.back().sort;
|
|
|
|
}
|
2023-07-21 15:14:29 +00:00
|
|
|
return smtutil::Expression(op, std::move(arguments), std::move(sort));
|
2023-07-21 13:40:00 +00:00
|
|
|
} else {
|
|
|
|
// check for const array
|
|
|
|
if (_subExpr.size() == 2 and !isAtom(_subExpr[0]))
|
2023-07-16 10:56:40 +00:00
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
auto const& typeArgs = asSubExpressions(_subExpr.front());
|
|
|
|
if (typeArgs.size() == 3 && typeArgs[0].toString() == "as" && typeArgs[1].toString() == "const")
|
|
|
|
{
|
|
|
|
auto arraySort = toSort(typeArgs[2]);
|
|
|
|
auto sortSort = std::make_shared<SortSort>(arraySort);
|
|
|
|
return smtutil::Expression::const_array(Expression(sortSort), toSMTUtilExpression(_subExpr[1]));
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 13:40:00 +00:00
|
|
|
smtAssert(false, "Unhandled case in expression conversion");
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
2023-07-21 13:40:00 +00:00
|
|
|
}, _expr.data);
|
|
|
|
}
|
|
|
|
};
|
2023-07-16 10:56:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMTLib2Parser
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
SMTLib2Parser(std::istream& _input):
|
|
|
|
m_input(_input),
|
|
|
|
m_token(static_cast<char>(m_input.get()))
|
|
|
|
{}
|
|
|
|
|
|
|
|
SMTLib2Expression parseExpression()
|
|
|
|
{
|
|
|
|
skipWhitespace();
|
|
|
|
if (token() == '(')
|
|
|
|
{
|
|
|
|
advance();
|
|
|
|
skipWhitespace();
|
|
|
|
std::vector<SMTLib2Expression> subExpressions;
|
|
|
|
while (token() != 0 && token() != ')')
|
|
|
|
{
|
|
|
|
subExpressions.emplace_back(parseExpression());
|
|
|
|
skipWhitespace();
|
|
|
|
}
|
|
|
|
solAssert(token() == ')');
|
|
|
|
// simulate whitespace because we do not want to read the next token
|
|
|
|
// since it might block.
|
|
|
|
m_token = ' ';
|
2023-07-21 15:14:29 +00:00
|
|
|
return {std::move(subExpressions)};
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
return {parseToken()};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isEOF()
|
|
|
|
{
|
|
|
|
skipWhitespace();
|
|
|
|
return m_input.eof();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string parseToken()
|
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
|
|
|
|
skipWhitespace();
|
|
|
|
bool isPipe = token() == '|';
|
|
|
|
if (isPipe)
|
|
|
|
advance();
|
|
|
|
while (token() != 0)
|
|
|
|
{
|
|
|
|
char c = token();
|
|
|
|
if (isPipe && c == '|')
|
|
|
|
{
|
|
|
|
advance();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (!isPipe && (isWhiteSpace(c) || c == '(' || c == ')'))
|
|
|
|
break;
|
|
|
|
result.push_back(c);
|
|
|
|
advance();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void skipWhitespace()
|
|
|
|
{
|
|
|
|
while (isWhiteSpace(token()))
|
|
|
|
advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
char token() const
|
|
|
|
{
|
|
|
|
return m_token;
|
|
|
|
}
|
|
|
|
|
|
|
|
void advance()
|
|
|
|
{
|
|
|
|
m_token = static_cast<char>(m_input.get());
|
|
|
|
if (token() == ';')
|
|
|
|
while (token() != '\n' && token() != 0)
|
|
|
|
m_token = static_cast<char>(m_input.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::istream& m_input;
|
|
|
|
char m_token = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct LetBindings {
|
2023-07-26 15:40:02 +00:00
|
|
|
using BindingRecord = std::vector<SMTLib2Expression>;
|
|
|
|
std::unordered_map<std::string, BindingRecord> bindings;
|
2023-07-16 10:56:40 +00:00
|
|
|
std::vector<std::string> varNames;
|
|
|
|
std::vector<std::size_t> scopeBounds;
|
|
|
|
|
|
|
|
bool has(std::string const& varName)
|
|
|
|
{
|
|
|
|
return bindings.find(varName) != bindings.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
SMTLib2Expression & operator[](std::string const& varName)
|
|
|
|
{
|
|
|
|
auto it = bindings.find(varName);
|
|
|
|
assert(it != bindings.end());
|
2023-07-26 15:40:02 +00:00
|
|
|
assert(!it->second.empty());
|
|
|
|
return it->second.back();
|
2023-07-16 10:56:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pushScope()
|
|
|
|
{
|
|
|
|
scopeBounds.push_back(varNames.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
void popScope()
|
|
|
|
{
|
|
|
|
assert(scopeBounds.size() > 0);
|
|
|
|
auto bound = scopeBounds.back();
|
|
|
|
while (varNames.size() > bound) {
|
|
|
|
auto const& varName = varNames.back();
|
|
|
|
auto it = bindings.find(varName);
|
|
|
|
assert(it != bindings.end());
|
2023-07-26 15:40:02 +00:00
|
|
|
auto & record = it->second;
|
|
|
|
record.pop_back();
|
|
|
|
if (record.empty()) {
|
|
|
|
bindings.erase(it);
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
varNames.pop_back();
|
|
|
|
}
|
|
|
|
scopeBounds.pop_back();
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBinding(std::string name, SMTLib2Expression expression)
|
|
|
|
{
|
2023-07-26 15:40:02 +00:00
|
|
|
auto it = bindings.find(name);
|
|
|
|
if (it == bindings.end()) {
|
|
|
|
bindings.insert({name, {std::move(expression)}});
|
|
|
|
} else {
|
|
|
|
it->second.push_back(std::move(expression));
|
|
|
|
}
|
2023-07-16 10:56:40 +00:00
|
|
|
varNames.push_back(std::move(name));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void inlineLetExpressions(SMTLib2Expression& expr, LetBindings & bindings)
|
|
|
|
{
|
|
|
|
if (isAtom(expr))
|
|
|
|
{
|
|
|
|
auto const& atom = std::get<std::string>(expr.data);
|
|
|
|
if (bindings.has(atom))
|
|
|
|
expr = bindings[atom];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto& subexprs = std::get<SMTLib2Expression::args_t>(expr.data);
|
|
|
|
auto const& first = subexprs[0];
|
|
|
|
if (isAtom(first) && std::get<std::string>(first.data) == "let")
|
|
|
|
{
|
|
|
|
assert(!isAtom(subexprs[1]));
|
2023-07-21 15:14:29 +00:00
|
|
|
auto& bindingExpressions = std::get<SMTLib2Expression::args_t>(subexprs[1].data);
|
2023-07-16 10:56:40 +00:00
|
|
|
// process new bindings
|
|
|
|
std::vector<std::pair<std::string, SMTLib2Expression>> newBindings;
|
2023-07-21 15:14:29 +00:00
|
|
|
for (auto& binding: bindingExpressions)
|
2023-07-16 10:56:40 +00:00
|
|
|
{
|
|
|
|
assert(!isAtom(binding));
|
2023-07-21 15:14:29 +00:00
|
|
|
auto& bindingPair = std::get<SMTLib2Expression::args_t>(binding.data);
|
2023-07-16 10:56:40 +00:00
|
|
|
assert(bindingPair.size() == 2);
|
|
|
|
assert(isAtom(bindingPair.at(0)));
|
|
|
|
inlineLetExpressions(bindingPair.at(1), bindings);
|
|
|
|
newBindings.emplace_back(std::get<std::string>(bindingPair.at(0).data), bindingPair.at(1));
|
|
|
|
}
|
|
|
|
bindings.pushScope();
|
2023-07-21 15:14:29 +00:00
|
|
|
for (auto&& [name, expr] : newBindings)
|
2023-07-16 10:56:40 +00:00
|
|
|
bindings.addBinding(std::move(name), std::move(expr));
|
|
|
|
newBindings.clear();
|
|
|
|
|
|
|
|
// get new subexpression
|
|
|
|
inlineLetExpressions(subexprs.at(2), bindings);
|
|
|
|
// remove the new bindings
|
|
|
|
bindings.popScope();
|
|
|
|
|
|
|
|
// update the expression
|
|
|
|
auto tmp = std::move(subexprs.at(2));
|
|
|
|
expr = std::move(tmp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// not a let expression, just process all arguments
|
2023-07-21 15:14:29 +00:00
|
|
|
for (auto& subexpr: subexprs)
|
2023-07-16 10:56:40 +00:00
|
|
|
{
|
|
|
|
inlineLetExpressions(subexpr, bindings);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void inlineLetExpressions(SMTLib2Expression& expr)
|
|
|
|
{
|
|
|
|
LetBindings bindings;
|
|
|
|
inlineLetExpressions(expr, bindings);
|
|
|
|
}
|
|
|
|
|
|
|
|
SMTLib2Expression const& fact(SMTLib2Expression const& _node)
|
|
|
|
{
|
|
|
|
if (isAtom(_node))
|
|
|
|
return _node;
|
|
|
|
return asSubExpressions(_node).back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CHCSolverInterface::CexGraph CHCSmtLib2Interface::graphFromSMTLib2Expression(SMTLib2Expression const& _proof)
|
|
|
|
{
|
|
|
|
assert(!isAtom(_proof));
|
|
|
|
auto const& args = asSubExpressions(_proof);
|
|
|
|
smtAssert(args.size() == 2);
|
|
|
|
smtAssert(isAtom(args.at(0)) && asAtom(args.at(0)) == "proof");
|
|
|
|
auto const& proofNode = args.at(1);
|
|
|
|
auto derivedFact = fact(proofNode);
|
|
|
|
if (isAtom(proofNode) || !isAtom(derivedFact) || asAtom(derivedFact) != "false")
|
|
|
|
return {};
|
|
|
|
|
|
|
|
CHCSolverInterface::CexGraph graph;
|
2023-07-21 13:40:00 +00:00
|
|
|
SMTLibTranslationContext context(*m_smtlib2);
|
2023-07-16 10:56:40 +00:00
|
|
|
|
|
|
|
std::stack<SMTLib2Expression const*> proofStack;
|
|
|
|
proofStack.push(&asSubExpressions(proofNode).at(1));
|
|
|
|
|
|
|
|
std::map<SMTLib2Expression const*, unsigned> visitedIds;
|
|
|
|
unsigned nextId = 0;
|
|
|
|
|
|
|
|
|
|
|
|
auto const* root = proofStack.top();
|
|
|
|
auto const& derivedRootFact = fact(*root);
|
|
|
|
visitedIds.insert({root, nextId++});
|
2023-07-21 13:40:00 +00:00
|
|
|
graph.nodes.emplace(visitedIds.at(root), context.toSMTUtilExpression(derivedRootFact));
|
2023-07-16 10:56:40 +00:00
|
|
|
|
|
|
|
auto isHyperRes = [](SMTLib2Expression const& expr) {
|
|
|
|
if (isAtom(expr)) return false;
|
|
|
|
auto const& subExprs = asSubExpressions(expr);
|
|
|
|
assert(!subExprs.empty());
|
|
|
|
auto const& op = subExprs.at(0);
|
|
|
|
if (isAtom(op)) return false;
|
|
|
|
auto const& opExprs = asSubExpressions(op);
|
|
|
|
if (opExprs.size() < 2) return false;
|
|
|
|
auto const& ruleName = opExprs.at(1);
|
|
|
|
return isAtom(ruleName) && asAtom(ruleName) == "hyper-res";
|
|
|
|
};
|
|
|
|
|
|
|
|
while (!proofStack.empty())
|
|
|
|
{
|
|
|
|
auto const* proofNode = proofStack.top();
|
|
|
|
smtAssert(visitedIds.find(proofNode) != visitedIds.end(), "");
|
|
|
|
auto id = visitedIds.at(proofNode);
|
|
|
|
smtAssert(graph.nodes.count(id), "");
|
|
|
|
proofStack.pop();
|
|
|
|
|
|
|
|
if (isHyperRes(*proofNode))
|
|
|
|
{
|
|
|
|
auto const& args = asSubExpressions(*proofNode);
|
|
|
|
smtAssert(args.size() > 1, "");
|
|
|
|
// args[0] is the name of the rule
|
|
|
|
// args[1] is the clause used
|
|
|
|
// last argument is the derived fact
|
|
|
|
// the arguments in the middle are the facts where we need to recurse
|
|
|
|
for (unsigned i = 2; i < args.size() - 1; ++i)
|
|
|
|
{
|
|
|
|
auto const* child = &args[i];
|
|
|
|
if (!visitedIds.count(child))
|
|
|
|
{
|
|
|
|
visitedIds.insert({child, nextId++});
|
|
|
|
proofStack.push(child);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto childId = visitedIds.at(child);
|
|
|
|
if (!graph.nodes.count(childId))
|
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
graph.nodes.emplace(childId, context.toSMTUtilExpression(fact(*child)));
|
2023-07-16 10:56:40 +00:00
|
|
|
graph.edges[childId] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
graph.edges[id].push_back(childId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return graph;
|
|
|
|
}
|
|
|
|
|
2023-07-21 15:14:29 +00:00
|
|
|
CHCSolverInterface::CexGraph CHCSmtLib2Interface::graphFromZ3Proof(std::string const& _proof)
|
|
|
|
{
|
2023-07-16 10:56:40 +00:00
|
|
|
std::stringstream ss(_proof);
|
|
|
|
std::string answer;
|
|
|
|
ss >> answer;
|
|
|
|
solAssert(answer == "unsat");
|
|
|
|
SMTLib2Parser parser(ss);
|
|
|
|
solAssert(!parser.isEOF());
|
|
|
|
// For some reason Z3 outputs everything as a single s-expression
|
|
|
|
auto all = parser.parseExpression();
|
|
|
|
solAssert(parser.isEOF());
|
|
|
|
solAssert(!isAtom(all));
|
|
|
|
auto& commands = std::get<SMTLib2Expression::args_t>(all.data);
|
2023-07-21 15:14:29 +00:00
|
|
|
for (auto& command: commands) {
|
2023-07-16 10:56:40 +00:00
|
|
|
// std::cout << command.toString() << '\n' << std::endl;
|
2023-07-21 15:14:29 +00:00
|
|
|
if (!isAtom(command))
|
|
|
|
{
|
2023-07-16 10:56:40 +00:00
|
|
|
auto const& head = std::get<SMTLib2Expression::args_t>(command.data)[0];
|
2023-07-21 15:14:29 +00:00
|
|
|
if (isAtom(head) && std::get<std::string>(head.data) == "proof")
|
|
|
|
{
|
2023-07-21 13:40:00 +00:00
|
|
|
// std::cout << "Proof expression!\n" << command.toString() << std::endl;
|
2023-07-16 10:56:40 +00:00
|
|
|
inlineLetExpressions(command);
|
2023-07-21 13:40:00 +00:00
|
|
|
// std::cout << "Cleaned Proof expression!\n" << command.toString() << std::endl;
|
2023-07-16 10:56:40 +00:00
|
|
|
return graphFromSMTLib2Expression(command);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
2023-07-21 15:14:29 +00:00
|
|
|
}
|