/src/solidity/libsmtutil/CHCSmtLib2Interface.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/CHCSmtLib2Interface.h> |
20 | | |
21 | | #include <libsmtutil/SMTLib2Parser.h> |
22 | | |
23 | | #include <libsolutil/Keccak256.h> |
24 | | #include <libsolutil/StringUtils.h> |
25 | | #include <libsolutil/Visitor.h> |
26 | | |
27 | | #include <boost/algorithm/string/join.hpp> |
28 | | #include <boost/algorithm/string/predicate.hpp> |
29 | | |
30 | | #include <range/v3/algorithm/all_of.hpp> |
31 | | #include <range/v3/algorithm/sort.hpp> |
32 | | #include <range/v3/view.hpp> |
33 | | |
34 | | #include <array> |
35 | | #include <fstream> |
36 | | #include <iostream> |
37 | | #include <memory> |
38 | | #include <stdexcept> |
39 | | |
40 | | using namespace solidity; |
41 | | using namespace solidity::util; |
42 | | using namespace solidity::frontend; |
43 | | using namespace solidity::smtutil; |
44 | | |
45 | | CHCSmtLib2Interface::CHCSmtLib2Interface( |
46 | | std::map<h256, std::string> _queryResponses, |
47 | | ReadCallback::Callback _smtCallback, |
48 | | std::optional<unsigned> _queryTimeout |
49 | | ): |
50 | | CHCSolverInterface(_queryTimeout), |
51 | | m_queryResponses(std::move(_queryResponses)), |
52 | | m_smtCallback(std::move(_smtCallback)) |
53 | 14.6k | { |
54 | 14.6k | reset(); |
55 | 14.6k | } |
56 | | |
57 | | void CHCSmtLib2Interface::reset() |
58 | 30.8k | { |
59 | 30.8k | m_unhandledQueries.clear(); |
60 | 30.8k | m_commands.clear(); |
61 | 30.8k | m_context.clear(); |
62 | 30.8k | createHeader(); |
63 | 99.0k | m_context.setTupleDeclarationCallback([&](TupleSort const& _tupleSort){ |
64 | 99.0k | m_commands.declareTuple( |
65 | 99.0k | _tupleSort.name, |
66 | 99.0k | _tupleSort.members, |
67 | 99.0k | _tupleSort.components |
68 | 410k | | ranges::views::transform([&](SortPointer const& _sort){ return m_context.toSmtLibSort(_sort); }) |
69 | 99.0k | | ranges::to<std::vector>() |
70 | 99.0k | ); |
71 | 99.0k | }); |
72 | 30.8k | } |
73 | | |
74 | | void CHCSmtLib2Interface::registerRelation(Expression const& _expr) |
75 | 283k | { |
76 | 283k | smtAssert(_expr.sort); |
77 | 283k | smtAssert(_expr.sort->kind == Kind::Function); |
78 | 283k | if (m_context.isDeclared(_expr.name)) |
79 | 0 | return; |
80 | 283k | auto const& fSort = std::dynamic_pointer_cast<FunctionSort>(_expr.sort); |
81 | 283k | smtAssert(fSort->codomain); |
82 | 283k | auto domain = toSmtLibSort(fSort->domain); |
83 | 283k | std::string codomain = toSmtLibSort(fSort->codomain); |
84 | 283k | m_commands.declareFunction(_expr.name, domain, codomain); |
85 | 283k | m_context.declare(_expr.name, _expr.sort); |
86 | 283k | } |
87 | | |
88 | | void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& /*_name*/) |
89 | 355k | { |
90 | 355k | m_commands.assertion("(forall" + forall(_expr) + '\n' + m_context.toSExpr(_expr) + ")\n"); |
91 | 355k | } |
92 | | |
93 | | CHCSolverInterface::QueryResult CHCSmtLib2Interface::query(Expression const& _block) |
94 | 22.2k | { |
95 | 22.2k | std::string query = dumpQuery(_block); |
96 | 22.2k | try |
97 | 22.2k | { |
98 | 22.2k | std::string response = querySolver(query); |
99 | | |
100 | 22.2k | CheckResult result; |
101 | | // NOTE: Our internal semantics is UNSAT -> SAFE and SAT -> UNSAFE, which corresponds to usual SMT-based model checking |
102 | | // However, with CHC solvers, the meaning is flipped, UNSAT -> UNSAFE and SAT -> SAFE. |
103 | | // So we have to flip the answer. |
104 | 22.2k | if (boost::starts_with(response, "sat")) |
105 | 0 | return {CheckResult::UNSATISFIABLE, invariantsFromSolverResponse(response), {}}; |
106 | 22.2k | if (boost::starts_with(response, "unsat")) |
107 | 0 | result = CheckResult::SATISFIABLE; |
108 | 22.2k | else if (boost::starts_with(response, "unknown")) |
109 | 22.2k | result = CheckResult::UNKNOWN; |
110 | 0 | else |
111 | 0 | result = CheckResult::ERROR; |
112 | 22.2k | return {result, {}, {}}; |
113 | 22.2k | } |
114 | 22.2k | catch(smtutil::SMTSolverInteractionError const&) |
115 | 22.2k | { |
116 | 0 | return {CheckResult::ERROR, {}, {}}; |
117 | 0 | } |
118 | | |
119 | 22.2k | } |
120 | | |
121 | | void CHCSmtLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort) |
122 | 9.60M | { |
123 | 9.60M | smtAssert(_sort); |
124 | 9.60M | if (!m_context.isDeclared(_name)) |
125 | 509k | m_context.declare(_name, _sort); |
126 | 9.60M | } |
127 | | |
128 | | std::string CHCSmtLib2Interface::toSmtLibSort(SortPointer const& _sort) |
129 | 7.18M | { |
130 | 7.18M | return m_context.toSmtLibSort(_sort); |
131 | 7.18M | } |
132 | | |
133 | | std::vector<std::string> CHCSmtLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts) |
134 | 283k | { |
135 | 2.28M | return applyMap(_sorts, [this](auto const& sort) { return toSmtLibSort(sort); }); |
136 | 283k | } |
137 | | |
138 | | std::set<std::string> CHCSmtLib2Interface::collectVariableNames(Expression const& _expr) const |
139 | 355k | { |
140 | 355k | std::set<std::string> names; |
141 | 355k | auto dfs = [&](Expression const& _current, auto _recurse) -> void |
142 | 29.6M | { |
143 | 29.6M | if (_current.arguments.empty()) |
144 | 17.5M | { |
145 | 17.5M | if (m_context.isDeclared(_current.name)) |
146 | 11.2M | names.insert(_current.name); |
147 | 17.5M | } |
148 | 12.1M | else |
149 | 12.1M | for (auto const& arg: _current.arguments) |
150 | 29.3M | _recurse(arg, _recurse); |
151 | 29.6M | }; |
152 | 355k | dfs(_expr, dfs); |
153 | 355k | return names; |
154 | 355k | } |
155 | | |
156 | | std::string CHCSmtLib2Interface::forall(Expression const& _expr) |
157 | 355k | { |
158 | 355k | auto varNames = collectVariableNames(_expr); |
159 | 355k | std::string vars("("); |
160 | 355k | for (auto const& name: varNames) |
161 | 4.64M | { |
162 | 4.64M | auto sort = m_context.getDeclaredSort(name); |
163 | 4.64M | if (sort->kind != Kind::Function) |
164 | 4.62M | vars += " (" + name + " " + toSmtLibSort(sort) + ")"; |
165 | 4.64M | } |
166 | 355k | vars += ")"; |
167 | 355k | return vars; |
168 | 355k | } |
169 | | |
170 | | std::string CHCSmtLib2Interface::querySolver(std::string const& _input) |
171 | 22.2k | { |
172 | 22.2k | util::h256 inputHash = util::keccak256(_input); |
173 | 22.2k | if (m_queryResponses.count(inputHash)) |
174 | 0 | return m_queryResponses.at(inputHash); |
175 | | |
176 | 22.2k | if (m_smtCallback) |
177 | 0 | { |
178 | 0 | auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input); |
179 | 0 | if (result.success) |
180 | 0 | return result.responseOrErrorMessage; |
181 | 0 | } |
182 | | |
183 | 22.2k | m_unhandledQueries.push_back(_input); |
184 | 22.2k | return "unknown\n"; |
185 | 22.2k | } |
186 | | |
187 | | std::string CHCSmtLib2Interface::dumpQuery(Expression const& _expr) |
188 | 22.2k | { |
189 | 22.2k | return m_commands.toString() + createQueryAssertion(_expr.name) + '\n' + "(check-sat)" + '\n'; |
190 | 22.2k | } |
191 | | |
192 | | void CHCSmtLib2Interface::createHeader() |
193 | 30.8k | { |
194 | 30.8k | if (m_queryTimeout) |
195 | 30.8k | m_commands.setOption("timeout", std::to_string(*m_queryTimeout)); |
196 | 30.8k | m_commands.setLogic("HORN"); |
197 | 30.8k | } |
198 | | |
199 | 22.2k | std::string CHCSmtLib2Interface::createQueryAssertion(std::string _name) { |
200 | 22.2k | return "(assert\n(forall ((UNUSED Bool))\n(=> " + std::move(_name) + " false)))"; |
201 | 22.2k | } |
202 | | |
203 | | namespace |
204 | | { |
205 | | bool isNumber(std::string const& _expr) |
206 | 0 | { |
207 | 0 | return ranges::all_of(_expr, [](char c) { return isDigit(c) || c == '.'; }); |
208 | 0 | } |
209 | | |
210 | | bool isBitVectorHexConstant(std::string const& _string) |
211 | 0 | { |
212 | 0 | if (_string.substr(0, 2) != "#x") |
213 | 0 | return false; |
214 | 0 | if (_string.find_first_not_of("0123456789abcdefABCDEF", 2) != std::string::npos) |
215 | 0 | return false; |
216 | 0 | return true; |
217 | 0 | } |
218 | | |
219 | | bool isBitVectorConstant(std::string const& _string) |
220 | 0 | { |
221 | 0 | if (_string.substr(0, 2) != "#b") |
222 | 0 | return false; |
223 | 0 | if (_string.find_first_not_of("01", 2) != std::string::npos) |
224 | 0 | return false; |
225 | 0 | return true; |
226 | 0 | } |
227 | | } |
228 | | |
229 | | void CHCSmtLib2Interface::ScopedParser::addVariableDeclaration(std::string _name, solidity::smtutil::SortPointer _sort) |
230 | 0 | { |
231 | 0 | m_localVariables.emplace(std::move(_name), std::move(_sort)); |
232 | 0 | } |
233 | | |
234 | | std::optional<SortPointer> CHCSmtLib2Interface::ScopedParser::lookupKnownTupleSort(std::string const& _name) const |
235 | 0 | { |
236 | 0 | return m_context.getTupleType(_name); |
237 | 0 | } |
238 | | |
239 | | SortPointer CHCSmtLib2Interface::ScopedParser::toSort(SMTLib2Expression const& _expr) |
240 | 0 | { |
241 | 0 | if (isAtom(_expr)) |
242 | 0 | { |
243 | 0 | auto const& name = asAtom(_expr); |
244 | 0 | if (name == "Int") |
245 | 0 | return SortProvider::sintSort; |
246 | 0 | if (name == "Bool") |
247 | 0 | return SortProvider::boolSort; |
248 | 0 | auto tupleSort = lookupKnownTupleSort(name); |
249 | 0 | if (tupleSort) |
250 | 0 | return tupleSort.value(); |
251 | 0 | } |
252 | 0 | else |
253 | 0 | { |
254 | 0 | auto const& args = asSubExpressions(_expr); |
255 | 0 | if (asAtom(args[0]) == "Array") |
256 | 0 | { |
257 | 0 | smtSolverInteractionRequire(args.size() == 3, "Wrong format of Array sort in solver's response"); |
258 | 0 | auto domainSort = toSort(args[1]); |
259 | 0 | auto codomainSort = toSort(args[2]); |
260 | 0 | return std::make_shared<ArraySort>(std::move(domainSort), std::move(codomainSort)); |
261 | 0 | } |
262 | 0 | if (args.size() == 3 && isAtom(args[0]) && asAtom(args[0]) == "_" && isAtom(args[1]) |
263 | 0 | && asAtom(args[1]) == "int2bv") |
264 | 0 | return std::make_shared<BitVectorSort>(std::stoul(asAtom(args[2]))); |
265 | 0 | } |
266 | 0 | smtSolverInteractionRequire(false, "Unknown sort encountered"); |
267 | 0 | } |
268 | | |
269 | | smtutil::Expression CHCSmtLib2Interface::ScopedParser::parseQuantifier( |
270 | | std::string const& _quantifierName, |
271 | | std::vector<SMTLib2Expression> const& _varList, |
272 | | SMTLib2Expression const& _coreExpression) |
273 | 0 | { |
274 | 0 | std::vector<std::pair<std::string, SortPointer>> boundVariables; |
275 | 0 | for (auto const& sortedVar: _varList) |
276 | 0 | { |
277 | 0 | smtSolverInteractionRequire(!isAtom(sortedVar), "Wrong format of quantified expression in solver's response"); |
278 | 0 | auto varSortPair = asSubExpressions(sortedVar); |
279 | 0 | smtSolverInteractionRequire(varSortPair.size() == 2, "Wrong format of quantified expression in solver's response"); |
280 | 0 | boundVariables.emplace_back(asAtom(varSortPair[0]), toSort(varSortPair[1])); |
281 | 0 | } |
282 | 0 | for (auto const& [var, sort]: boundVariables) |
283 | 0 | { |
284 | 0 | smtSolverInteractionRequire(m_localVariables.count(var) == 0, "Quantifying over previously encountered variable"); // TODO: deal with shadowing? |
285 | 0 | m_localVariables.emplace(var, sort); |
286 | 0 | } |
287 | 0 | auto core = toSMTUtilExpression(_coreExpression); |
288 | 0 | for (auto const& [var, sort]: boundVariables) |
289 | 0 | { |
290 | 0 | smtSolverInteractionRequire(m_localVariables.count(var) != 0, "Error in processing quantified expression"); |
291 | 0 | m_localVariables.erase(var); |
292 | 0 | } |
293 | 0 | return Expression(_quantifierName, {core}, SortProvider::boolSort); // TODO: what about the bound variables? |
294 | 0 | } |
295 | | |
296 | | smtutil::Expression CHCSmtLib2Interface::ScopedParser::toSMTUtilExpression(SMTLib2Expression const& _expr) |
297 | 0 | { |
298 | 0 | return std::visit( |
299 | 0 | GenericVisitor{ |
300 | 0 | [&](std::string const& _atom) |
301 | 0 | { |
302 | 0 | if (_atom == "true" || _atom == "false") |
303 | 0 | return smtutil::Expression(_atom == "true"); |
304 | 0 | else if (isNumber(_atom)) |
305 | 0 | return smtutil::Expression(_atom, {}, SortProvider::sintSort); |
306 | 0 | else if (isBitVectorHexConstant(_atom)) |
307 | 0 | return smtutil::Expression(_atom, {}, std::make_shared<BitVectorSort>((_atom.size() - 2) * 4)); |
308 | 0 | else if (isBitVectorConstant(_atom)) |
309 | 0 | return smtutil::Expression(_atom, {}, std::make_shared<BitVectorSort>(_atom.size() - 2)); |
310 | 0 | else if (auto it = m_localVariables.find(_atom); it != m_localVariables.end()) |
311 | 0 | return smtutil::Expression(_atom, {}, it->second); |
312 | 0 | else if (m_context.isDeclared(_atom)) |
313 | 0 | return smtutil::Expression(_atom, {}, m_context.getDeclaredSort(_atom)); |
314 | 0 | else if (auto maybeTupleType = m_context.getTupleType(_atom); maybeTupleType.has_value()) |
315 | 0 | { |
316 | | // 0-ary tuple type, can happen |
317 | 0 | return smtutil::Expression(_atom, {}, std::make_shared<TupleSort>(_atom, std::vector<std::string>{}, std::vector<SortPointer>{})); |
318 | 0 | } |
319 | 0 | else |
320 | 0 | smtSolverInteractionRequire(false, "Unhandled atomic SMT expression"); |
321 | 0 | }, |
322 | 0 | [&](std::vector<SMTLib2Expression> const& _subExpr) |
323 | 0 | { |
324 | 0 | SortPointer sort; |
325 | 0 | std::vector<smtutil::Expression> arguments; |
326 | 0 | if (isAtom(_subExpr.front())) |
327 | 0 | { |
328 | 0 | std::string const& op = asAtom(_subExpr.front()); |
329 | 0 | if (op == "!") |
330 | 0 | { |
331 | | // named term, we ignore the name |
332 | 0 | smtSolverInteractionRequire(_subExpr.size() > 2, "Wrong format of named SMT-LIB term"); |
333 | 0 | return toSMTUtilExpression(_subExpr[1]); |
334 | 0 | } |
335 | 0 | if (op == "exists" || op == "forall") |
336 | 0 | { |
337 | 0 | smtSolverInteractionRequire(_subExpr.size() == 3, "Wrong format of quantified expression"); |
338 | 0 | smtSolverInteractionRequire(!isAtom(_subExpr[1]), "Wrong format of quantified expression"); |
339 | 0 | return parseQuantifier(op, asSubExpressions(_subExpr[1]), _subExpr[2]); |
340 | 0 | } |
341 | 0 | for (size_t i = 1; i < _subExpr.size(); i++) |
342 | 0 | arguments.emplace_back(toSMTUtilExpression(_subExpr[i])); |
343 | 0 | if (auto tupleSort = lookupKnownTupleSort(op); tupleSort) |
344 | 0 | { |
345 | 0 | auto sortSort = std::make_shared<SortSort>(tupleSort.value()); |
346 | 0 | return Expression::tuple_constructor(Expression(sortSort), arguments); |
347 | 0 | } |
348 | 0 | if (m_context.isDeclared(op)) |
349 | 0 | return smtutil::Expression(op, std::move(arguments), m_context.getDeclaredSort(op)); |
350 | 0 | if (auto maybeTupleAccessor = m_context.getTupleAccessor(op); maybeTupleAccessor.has_value()) |
351 | 0 | { |
352 | 0 | auto accessor = maybeTupleAccessor.value(); |
353 | 0 | return smtutil::Expression("dt_accessor_" + accessor.first, std::move(arguments), accessor.second); |
354 | 0 | } |
355 | 0 | if (op == "select") |
356 | 0 | { |
357 | 0 | smtSolverInteractionRequire(arguments.size() == 2, "Select has two arguments: array and index"); |
358 | 0 | return smtutil::Expression::select(arguments[0], arguments[1]); |
359 | 0 | } |
360 | 0 | if (op == "store") |
361 | 0 | { |
362 | 0 | smtSolverInteractionRequire(arguments.size() == 3, "Store has three arguments: array, index and element"); |
363 | 0 | return smtutil::Expression::store(arguments[0], arguments[1], arguments[2]); |
364 | 0 | } |
365 | 0 | if (op == "bv2int") |
366 | 0 | { |
367 | 0 | smtSolverInteractionRequire(arguments.size() == 1, "bv2int has one argument"); |
368 | 0 | return smtutil::Expression::bv2int(arguments[0]); |
369 | 0 | } |
370 | 0 | else |
371 | 0 | { |
372 | 0 | std::set<std::string> boolOperators{"and", "or", "not", "=", "<", ">", "<=", ">=", "=>"}; |
373 | 0 | sort = contains(boolOperators, op) ? SortProvider::boolSort : arguments.back().sort; |
374 | 0 | return smtutil::Expression(op, std::move(arguments), std::move(sort)); |
375 | 0 | } |
376 | 0 | smtSolverInteractionRequire(false, "Unhandled case in expression conversion"); |
377 | 0 | } |
378 | 0 | else |
379 | 0 | { |
380 | | // check for const array |
381 | 0 | if (_subExpr.size() == 2 and !isAtom(_subExpr[0])) |
382 | 0 | { |
383 | 0 | auto const& typeArgs = asSubExpressions(_subExpr.front()); |
384 | 0 | if (typeArgs.size() == 3 && typeArgs[0].toString() == "as" |
385 | 0 | && typeArgs[1].toString() == "const") |
386 | 0 | { |
387 | 0 | auto arraySort = toSort(typeArgs[2]); |
388 | 0 | auto sortSort = std::make_shared<SortSort>(arraySort); |
389 | 0 | return smtutil::Expression:: |
390 | 0 | const_array(Expression(sortSort), toSMTUtilExpression(_subExpr[1])); |
391 | 0 | } |
392 | 0 | if (typeArgs.size() == 3 && typeArgs[0].toString() == "_" |
393 | 0 | && typeArgs[1].toString() == "int2bv") |
394 | 0 | { |
395 | 0 | auto bvSort = std::dynamic_pointer_cast<BitVectorSort>(toSort(_subExpr[0])); |
396 | 0 | smtSolverInteractionRequire(bvSort, "Invalid format of bitvector sort"); |
397 | 0 | return smtutil::Expression::int2bv(toSMTUtilExpression(_subExpr[1]), bvSort->size); |
398 | 0 | } |
399 | 0 | if (typeArgs.size() == 4 && typeArgs[0].toString() == "_" |
400 | 0 | && typeArgs[1].toString() == "extract") |
401 | 0 | return smtutil::Expression( |
402 | 0 | "extract", |
403 | 0 | {toSMTUtilExpression(typeArgs[2]), toSMTUtilExpression(typeArgs[3])}, |
404 | 0 | SortProvider::bitVectorSort // TODO: Compute bit size properly? |
405 | 0 | ); |
406 | 0 | } |
407 | 0 | smtSolverInteractionRequire(false, "Unhandled case in expression conversion"); |
408 | 0 | } |
409 | 0 | } |
410 | 0 | }, |
411 | 0 | _expr.data |
412 | 0 | ); |
413 | 0 | } |
414 | | |
415 | | |
416 | | CHCSmtLib2Interface::Invariants CHCSmtLib2Interface::invariantsFromSolverResponse(std::string const& _response) const |
417 | 0 | { |
418 | 0 | std::stringstream ss(_response); |
419 | 0 | std::string answer; |
420 | 0 | ss >> answer; |
421 | 0 | smtSolverInteractionRequire(answer == "sat", "CHC model can only be extracted from sat answer"); |
422 | 0 | SMTLib2Parser parser(ss); |
423 | 0 | if (parser.isEOF()) |
424 | 0 | return {}; |
425 | 0 | std::vector<SMTLib2Expression> parsedOutput; |
426 | 0 | try |
427 | 0 | { |
428 | 0 | while (!parser.isEOF()) |
429 | 0 | parsedOutput.push_back(parser.parseExpression()); |
430 | 0 | } |
431 | 0 | catch(SMTLib2Parser::ParsingException&) |
432 | 0 | { |
433 | 0 | smtSolverInteractionRequire(false, "Error during parsing CHC model"); |
434 | 0 | } |
435 | 0 | smtSolverInteractionRequire(parser.isEOF(), "Error during parsing CHC model"); |
436 | 0 | smtSolverInteractionRequire(!parsedOutput.empty(), "Error during parsing CHC model"); |
437 | 0 | auto& commands = parsedOutput.size() == 1 ? asSubExpressions(parsedOutput[0]) : parsedOutput; |
438 | 0 | Invariants definitions; |
439 | 0 | for (auto& command: commands) |
440 | 0 | { |
441 | 0 | auto& args = asSubExpressions(command); |
442 | 0 | smtSolverInteractionRequire(args.size() == 5, "Invalid format of CHC model"); |
443 | | // args[0] = "define-fun" |
444 | | // args[1] = predicate name |
445 | | // args[2] = formal arguments of the predicate |
446 | | // args[3] = return sort |
447 | | // args[4] = body of the predicate's interpretation |
448 | 0 | smtSolverInteractionRequire(isAtom(args[0]) && asAtom(args[0]) == "define-fun", "Invalid format of CHC model"); |
449 | 0 | smtSolverInteractionRequire(isAtom(args[1]), "Invalid format of CHC model"); |
450 | 0 | smtSolverInteractionRequire(!isAtom(args[2]), "Invalid format of CHC model"); |
451 | 0 | smtSolverInteractionRequire(isAtom(args[3]) && asAtom(args[3]) == "Bool", "Invalid format of CHC model"); |
452 | 0 | auto& interpretation = args[4]; |
453 | 0 | inlineLetExpressions(interpretation); |
454 | 0 | ScopedParser scopedParser(m_context); |
455 | 0 | auto const& formalArguments = asSubExpressions(args[2]); |
456 | 0 | std::vector<std::string> predicateArguments; |
457 | 0 | for (auto const& formalArgument: formalArguments) |
458 | 0 | { |
459 | 0 | smtSolverInteractionRequire(!isAtom(formalArgument), "Invalid format of CHC model"); |
460 | 0 | auto const& nameSortPair = asSubExpressions(formalArgument); |
461 | 0 | smtSolverInteractionRequire(nameSortPair.size() == 2, "Invalid format of CHC model"); |
462 | 0 | smtSolverInteractionRequire(isAtom(nameSortPair[0]), "Invalid format of CHC model"); |
463 | 0 | SortPointer varSort = scopedParser.toSort(nameSortPair[1]); |
464 | 0 | scopedParser.addVariableDeclaration(asAtom(nameSortPair[0]), varSort); |
465 | 0 | predicateArguments.push_back(asAtom(nameSortPair[0])); |
466 | 0 | } |
467 | | |
468 | 0 | auto parsedInterpretation = scopedParser.toSMTUtilExpression(interpretation); |
469 | | |
470 | | // Hack to make invariants more stable across operating systems |
471 | 0 | if (parsedInterpretation.name == "and" || parsedInterpretation.name == "or") |
472 | 0 | ranges::sort(parsedInterpretation.arguments, [](Expression const& first, Expression const& second) { |
473 | 0 | return first.name < second.name; |
474 | 0 | }); |
475 | |
|
476 | 0 | std::string const& predicateName = asAtom(args[1]); |
477 | 0 | smtSolverInteractionRequire(!definitions.contains(predicateName), "Predicates must be unique"); |
478 | 0 | definitions.emplace(predicateName, InvariantInfo{parsedInterpretation, predicateArguments}); |
479 | 0 | } |
480 | 0 | return definitions; |
481 | 0 | } |
482 | | |
483 | | namespace |
484 | | { |
485 | | |
486 | | struct LetBindings |
487 | | { |
488 | | using BindingRecord = std::vector<SMTLib2Expression>; |
489 | | std::unordered_map<std::string, BindingRecord> bindings; |
490 | | std::vector<std::string> varNames; |
491 | | std::vector<std::size_t> scopeBounds; |
492 | | |
493 | 0 | bool has(std::string const& varName) { return bindings.find(varName) != bindings.end(); } |
494 | | |
495 | | SMTLib2Expression& operator[](std::string const& varName) |
496 | 0 | { |
497 | 0 | auto it = bindings.find(varName); |
498 | 0 | smtSolverInteractionRequire(it != bindings.end(), "Error in processing let bindings"); |
499 | 0 | smtSolverInteractionRequire(!it->second.empty(), "Error in processing let bindings"); |
500 | 0 | return it->second.back(); |
501 | 0 | } |
502 | | |
503 | 0 | void pushScope() { scopeBounds.push_back(varNames.size()); } |
504 | | |
505 | | void popScope() |
506 | 0 | { |
507 | 0 | smtSolverInteractionRequire(!scopeBounds.empty(), "Error in processing let bindings"); |
508 | 0 | auto bound = scopeBounds.back(); |
509 | 0 | while (varNames.size() > bound) |
510 | 0 | { |
511 | 0 | auto const& varName = varNames.back(); |
512 | 0 | auto it = bindings.find(varName); |
513 | 0 | smtSolverInteractionRequire(it != bindings.end(), "Error in processing let bindings"); |
514 | 0 | auto& record = it->second; |
515 | 0 | record.pop_back(); |
516 | 0 | if (record.empty()) |
517 | 0 | bindings.erase(it); |
518 | 0 | varNames.pop_back(); |
519 | 0 | } |
520 | 0 | scopeBounds.pop_back(); |
521 | 0 | } |
522 | | |
523 | | void addBinding(std::string name, SMTLib2Expression expression) |
524 | 0 | { |
525 | 0 | auto it = bindings.find(name); |
526 | 0 | if (it == bindings.end()) |
527 | 0 | bindings.insert({name, {std::move(expression)}}); |
528 | 0 | else |
529 | 0 | it->second.push_back(std::move(expression)); |
530 | 0 | varNames.push_back(std::move(name)); |
531 | 0 | } |
532 | | }; |
533 | | |
534 | | void inlineLetExpressions(SMTLib2Expression& _expr, LetBindings& _bindings) |
535 | 0 | { |
536 | 0 | if (isAtom(_expr)) |
537 | 0 | { |
538 | 0 | auto const& atom = asAtom(_expr); |
539 | 0 | if (_bindings.has(atom)) |
540 | 0 | _expr = _bindings[atom]; |
541 | 0 | return; |
542 | 0 | } |
543 | 0 | auto& subexprs = asSubExpressions(_expr); |
544 | 0 | smtSolverInteractionRequire(!subexprs.empty(), "Invalid let expression"); |
545 | 0 | auto const& first = subexprs.at(0); |
546 | 0 | if (isAtom(first) && asAtom(first) == "let") |
547 | 0 | { |
548 | 0 | smtSolverInteractionRequire(subexprs.size() == 3, "Invalid let expression"); |
549 | 0 | smtSolverInteractionRequire(!isAtom(subexprs[1]), "Invalid let expression"); |
550 | 0 | auto& bindingExpressions = asSubExpressions(subexprs[1]); |
551 | | // process new bindings |
552 | 0 | std::vector<std::pair<std::string, SMTLib2Expression>> newBindings; |
553 | 0 | for (auto& binding: bindingExpressions) |
554 | 0 | { |
555 | 0 | smtSolverInteractionRequire(!isAtom(binding), "Invalid let expression"); |
556 | 0 | auto& bindingPair = asSubExpressions(binding); |
557 | 0 | smtSolverInteractionRequire(bindingPair.size() == 2, "Invalid let expression"); |
558 | 0 | smtSolverInteractionRequire(isAtom(bindingPair.at(0)), "Invalid let expression"); |
559 | 0 | inlineLetExpressions(bindingPair.at(1), _bindings); |
560 | 0 | newBindings.emplace_back(asAtom(bindingPair.at(0)), bindingPair.at(1)); |
561 | 0 | } |
562 | 0 | _bindings.pushScope(); |
563 | 0 | for (auto&& [name, expr]: newBindings) |
564 | 0 | _bindings.addBinding(std::move(name), std::move(expr)); |
565 | |
|
566 | 0 | newBindings.clear(); |
567 | | |
568 | | // get new subexpression |
569 | 0 | inlineLetExpressions(subexprs.at(2), _bindings); |
570 | | // remove the new bindings |
571 | 0 | _bindings.popScope(); |
572 | | |
573 | | // update the expression |
574 | 0 | auto tmp = std::move(subexprs.at(2)); |
575 | 0 | _expr = std::move(tmp); |
576 | 0 | return; |
577 | 0 | } |
578 | 0 | else if (isAtom(first) && (asAtom(first) == "forall" || asAtom(first) == "exists")) |
579 | 0 | { |
580 | | // A little hack to ensure quantified variables are not substituted because of some outer let definition: |
581 | | // We define the current binding of the variable to itself, before we recurse in to subterm |
582 | 0 | smtSolverInteractionRequire(subexprs.size() == 3, "Invalid let expression"); |
583 | 0 | _bindings.pushScope(); |
584 | 0 | for (auto const& sortedVar: asSubExpressions(subexprs.at(1))) |
585 | 0 | { |
586 | 0 | auto const& varNameExpr = asSubExpressions(sortedVar).at(0); |
587 | 0 | _bindings.addBinding(asAtom(varNameExpr), varNameExpr); |
588 | 0 | } |
589 | 0 | inlineLetExpressions(subexprs.at(2), _bindings); |
590 | 0 | _bindings.popScope(); |
591 | 0 | return; |
592 | 0 | } |
593 | | |
594 | | // not a let expression, just process all arguments recursively |
595 | 0 | for (auto& subexpr: subexprs) |
596 | 0 | inlineLetExpressions(subexpr, _bindings); |
597 | 0 | } |
598 | | } |
599 | | |
600 | | void CHCSmtLib2Interface::inlineLetExpressions(SMTLib2Expression& expr) |
601 | 0 | { |
602 | 0 | LetBindings bindings; |
603 | 0 | ::inlineLetExpressions(expr, bindings); |
604 | 0 | } |