/src/solidity/libsmtutil/SMTLib2Interface.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | |
19 | | #include <libsmtutil/SMTLib2Interface.h> |
20 | | |
21 | | #include <libsmtutil/SMTLib2Parser.h> |
22 | | |
23 | | #include <libsolutil/Keccak256.h> |
24 | | |
25 | | #include <boost/algorithm/string/join.hpp> |
26 | | #include <boost/algorithm/string/predicate.hpp> |
27 | | |
28 | | #include <range/v3/view/transform.hpp> |
29 | | #include <range/v3/view/zip.hpp> |
30 | | |
31 | | #include <array> |
32 | | #include <fstream> |
33 | | #include <iostream> |
34 | | #include <memory> |
35 | | #include <stdexcept> |
36 | | #include <string> |
37 | | #include <utility> |
38 | | |
39 | | using namespace solidity; |
40 | | using namespace solidity::util; |
41 | | using namespace solidity::frontend; |
42 | | using namespace solidity::smtutil; |
43 | | |
44 | | SMTLib2Interface::SMTLib2Interface( |
45 | | std::map<h256, std::string> _queryResponses, |
46 | | ReadCallback::Callback _smtCallback, |
47 | | std::optional<unsigned> _queryTimeout |
48 | | ): |
49 | | BMCSolverInterface(_queryTimeout), |
50 | | m_queryResponses(std::move(_queryResponses)), |
51 | | m_smtCallback(std::move(_smtCallback)) |
52 | 14.6k | { |
53 | 14.6k | reset(); |
54 | 14.6k | } |
55 | | |
56 | | void SMTLib2Interface::reset() |
57 | 14.6k | { |
58 | 14.6k | m_commands.clear(); |
59 | 14.6k | m_context.clear(); |
60 | 14.6k | m_commands.setOption("produce-models", "true"); |
61 | 14.6k | if (m_queryTimeout) |
62 | 14.0k | m_commands.setOption("timeout", std::to_string(*m_queryTimeout)); |
63 | 14.6k | m_commands.setLogic("ALL"); |
64 | 98.5k | m_context.setTupleDeclarationCallback([&](TupleSort const& tupleSort){ |
65 | 98.5k | m_commands.declareTuple( |
66 | 98.5k | tupleSort.name, |
67 | 98.5k | tupleSort.members, |
68 | 98.5k | tupleSort.components |
69 | 407k | | ranges::views::transform([&](SortPointer const& sort){ return m_context.toSmtLibSort(sort); }) |
70 | 98.5k | | ranges::to<std::vector>() |
71 | 98.5k | ); |
72 | 98.5k | }); |
73 | 14.6k | } |
74 | | |
75 | | void SMTLib2Interface::push() |
76 | 6.28k | { |
77 | 6.28k | m_commands.push(); |
78 | 6.28k | } |
79 | | |
80 | | void SMTLib2Interface::pop() |
81 | 6.28k | { |
82 | 6.28k | m_commands.pop(); |
83 | 6.28k | } |
84 | | |
85 | | void SMTLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort) |
86 | 1.81M | { |
87 | 1.81M | smtAssert(_sort); |
88 | 1.81M | if (_sort->kind == Kind::Function) |
89 | 10.1k | declareFunction(_name, _sort); |
90 | 1.80M | else if (!m_context.isDeclared(_name)) |
91 | 454k | { |
92 | 454k | m_context.declare(_name, _sort); |
93 | 454k | m_commands.declareVariable(_name, toSmtLibSort(_sort)); |
94 | 454k | } |
95 | 1.81M | } |
96 | | |
97 | | void SMTLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort) |
98 | 10.1k | { |
99 | 10.1k | smtAssert(_sort); |
100 | 10.1k | smtAssert(_sort->kind == Kind::Function); |
101 | 10.1k | if (!m_context.isDeclared(_name)) |
102 | 9.51k | { |
103 | 9.51k | auto const& fSort = std::dynamic_pointer_cast<FunctionSort>(_sort); |
104 | 9.51k | auto domain = toSmtLibSort(fSort->domain); |
105 | 9.51k | std::string codomain = toSmtLibSort(fSort->codomain); |
106 | 9.51k | m_context.declare(_name, _sort); |
107 | 9.51k | m_commands.declareFunction(_name, domain, codomain); |
108 | 9.51k | } |
109 | 10.1k | } |
110 | | |
111 | | void SMTLib2Interface::addAssertion(Expression const& _expr) |
112 | 6.28k | { |
113 | 6.28k | m_commands.assertion(toSExpr(_expr)); |
114 | 6.28k | } |
115 | | |
116 | | namespace |
117 | | { |
118 | | std::vector<std::string> parseValuesFromResponse(std::string const& _response) |
119 | 0 | { |
120 | 0 | std::stringstream ss(_response); |
121 | 0 | std::string answer; |
122 | 0 | ss >> answer; |
123 | 0 | smtSolverInteractionRequire(answer == "sat", "SMT: Parsing model values only possible after sat answer"); |
124 | | |
125 | 0 | std::vector<SMTLib2Expression> parsedOutput; |
126 | 0 | SMTLib2Parser parser(ss); |
127 | 0 | try |
128 | 0 | { |
129 | 0 | while (!parser.isEOF()) |
130 | 0 | parsedOutput.push_back(parser.parseExpression()); |
131 | 0 | } |
132 | 0 | catch(SMTLib2Parser::ParsingException&) |
133 | 0 | { |
134 | 0 | smtSolverInteractionRequire(false, "Error during parsing SMT answer"); |
135 | 0 | } |
136 | 0 | smtSolverInteractionRequire(parsedOutput.size() == 1, "SMT: Expected model values as a single s-expression"); |
137 | 0 | auto const& values = parsedOutput[0]; |
138 | 0 | smtSolverInteractionRequire(!isAtom(values), "Invalid format of values in SMT answer"); |
139 | 0 | std::vector<std::string> parsedValues; |
140 | 0 | for (auto const& nameValuePair: asSubExpressions(values)) |
141 | 0 | { |
142 | 0 | smtSolverInteractionRequire(!isAtom(nameValuePair), "Invalid format of values in SMT answer"); |
143 | 0 | smtSolverInteractionRequire(asSubExpressions(nameValuePair).size() == 2, "Invalid format of values in SMT answer"); |
144 | 0 | auto const& value = asSubExpressions(nameValuePair)[1]; |
145 | 0 | parsedValues.push_back(value.toString()); |
146 | 0 | } |
147 | 0 | return parsedValues; |
148 | 0 | } |
149 | | } // namespace |
150 | | |
151 | | std::pair<CheckResult, std::vector<std::string>> SMTLib2Interface::check(std::vector<Expression> const& _expressionsToEvaluate) |
152 | 6.28k | { |
153 | 6.28k | std::string response = querySolver(dumpQuery(_expressionsToEvaluate)); |
154 | | |
155 | 6.28k | CheckResult result; |
156 | | // TODO proper parsing |
157 | 6.28k | if (boost::starts_with(response, "sat")) |
158 | 0 | result = CheckResult::SATISFIABLE; |
159 | 6.28k | else if (boost::starts_with(response, "unsat")) |
160 | 0 | result = CheckResult::UNSATISFIABLE; |
161 | 6.28k | else if (boost::starts_with(response, "unknown")) |
162 | 6.28k | result = CheckResult::UNKNOWN; |
163 | 0 | else |
164 | 0 | result = CheckResult::ERROR; |
165 | | |
166 | 6.28k | std::vector<std::string> values; |
167 | 6.28k | if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty()) |
168 | 0 | values = parseValuesFromResponse(response); |
169 | 6.28k | return std::make_pair(result, values); |
170 | 6.28k | } |
171 | | |
172 | | std::string SMTLib2Interface::toSmtLibSort(SortPointer _sort) |
173 | 468k | { |
174 | 468k | return m_context.toSmtLibSort(std::move(_sort)); |
175 | 468k | } |
176 | | |
177 | | std::vector<std::string> SMTLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts) |
178 | 9.51k | { |
179 | 9.51k | std::vector<std::string> ssorts; |
180 | 9.51k | ssorts.reserve(_sorts.size()); |
181 | 9.51k | for (auto const& sort: _sorts) |
182 | 4.08k | ssorts.push_back(toSmtLibSort(sort)); |
183 | 9.51k | return ssorts; |
184 | 9.51k | } |
185 | | |
186 | | std::string SMTLib2Interface::toSExpr(solidity::smtutil::Expression const& _expr) |
187 | 17.7k | { |
188 | 17.7k | return m_context.toSExpr(_expr); |
189 | 17.7k | } |
190 | | |
191 | | std::string SMTLib2Interface::checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate) |
192 | 6.28k | { |
193 | 6.28k | std::string command; |
194 | 6.28k | if (_expressionsToEvaluate.empty()) |
195 | 2.91k | command = "(check-sat)\n"; |
196 | 3.36k | else |
197 | 3.36k | { |
198 | | // TODO make sure these are unique |
199 | 14.8k | for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) |
200 | 11.4k | { |
201 | 11.4k | auto const& e = _expressionsToEvaluate.at(i); |
202 | 11.4k | smtAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate."); |
203 | 11.4k | command += "(declare-const |EVALEXPR_" + std::to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n"; |
204 | 11.4k | command += "(assert (= |EVALEXPR_" + std::to_string(i) + "| " + toSExpr(e) + "))\n"; |
205 | 11.4k | } |
206 | 3.36k | command += "(check-sat)\n"; |
207 | 3.36k | command += "(get-value ("; |
208 | 14.8k | for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) |
209 | 11.4k | command += "|EVALEXPR_" + std::to_string(i) + "| "; |
210 | 3.36k | command += "))\n"; |
211 | 3.36k | } |
212 | | |
213 | 6.28k | return command; |
214 | 6.28k | } |
215 | | |
216 | | std::string SMTLib2Interface::querySolver(std::string const& _input) |
217 | 6.28k | { |
218 | 6.28k | h256 inputHash = keccak256(_input); |
219 | 6.28k | if (m_queryResponses.count(inputHash)) |
220 | 0 | return m_queryResponses.at(inputHash); |
221 | 6.28k | if (m_smtCallback) |
222 | 0 | { |
223 | 0 | setupSmtCallback(); |
224 | 0 | auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input); |
225 | 0 | if (result.success) |
226 | 0 | return result.responseOrErrorMessage; |
227 | 0 | } |
228 | 6.28k | m_unhandledQueries.push_back(_input); |
229 | 6.28k | return "unknown\n"; |
230 | 6.28k | } |
231 | | |
232 | | std::string SMTLib2Interface::dumpQuery(std::vector<Expression> const& _expressionsToEvaluate) |
233 | 6.28k | { |
234 | 6.28k | return m_commands.toString() + '\n' + checkSatAndGetValuesCommand(_expressionsToEvaluate); |
235 | 6.28k | } |
236 | | |
237 | | |
238 | 6.28k | void SMTLib2Commands::push() { |
239 | 6.28k | m_frameLimits.push_back(m_commands.size()); |
240 | 6.28k | } |
241 | | |
242 | 6.28k | void SMTLib2Commands::pop() { |
243 | 6.28k | smtAssert(!m_commands.empty()); |
244 | 6.28k | auto limit = m_frameLimits.back(); |
245 | 6.28k | m_frameLimits.pop_back(); |
246 | 12.5k | while (m_commands.size() > limit) |
247 | 6.28k | m_commands.pop_back(); |
248 | 6.28k | } |
249 | | |
250 | 27.4k | std::string SMTLib2Commands::toString() const { |
251 | 27.4k | return boost::algorithm::join(m_commands, "\n"); |
252 | 27.4k | } |
253 | | |
254 | 44.1k | void SMTLib2Commands::clear() { |
255 | 44.1k | m_commands.clear(); |
256 | 44.1k | m_frameLimits.clear(); |
257 | 44.1k | } |
258 | | |
259 | 349k | void SMTLib2Commands::assertion(std::string _expr) { |
260 | 349k | m_commands.push_back("(assert " + std::move(_expr) + ')'); |
261 | 349k | } |
262 | | |
263 | | void SMTLib2Commands::setOption(std::string _name, std::string _value) |
264 | 58.1k | { |
265 | 58.1k | m_commands.push_back("(set-option :" + std::move(_name) + ' ' + std::move(_value) + ')'); |
266 | 58.1k | } |
267 | | |
268 | | void SMTLib2Commands::setLogic(std::string _logic) |
269 | 44.1k | { |
270 | 44.1k | m_commands.push_back("(set-logic " + std::move(_logic) + ')'); |
271 | 44.1k | } |
272 | | |
273 | | void SMTLib2Commands::declareVariable(std::string _name, std::string _sort) |
274 | 454k | { |
275 | 454k | m_commands.push_back("(declare-fun |" + std::move(_name) + "| () " + std::move(_sort) + ')'); |
276 | 454k | } |
277 | | |
278 | | void SMTLib2Commands::declareFunction(std::string const& _name, std::vector<std::string> const& _domain, std::string const& _codomain) |
279 | 282k | { |
280 | 282k | std::stringstream ss; |
281 | 282k | ss << "(declare-fun |" << _name << "| " << '(' << boost::join(_domain, " ") |
282 | 282k | << ')' << ' ' << _codomain << ')'; |
283 | 282k | m_commands.push_back(ss.str()); |
284 | 282k | } |
285 | | |
286 | | void SMTLib2Commands::declareTuple( |
287 | | std::string const& _name, |
288 | | std::vector<std::string> const& _memberNames, |
289 | | std::vector<std::string> const& _memberSorts |
290 | | ) |
291 | 193k | { |
292 | 193k | auto quotedName = '|' + _name + '|'; |
293 | 193k | std::stringstream ss; |
294 | 193k | ss << "(declare-datatypes ((" << quotedName << " 0)) (((" << quotedName; |
295 | 193k | for (auto && [memberName, memberSort]: ranges::views::zip(_memberNames, _memberSorts)) |
296 | 801k | ss << " (|" << memberName << "| " << memberSort << ")"; |
297 | 193k | ss << "))))"; |
298 | 193k | auto declaration = ss.str(); |
299 | 193k | m_commands.push_back(ss.str()); |
300 | 193k | } |