/src/solidity/libsolidity/formal/Z3CHCSmtLib2Interface.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 <libsolidity/formal/Z3CHCSmtLib2Interface.h> |
20 | | |
21 | | #include <libsolidity/interface/UniversalCallback.h> |
22 | | |
23 | | #include <libsmtutil/SMTLib2Parser.h> |
24 | | |
25 | | #include <boost/algorithm/string/predicate.hpp> |
26 | | |
27 | | #include <stack> |
28 | | |
29 | | #ifdef EMSCRIPTEN_BUILD |
30 | | #include <z3++.h> |
31 | | #endif |
32 | | |
33 | | using namespace solidity::frontend::smt; |
34 | | using namespace solidity::smtutil; |
35 | | |
36 | | Z3CHCSmtLib2Interface::Z3CHCSmtLib2Interface( |
37 | | frontend::ReadCallback::Callback _smtCallback, |
38 | | std::optional<unsigned int> _queryTimeout, |
39 | | bool _computeInvariants |
40 | | ): CHCSmtLib2Interface({}, std::move(_smtCallback), _queryTimeout), m_computeInvariants(_computeInvariants) |
41 | 0 | { |
42 | | #ifdef EMSCRIPTEN_BUILD |
43 | | constexpr int resourceLimit = 2000000; |
44 | | if (m_queryTimeout) |
45 | | z3::set_param("timeout", int(*m_queryTimeout)); |
46 | | else |
47 | | z3::set_param("rlimit", resourceLimit); |
48 | | z3::set_param("rewriter.pull_cheap_ite", true); |
49 | | z3::set_param("fp.spacer.q3.use_qgen", true); |
50 | | z3::set_param("fp.spacer.mbqi", false); |
51 | | z3::set_param("fp.spacer.ground_pobs", false); |
52 | | #endif |
53 | 0 | } |
54 | | |
55 | | void Z3CHCSmtLib2Interface::setupSmtCallback(bool _enablePreprocessing) |
56 | 0 | { |
57 | 0 | if (auto* universalCallback = m_smtCallback.target<frontend::UniversalCallback>()) |
58 | 0 | universalCallback->smtCommand().setZ3(m_queryTimeout, _enablePreprocessing, m_computeInvariants); |
59 | 0 | } |
60 | | |
61 | | CHCSolverInterface::QueryResult Z3CHCSmtLib2Interface::query(smtutil::Expression const& _block) |
62 | 0 | { |
63 | 0 | setupSmtCallback(true); |
64 | 0 | std::string query = dumpQuery(_block); |
65 | 0 | try |
66 | 0 | { |
67 | | #ifdef EMSCRIPTEN_BUILD |
68 | | z3::set_param("fp.xform.slice", true); |
69 | | z3::set_param("fp.xform.inline_linear", true); |
70 | | z3::set_param("fp.xform.inline_eager", true); |
71 | | std::string response = Z3_eval_smtlib2_string(z3::context{}, query.c_str()); |
72 | | #else |
73 | 0 | std::string response = querySolver(query); |
74 | 0 | #endif |
75 | | // NOTE: Our internal semantics is UNSAT -> SAFE and SAT -> UNSAFE, which corresponds to usual SMT-based model checking |
76 | | // However, with CHC solvers, the meaning is flipped, UNSAT -> UNSAFE and SAT -> SAFE. |
77 | | // So we have to flip the answer. |
78 | 0 | if (boost::starts_with(response, "unsat")) |
79 | 0 | { |
80 | | // Repeat the query with preprocessing disabled, to get the full proof |
81 | 0 | setupSmtCallback(false); |
82 | 0 | query = "(set-option :produce-proofs true)" + query + "\n(get-proof)"; |
83 | | #ifdef EMSCRIPTEN_BUILD |
84 | | z3::set_param("fp.xform.slice", false); |
85 | | z3::set_param("fp.xform.inline_linear", false); |
86 | | z3::set_param("fp.xform.inline_eager", false); |
87 | | response = Z3_eval_smtlib2_string(z3::context{}, query.c_str()); |
88 | | #else |
89 | 0 | response = querySolver(query); |
90 | 0 | #endif |
91 | 0 | setupSmtCallback(true); |
92 | 0 | if (!boost::starts_with(response, "unsat")) |
93 | 0 | return {CheckResult::SATISFIABLE, {}, {}}; |
94 | 0 | return {CheckResult::SATISFIABLE, {}, graphFromZ3Answer(response)}; |
95 | 0 | } |
96 | | |
97 | 0 | CheckResult result; |
98 | 0 | if (boost::starts_with(response, "sat")) |
99 | 0 | return {CheckResult::UNSATISFIABLE, invariantsFromSolverResponse(response), {}}; |
100 | 0 | if (boost::starts_with(response, "unknown")) |
101 | 0 | result = CheckResult::UNKNOWN; |
102 | 0 | else |
103 | 0 | result = CheckResult::ERROR; |
104 | |
|
105 | 0 | return {result, {}, {}}; |
106 | 0 | } |
107 | 0 | catch(smtutil::SMTSolverInteractionError const&) |
108 | 0 | { |
109 | 0 | return {CheckResult::ERROR, {}, {}}; |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | |
114 | | CHCSolverInterface::CexGraph Z3CHCSmtLib2Interface::graphFromZ3Answer(std::string const& _proof) const |
115 | 0 | { |
116 | 0 | std::stringstream ss(_proof); |
117 | 0 | std::string answer; |
118 | 0 | ss >> answer; |
119 | 0 | smtSolverInteractionRequire(answer == "unsat", "Proof must follow an unsat answer"); |
120 | | |
121 | 0 | SMTLib2Parser parser(ss); |
122 | 0 | if (parser.isEOF()) // No proof from Z3 |
123 | 0 | return {}; |
124 | | // For some reason Z3 outputs everything as a single s-expression |
125 | 0 | SMTLib2Expression parsedOutput; |
126 | 0 | try |
127 | 0 | { |
128 | 0 | parsedOutput = parser.parseExpression(); |
129 | 0 | } |
130 | 0 | catch (SMTLib2Parser::ParsingException&) |
131 | 0 | { |
132 | 0 | smtSolverInteractionRequire(false, "Error during parsing Z3's proof"); |
133 | 0 | } |
134 | 0 | smtSolverInteractionRequire(parser.isEOF(), "Error during parsing Z3's proof"); |
135 | 0 | smtSolverInteractionRequire(!isAtom(parsedOutput), "Encountered unexpected format of Z3's proof"); |
136 | 0 | auto& commands = asSubExpressions(parsedOutput); |
137 | 0 | ScopedParser expressionParser(m_context); |
138 | 0 | for (auto& command: commands) |
139 | 0 | { |
140 | 0 | if (isAtom(command)) |
141 | 0 | continue; |
142 | | |
143 | 0 | auto const& args = asSubExpressions(command); |
144 | 0 | smtSolverInteractionRequire(args.size() > 0, "Encountered unexpected format of Z3's proof"); |
145 | 0 | auto const& head = args[0]; |
146 | 0 | if (!isAtom(head)) |
147 | 0 | continue; |
148 | | |
149 | | // Z3 can introduce new helper predicates to be used in the proof |
150 | | // e.g., "(declare-fun query!0 (Bool Bool Bool Int Int Bool Bool Bool Bool Bool Bool Bool Int) Bool)" |
151 | 0 | if (asAtom(head) == "declare-fun") |
152 | 0 | { |
153 | 0 | smtSolverInteractionRequire(args.size() == 4, "Encountered unexpected format of Z3's proof"); |
154 | 0 | auto const& name = args[1]; |
155 | 0 | auto const& domainSorts = args[2]; |
156 | 0 | auto const& codomainSort = args[3]; |
157 | 0 | smtSolverInteractionRequire(isAtom(name), "Encountered unexpected format of Z3's proof"); |
158 | 0 | smtSolverInteractionRequire(!isAtom(domainSorts), "Encountered unexpected format of Z3's proof"); |
159 | 0 | expressionParser.addVariableDeclaration(asAtom(name), expressionParser.toSort(codomainSort)); |
160 | 0 | } |
161 | | // The subexpression starting with "proof" contains the whole proof, which we need to transform to our internal |
162 | | // representation |
163 | 0 | else if (asAtom(head) == "proof") |
164 | 0 | { |
165 | 0 | inlineLetExpressions(command); |
166 | 0 | return graphFromSMTLib2Expression(command, expressionParser); |
167 | 0 | } |
168 | 0 | } |
169 | 0 | return {}; |
170 | 0 | } |
171 | | |
172 | | CHCSolverInterface::CexGraph Z3CHCSmtLib2Interface::graphFromSMTLib2Expression( |
173 | | SMTLib2Expression const& _proof, |
174 | | ScopedParser& _context |
175 | | ) |
176 | 0 | { |
177 | 0 | auto fact = [](SMTLib2Expression const& _node) -> SMTLib2Expression const& { |
178 | 0 | if (isAtom(_node)) |
179 | 0 | return _node; |
180 | 0 | smtSolverInteractionRequire(!asSubExpressions(_node).empty(), "Encountered unexpected format of Z3's proof"); |
181 | 0 | return asSubExpressions(_node).back(); |
182 | 0 | }; |
183 | 0 | smtSolverInteractionRequire(!isAtom(_proof), "Encountered unexpected format of Z3's proof"); |
184 | 0 | auto const& proofArgs = asSubExpressions(_proof); |
185 | 0 | smtSolverInteractionRequire(proofArgs.size() == 2, "Encountered unexpected format of Z3's proof"); |
186 | 0 | smtSolverInteractionRequire(isAtom(proofArgs.at(0)) && asAtom(proofArgs.at(0)) == "proof", "Encountered unexpected format of Z3's proof"); |
187 | 0 | auto const& proofNode = proofArgs.at(1); |
188 | 0 | auto const& derivedFact = fact(proofNode); |
189 | 0 | if (isAtom(proofNode) || !isAtom(derivedFact) || asAtom(derivedFact) != "false") |
190 | 0 | return {}; |
191 | | |
192 | 0 | CHCSolverInterface::CexGraph graph; |
193 | |
|
194 | 0 | std::stack<SMTLib2Expression const*> proofStack; |
195 | 0 | proofStack.push(&asSubExpressions(proofNode).at(1)); |
196 | |
|
197 | 0 | std::map<SMTLib2Expression const*, unsigned> visitedIds; |
198 | 0 | unsigned nextId = 0; |
199 | |
|
200 | 0 | auto const* root = proofStack.top(); |
201 | 0 | auto const& derivedRootFact = fact(*root); |
202 | 0 | visitedIds.emplace(root, nextId++); |
203 | 0 | graph.nodes.emplace(visitedIds.at(root), _context.toSMTUtilExpression(derivedRootFact)); |
204 | |
|
205 | 0 | auto isHyperRes = [](SMTLib2Expression const& expr) { |
206 | 0 | if (isAtom(expr)) return false; |
207 | 0 | auto const& subExprs = asSubExpressions(expr); |
208 | 0 | smtSolverInteractionRequire(!subExprs.empty(), "Encountered unexpected format of Z3's proof"); |
209 | 0 | auto const& op = subExprs.at(0); |
210 | 0 | if (isAtom(op)) return false; |
211 | 0 | auto const& opExprs = asSubExpressions(op); |
212 | 0 | if (opExprs.size() < 2) return false; |
213 | 0 | auto const& ruleName = opExprs.at(1); |
214 | 0 | return isAtom(ruleName) && asAtom(ruleName) == "hyper-res"; |
215 | 0 | }; |
216 | |
|
217 | 0 | while (!proofStack.empty()) |
218 | 0 | { |
219 | 0 | auto const* currentNode = proofStack.top(); |
220 | 0 | smtSolverInteractionRequire(visitedIds.find(currentNode) != visitedIds.end(), "Error in processing Z3's proof"); |
221 | 0 | auto id = visitedIds.at(currentNode); |
222 | 0 | smtSolverInteractionRequire(graph.nodes.count(id), "Error in processing Z3's proof"); |
223 | 0 | proofStack.pop(); |
224 | |
|
225 | 0 | if (isHyperRes(*currentNode)) |
226 | 0 | { |
227 | 0 | auto const& args = asSubExpressions(*currentNode); |
228 | 0 | smtSolverInteractionRequire(args.size() > 1, "Unexpected format of hyper-resolution rule in Z3's proof"); |
229 | | // args[0] is the name of the rule |
230 | | // args[1] is the clause used |
231 | | // last argument is the derived fact |
232 | | // the arguments in the middle are the facts where we need to recurse |
233 | 0 | for (unsigned i = 2; i < args.size() - 1; ++i) |
234 | 0 | { |
235 | 0 | auto const* child = &args[i]; |
236 | 0 | if (!visitedIds.count(child)) |
237 | 0 | { |
238 | 0 | visitedIds.emplace(child, nextId++); |
239 | 0 | proofStack.push(child); |
240 | 0 | } |
241 | |
|
242 | 0 | auto childId = visitedIds.at(child); |
243 | 0 | if (!graph.nodes.count(childId)) |
244 | 0 | { |
245 | 0 | graph.nodes.emplace(childId, _context.toSMTUtilExpression(fact(*child))); |
246 | 0 | graph.edges[childId] = {}; |
247 | 0 | } |
248 | |
|
249 | 0 | graph.edges[id].push_back(childId); |
250 | 0 | } |
251 | 0 | } |
252 | 0 | } |
253 | 0 | return graph; |
254 | 0 | } |
255 | | |