/*
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 .
*/
// SPDX-License-Identifier: GPL-3.0
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::smtutil;
using rational = boost::rational;
//#define DEBUG
namespace
{
template
void resizeAndSet(vector& _vector, size_t _index, T _value)
{
if (_vector.size() < _index + 1)
_vector.resize(_index + 1);
_vector[_index] = move(_value);
}
string toString(rational const& _x)
{
if (_x == bigint(1) << 256)
return "2**256";
else if (_x == (bigint(1) << 256) - 1)
return "2**256-1";
else if (_x.denominator() == 1)
return _x.numerator().str();
else
return _x.numerator().str() + "/" + _x.denominator().str();
}
}
void BooleanLPSolver::reset()
{
m_state = vector{{State{}}};
}
void BooleanLPSolver::push()
{
// TODO maybe find a way where we do not have to copy everything
State currentState = state();
m_state.emplace_back(move(currentState));
}
void BooleanLPSolver::pop()
{
m_state.pop_back();
solAssert(!m_state.empty(), "");
}
void BooleanLPSolver::declareVariable(string const& _name, SortPointer const& _sort)
{
// Internal variables are '$', or '$c' so escape `$` to `$$`.
string name = (_name.empty() || _name.at(0) != '$') ? _name : "$$" + _name;
solAssert(_sort && (_sort->kind == Kind::Int || _sort->kind == Kind::Real || _sort->kind == Kind::Bool), "");
solAssert(!state().variables.count(name), "");
// TODO store the actual kind (integer, real, bool)
declareVariable(name, _sort->kind == Kind::Bool);
}
pair> BooleanLPSolver::check(vector const&)
{
#ifdef DEBUG
cerr << "Solving boolean constraint system" << endl;
cerr << toString() << endl;
cerr << "--------------" << endl;
#endif
if (state().infeasible)
{
#ifdef DEBUG
cerr << "----->>>>> unsatisfiable" << endl;
#endif
return make_pair(CheckResult::UNSATISFIABLE, vector{});
}
std::vector booleanVariables;
std::vector clauses = state().clauses;
// TODO we start building up a new set of solver
// for each query, but we should also keep some
// kind of cache across queries.
std::vector> lpSolvers;
lpSolvers.emplace_back(0, LPSolver{});
LPSolver& lpSolver = lpSolvers.back().second;
for (auto&& [index, bound]: state().bounds)
{
if (bound.lower)
lpSolver.addLowerBound(index, *bound.lower);
if (bound.upper)
lpSolver.addUpperBound(index, *bound.upper);
}
for (Constraint const& c: state().fixedConstraints)
lpSolver.addConstraint(c);
// TODO this way, it will result in a lot of gaps in both sets of variables.
// should we compress them and store a mapping?
// Is it even a problem if the indices overlap?
for (auto&& [name, index]: state().variables)
if (state().isBooleanVariable.at(index) || isConditionalConstraint(index))
resizeAndSet(booleanVariables, index, name);
else
lpSolver.setVariableName(index, name);
if (lpSolver.check().first == LPResult::Infeasible)
{
#ifdef DEBUG
cerr << "----->>>>> unsatisfiable" << endl;
#endif
return {CheckResult::UNSATISFIABLE, {}};
}
auto theorySolver = [&](size_t _trailSize, map const& _newBooleanAssignment) -> optional
{
lpSolvers.emplace_back(_trailSize, LPSolver(lpSolvers.back().second));
for (auto&& [constraintIndex, value]: _newBooleanAssignment)
{
if (!value || !state().conditionalConstraints.count(constraintIndex))
continue;
// "reason" is already stored for those constraints.
Constraint const& constraint = state().conditionalConstraints.at(constraintIndex);
lpSolvers.back().second.addConstraint(constraint, constraintIndex);
}
auto&& [result, reasonSet] = lpSolvers.back().second.check();
// We can only really use the result "infeasible". Everything else should be "sat".
if (result == LPResult::Infeasible)
{
// TODO is it ok to ignore the non-constraint boolean variables here?
Clause conflictClause;
for (size_t constraintIndex: reasonSet)
conflictClause.emplace_back(Literal{false, constraintIndex});
#ifdef DEBUG
cerr << "||||| conflict claus: " << toString(conflictClause) << endl;
#endif
return conflictClause;
}
else
return nullopt;
};
auto backtrackNotify = [&](size_t _trailSize)
{
while (lpSolvers.back().first > _trailSize)
lpSolvers.pop_back();
};
auto optionalModel = CDCL{move(booleanVariables), clauses, theorySolver, backtrackNotify}.solve();
if (!optionalModel)
{
#ifdef DEBUG
cerr << "==============> CDCL final result: unsatisfiable." << endl;
#endif
return {CheckResult::UNSATISFIABLE, {}};
}
else
{
#ifdef DEBUG
cerr << "==============> CDCL final result: SATisfiable / UNKNOWN." << endl;
#endif
// TODO should be "unknown" later on
return {CheckResult::SATISFIABLE, {}};
//return {CheckResult::UNKNOWN, {}};
}
}
string BooleanLPSolver::toString() const
{
string result;
result += "-- Fixed Constraints:\n";
for (Constraint const& c: state().fixedConstraints)
result += toString(c) + "\n";
result += "-- Fixed Bounds:\n";
for (auto&& [index, bounds]: state().bounds)
{
if (!bounds.lower && !bounds.upper)
continue;
if (bounds.lower)
result += bounds.lower->toString() + " <= ";
result += variableName(index);
if (bounds.upper)
result += " <= " + bounds.upper->toString();
result += "\n";
}
result += "-- Clauses:\n";
for (Clause const& c: state().clauses)
result += toString(c);
return result;
}
void BooleanLPSolver::addAssertion(Expression const& _expr, LetBindings _letBindings)
{
#ifdef DEBUG
cerr << "adding assertion" << endl;
cerr << " - " << _expr.toString() << endl;
#endif
solAssert(_expr.sort->kind == Kind::Bool);
if (_expr.arguments.empty())
{
size_t varIndex = 0;
if (_letBindings->count(_expr.name))
{
LetBinding binding = _letBindings->at(_expr.name);
if (holds_alternative(binding))
{
addAssertion(std::get(binding), move(_letBindings));
return;
}
else
varIndex = std::get(binding);
}
else
varIndex = state().variables.at(_expr.name);
solAssert(varIndex > 0, "");
solAssert(isBooleanVariable(varIndex));
state().clauses.emplace_back(Clause{Literal{true, varIndex}});
}
else if (_expr.name == "let")
{
addLetBindings(_expr, _letBindings);
addAssertion(_expr.arguments.back(), move(_letBindings));
}
else if (_expr.name == "=")
{
solAssert(_expr.arguments.size() == 2);
solAssert(_expr.arguments.at(0).sort->kind == _expr.arguments.at(1).sort->kind);
if (_expr.arguments.at(0).sort->kind == Kind::Bool)
{
if (_expr.arguments.at(0).arguments.empty() && isBooleanVariable(_expr.arguments.at(0).name))
addBooleanEquality(*parseLiteral(_expr.arguments.at(0), _letBindings), _expr.arguments.at(1), _letBindings);
else if (_expr.arguments.at(1).arguments.empty() && isBooleanVariable(_expr.arguments.at(1).name))
addBooleanEquality(*parseLiteral(_expr.arguments.at(1), _letBindings), _expr.arguments.at(0), _letBindings);
else
{
Literal newBoolean = *parseLiteral(declareInternalVariable(true), make_shared