/* 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 using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::langutil; using namespace solidity::smtutil; struct SMTLib2Expression { variant> data; string toString() const { return std::visit(GenericVisitor{ [](string const& _sv) { return string{_sv}; }, [](vector const& _subExpr) { vector formatted; for (auto const& item: _subExpr) formatted.emplace_back(item.toString()); return "(" + joinHumanReadable(formatted, " ") + ")"; } }, data); } }; class SMTLib2Parser { public: SMTLib2Parser(istream& _input): m_input(_input), m_token(static_cast(m_input.get())) {} SMTLib2Expression parseExpression() { skipWhitespace(); if (token() == '(') { advance(); skipWhitespace(); vector 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 = ' '; return {move(subExpressions)}; } else return {parseToken()}; } bool isEOF() { skipWhitespace(); return m_input.eof(); } private: string parseToken() { string result; skipWhitespace(); bool isPipe = token() == '|'; if (isPipe) advance(); while (token() != 0) { char c = token(); if (isPipe && c == '|') { advance(); break; } else if (!isPipe && (langutil::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(m_input.get()); if (token() == ';') while (token() != '\n' && token() != 0) m_token = static_cast(m_input.get()); } istream& m_input; char m_token = 0; }; namespace { string const& command(SMTLib2Expression const& _expr) { vector const& items = get>(_expr.data); solAssert(!items.empty()); solAssert(holds_alternative(items.front().data)); return get(items.front().data); } namespace { bool isNumber(string const& _expr) { for (char c: _expr) if (!isDigit(c) && c != '.') return false; return true; } } smtutil::Expression toSMTUtilExpression(SMTLib2Expression const& _expr, map& _variableSorts) { return std::visit(GenericVisitor{ [&](string const& _atom) { if (_atom == "true" || _atom == "false") return Expression(_atom == "true"); else if (isNumber(_atom)) return Expression(_atom, {}, SortProvider::realSort); else return Expression(_atom, {}, _variableSorts.at(_atom)); }, [&](vector const& _subExpr) { SortPointer sort; vector arguments; string const& op = get(_subExpr.front().data); if (op == "let") { map subSorts; solAssert(_subExpr.size() == 3); // We change the nesting here: // (let ((x1 t1) (x2 t2)) T) -> let(x1(t1), x2(t2), T) for (auto const& binding: get>(_subExpr.at(1).data)) { auto const& bindingElements = get>(binding.data); solAssert(bindingElements.size() == 2); string const& varName = get(bindingElements.at(0).data); Expression replacement = toSMTUtilExpression(bindingElements.at(1), _variableSorts); #ifdef DEBUG cerr << "Binding " << varName << " to " << replacement.toString() << endl; #endif subSorts[varName] = replacement.sort; arguments.emplace_back(Expression(varName, {move(replacement)}, replacement.sort)); } for (auto&& [name, value]: subSorts) _variableSorts[name] = value; arguments.emplace_back(toSMTUtilExpression(_subExpr.at(2), _variableSorts)); for (auto const& var: subSorts) _variableSorts.erase(var.first); sort = arguments.back().sort; } else { set boolOperators{"and", "or", "not", "=", "<", ">", "<=", ">=", "=>"}; for (size_t i = 1; i < _subExpr.size(); i++) arguments.emplace_back(toSMTUtilExpression(_subExpr[i], _variableSorts)); sort = contains(boolOperators, op) ? SortProvider::boolSort : arguments.back().sort; } return Expression(op, move(arguments), move(sort)); } }, _expr.data); } class SolSMT { public: SolSMT(istream& _input): m_input(_input) {} void run(); private: istream& m_input; bool m_doExit = false; bool m_printSuccess = false; map m_variableSorts; BooleanLPSolver m_solver; }; void SolSMT::run() { SMTLib2Parser parser(m_input); while (!m_doExit && !parser.isEOF()) { SMTLib2Expression expr = parser.parseExpression(); #ifdef DEBUG cerr << " -> " << expr.toString() << endl; #endif vector const& items = get>(expr.data); string const& cmd = command(expr); if (cmd == "set-info") { // ignore } else if (cmd == "set-option") { solAssert(items.size() >= 2); string const& option = get(items[1].data); if (option == ":print-success") m_printSuccess = (get(items[2].data) == "true"); // else if (option == ":produce-models") // produceModels = (get(items[2].data) == "true"); // ignore the rest } else if (cmd == "declare-fun") { solAssert(items.size() == 4); string variableName = string{get(items[1].data)}; solAssert(get>(items[2].data).empty()); string const& type = get(items[3].data); solAssert(type == "Real" || type == "Bool"); SortPointer sort = type == "Real" ? SortProvider::realSort : SortProvider::boolSort; m_variableSorts[variableName] = sort; m_solver.declareVariable(variableName, move(sort)); } else if (cmd == "define-fun") { cerr << "Ignoring 'define-fun'" << endl; } else if (cmd == "assert") { solAssert(items.size() == 2); m_solver.addAssertion(toSMTUtilExpression(items[1], m_variableSorts)); } else if (cmd == "push") { // TODO what is the meaning of the numeric argument? solAssert(items.size() == 2); m_solver.push(); } else if (cmd == "pop") { // TODO what is the meaning of the numeric argument? solAssert(items.size() == 2); m_solver.pop(); } else if (cmd == "set-logic") { // ignore - could check the actual logic. } else if (cmd == "check-sat") { auto&& [result, model] = m_solver.check({}); if (result == CheckResult::SATISFIABLE) cout << "sat" << endl; else if (result == CheckResult::UNSATISFIABLE) cout << "unsat" << endl; else cout << "unknown" << endl; // do not print "success" continue; } else if (cmd == "exit") m_doExit = true; else solAssert(false, "Unknown instruction: " + string(cmd)); if (m_printSuccess) cout << "success" << endl; } } } int main(int argc, char** argv) { if (argc != 2 && argc != 1) { cout << "Usage: solsmt []" << endl; return -1; } optional input; if (argc == 2) input = ifstream(argv[1]); SolSMT{argc == 1 ? cin : *input}.run(); return 0; }