/src/solidity/libsolidity/formal/BMC.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/BMC.h> |
20 | | |
21 | | #include <libsolidity/formal/Cvc5SMTLib2Interface.h> |
22 | | #include <libsolidity/formal/SymbolicTypes.h> |
23 | | #include <libsolidity/formal/Z3SMTLib2Interface.h> |
24 | | |
25 | | #include <libsmtutil/SMTLib2Interface.h> |
26 | | #include <libsmtutil/SMTPortfolio.h> |
27 | | |
28 | | #include <liblangutil/CharStream.h> |
29 | | #include <liblangutil/CharStreamProvider.h> |
30 | | |
31 | | #include <utility> |
32 | | |
33 | | using namespace solidity; |
34 | | using namespace solidity::util; |
35 | | using namespace solidity::langutil; |
36 | | using namespace solidity::frontend; |
37 | | using namespace solidity::frontend::smt; |
38 | | using namespace solidity::smtutil; |
39 | | |
40 | | BMC::BMC( |
41 | | smt::EncodingContext& _context, |
42 | | UniqueErrorReporter& _errorReporter, |
43 | | UniqueErrorReporter& _unsupportedErrorReporter, |
44 | | ErrorReporter& _provedSafeReporter, |
45 | | std::map<h256, std::string> const& _smtlib2Responses, |
46 | | ReadCallback::Callback const& _smtCallback, |
47 | | ModelCheckerSettings _settings, |
48 | | CharStreamProvider const& _charStreamProvider |
49 | | ): |
50 | | SMTEncoder(_context, _settings, _errorReporter, _unsupportedErrorReporter, _provedSafeReporter, _charStreamProvider) |
51 | 15.5k | { |
52 | 15.5k | std::vector<std::unique_ptr<BMCSolverInterface>> solvers; |
53 | 15.5k | if (_settings.solvers.smtlib2) |
54 | 14.9k | solvers.emplace_back(std::make_unique<SMTLib2Interface>(_smtlib2Responses, _smtCallback, _settings.timeout)); |
55 | 15.5k | if (_settings.solvers.cvc5) |
56 | 0 | solvers.emplace_back(std::make_unique<Cvc5SMTLib2Interface>(_smtCallback, _settings.timeout)); |
57 | 15.5k | if (_settings.solvers.z3 ) |
58 | 597 | solvers.emplace_back(std::make_unique<Z3SMTLib2Interface>(_smtCallback, _settings.timeout)); |
59 | 15.5k | m_interface = std::make_unique<SMTPortfolio>(std::move(solvers), _settings.timeout); |
60 | 15.5k | } |
61 | | |
62 | | void BMC::analyze(SourceUnit const& _source, std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> _solvedTargets) |
63 | 16.5k | { |
64 | | // At this point every enabled solver is available. |
65 | 16.5k | if (!m_settings.solvers.cvc5 && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3) |
66 | 0 | { |
67 | 0 | m_errorReporter.warning( |
68 | 0 | 7710_error, |
69 | 0 | SourceLocation(), |
70 | 0 | "BMC analysis was not possible since no SMT solver was found and enabled." |
71 | 0 | " The accepted solvers for BMC are cvc5 and z3." |
72 | 0 | ); |
73 | 0 | return; |
74 | 0 | } |
75 | | |
76 | 16.5k | SMTEncoder::resetSourceAnalysis(); |
77 | | |
78 | 16.5k | state().prepareForSourceUnit(_source, false); |
79 | 16.5k | m_solvedTargets = std::move(_solvedTargets); |
80 | 16.5k | m_context.setSolver(m_interface.get()); |
81 | 16.5k | m_context.reset(); |
82 | 16.5k | m_context.setAssertionAccumulation(true); |
83 | 16.5k | auto const& sources = sourceDependencies(_source); |
84 | 16.5k | createFreeConstants(sources); |
85 | 16.5k | createStateVariables(sources); |
86 | 16.5k | m_unprovedAmt = 0; |
87 | | |
88 | 16.5k | _source.accept(*this); |
89 | | |
90 | 16.5k | if (m_unprovedAmt > 0 && !m_settings.showUnproved) |
91 | 2.09k | m_errorReporter.warning( |
92 | 2.09k | 2788_error, |
93 | 2.09k | {}, |
94 | 2.09k | "BMC: " + |
95 | 2.09k | std::to_string(m_unprovedAmt) + |
96 | 2.09k | " verification condition(s) could not be proved." + |
97 | 2.09k | " Enable the model checker option \"show unproved\" to see all of them." + |
98 | 2.09k | " Consider choosing a specific contract to be verified in order to reduce the solving problems." + |
99 | 2.09k | " Consider increasing the timeout per query." |
100 | 2.09k | ); |
101 | | |
102 | 16.5k | if (!m_settings.showProvedSafe && !m_safeTargets.empty()) |
103 | 0 | { |
104 | 0 | std::size_t provedSafeNum = 0; |
105 | 0 | for (auto&& [_, targets]: m_safeTargets) |
106 | 0 | provedSafeNum += targets.size(); |
107 | 0 | m_errorReporter.info( |
108 | 0 | 6002_error, |
109 | 0 | "BMC: " + |
110 | 0 | std::to_string(provedSafeNum) + |
111 | 0 | " verification condition(s) proved safe!" + |
112 | 0 | " Enable the model checker option \"show proved safe\" to see all of them." |
113 | 0 | ); |
114 | 0 | } |
115 | 16.5k | else if (m_settings.showProvedSafe) |
116 | 0 | for (auto const& [node, targets]: m_safeTargets) |
117 | 0 | for (auto const& target: targets) |
118 | 0 | m_provedSafeReporter.info( |
119 | 0 | 2961_error, |
120 | 0 | node->location(), |
121 | 0 | "BMC: " + |
122 | 0 | targetDescription(target) + |
123 | 0 | " check is safe!" |
124 | 0 | ); |
125 | | |
126 | | // If this check is true, Z3 and cvc5 are not available |
127 | | // and the query answers were not provided, since SMTPortfolio |
128 | | // guarantees that SmtLib2Interface is the first solver, if enabled. |
129 | 16.5k | if ( |
130 | 16.5k | !m_interface->unhandledQueries().empty() && |
131 | 16.5k | m_interface->solvers() == 1 && |
132 | 16.5k | m_settings.solvers.smtlib2 |
133 | 16.5k | ) |
134 | 2.57k | m_errorReporter.warning( |
135 | 2.57k | 8084_error, |
136 | 2.57k | SourceLocation(), |
137 | 2.57k | "BMC analysis was not possible. No SMT solver (Z3 or cvc5) was available." |
138 | 2.57k | " None of the installed solvers was enabled." |
139 | 2.57k | ); |
140 | 16.5k | } |
141 | | |
142 | | bool BMC::shouldInlineFunctionCall( |
143 | | FunctionCall const& _funCall, |
144 | | ContractDefinition const* _scopeContract, |
145 | | ContractDefinition const* _contextContract |
146 | | ) |
147 | 5.96k | { |
148 | 5.96k | auto funDef = functionCallToDefinition(_funCall, _scopeContract, _contextContract); |
149 | 5.96k | if (!funDef || !funDef->isImplemented()) |
150 | 1.05k | return false; |
151 | | |
152 | 4.90k | FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); |
153 | 4.90k | if (funType.kind() == FunctionType::Kind::External) |
154 | 956 | return isExternalCallToThis(&_funCall.expression()); |
155 | 3.95k | else if (funType.kind() != FunctionType::Kind::Internal) |
156 | 211 | return false; |
157 | | |
158 | 3.74k | return true; |
159 | 4.90k | } |
160 | | |
161 | | /// AST visitors. |
162 | | |
163 | | bool BMC::visit(ContractDefinition const& _contract) |
164 | 17.5k | { |
165 | | // Raises UnimplementedFeatureError in the presence of transient storage variables |
166 | 17.5k | TransientDataLocationChecker checker(_contract); |
167 | | |
168 | 17.5k | initContract(_contract); |
169 | | |
170 | 17.5k | SMTEncoder::visit(_contract); |
171 | | |
172 | 17.5k | return false; |
173 | 17.5k | } |
174 | | |
175 | | void BMC::endVisit(ContractDefinition const& _contract) |
176 | 17.5k | { |
177 | 17.5k | if (auto constructor = _contract.constructor()) |
178 | 1.80k | constructor->accept(*this); |
179 | 15.7k | else |
180 | 15.7k | { |
181 | | /// Visiting implicit constructor - we need a dummy callstack frame |
182 | 15.7k | pushCallStack({nullptr, nullptr}); |
183 | 15.7k | inlineConstructorHierarchy(_contract); |
184 | 15.7k | popCallStack(); |
185 | | /// Check targets created by state variable initialization. |
186 | 15.7k | checkVerificationTargets(); |
187 | 15.7k | m_verificationTargets.clear(); |
188 | 15.7k | } |
189 | | |
190 | 17.5k | SMTEncoder::endVisit(_contract); |
191 | 17.5k | } |
192 | | |
193 | | bool BMC::visit(FunctionDefinition const& _function) |
194 | 20.4k | { |
195 | | // Free functions need to be visited in the context of a contract. |
196 | 20.4k | if (!m_currentContract) |
197 | 939 | return false; |
198 | | |
199 | 19.5k | auto contract = dynamic_cast<ContractDefinition const*>(_function.scope()); |
200 | 19.5k | auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; |
201 | 19.5k | if (contract && find(hierarchy.begin(), hierarchy.end(), contract) == hierarchy.end()) |
202 | 182 | createStateVariables(*contract); |
203 | | |
204 | 19.5k | if (m_callStack.empty()) |
205 | 17.0k | { |
206 | 17.0k | reset(); |
207 | 17.0k | initFunction(_function); |
208 | 17.0k | if (_function.isConstructor() || _function.isPublic()) |
209 | 15.9k | m_context.addAssertion(state().txTypeConstraints() && state().txFunctionConstraints(_function)); |
210 | 17.0k | resetStateVariables(); |
211 | 17.0k | } |
212 | | |
213 | 19.5k | if (_function.isConstructor()) |
214 | 2.37k | { |
215 | 2.37k | solAssert(contract, ""); |
216 | 2.37k | inlineConstructorHierarchy(*contract); |
217 | 2.37k | } |
218 | | |
219 | | /// Already visits the children. |
220 | 19.5k | SMTEncoder::visit(_function); |
221 | | |
222 | 19.5k | return false; |
223 | 19.5k | } |
224 | | |
225 | | void BMC::endVisit(FunctionDefinition const& _function) |
226 | 20.4k | { |
227 | | // Free functions need to be visited in the context of a contract. |
228 | 20.4k | if (!m_currentContract) |
229 | 939 | return; |
230 | | |
231 | 19.5k | if (isRootFunction()) |
232 | 17.0k | { |
233 | 17.0k | checkVerificationTargets(); |
234 | 17.0k | m_verificationTargets.clear(); |
235 | 17.0k | m_pathConditions.clear(); |
236 | 17.0k | } |
237 | | |
238 | 19.5k | SMTEncoder::endVisit(_function); |
239 | 19.5k | } |
240 | | |
241 | | bool BMC::visit(IfStatement const& _node) |
242 | 872 | { |
243 | 872 | auto indicesBeforePush = copyVariableIndices(); |
244 | | // This check needs to be done in its own context otherwise |
245 | | // constraints from the If body might influence it. |
246 | 872 | m_context.pushSolver(); |
247 | 872 | _node.condition().accept(*this); |
248 | | |
249 | 872 | checkIfConditionIsConstant(_node.condition()); |
250 | | |
251 | 872 | m_context.popSolver(); |
252 | 872 | resetVariableIndices(std::move(indicesBeforePush)); |
253 | | |
254 | 872 | _node.condition().accept(*this); |
255 | 872 | auto conditionExpr = expr(_node.condition()); |
256 | | // visit true branch |
257 | 872 | auto [indicesEndTrue, trueEndPathCondition] = visitBranch(&_node.trueStatement(), conditionExpr); |
258 | | |
259 | | // visit false branch |
260 | 872 | decltype(indicesEndTrue) indicesEndFalse; |
261 | 872 | auto falseEndPathCondition = currentPathConditions() && !conditionExpr; |
262 | 872 | if (_node.falseStatement()) |
263 | 110 | std::tie(indicesEndFalse, falseEndPathCondition) = visitBranch(_node.falseStatement(), !conditionExpr); |
264 | 762 | else |
265 | 762 | indicesEndFalse = copyVariableIndices(); |
266 | | |
267 | | // merge the information from branches |
268 | 872 | setPathCondition(trueEndPathCondition || falseEndPathCondition); |
269 | 872 | mergeVariables(expr(_node.condition()), indicesEndTrue, indicesEndFalse); |
270 | | |
271 | 872 | return false; |
272 | 872 | } |
273 | | |
274 | | bool BMC::visit(Conditional const& _op) |
275 | 561 | { |
276 | 561 | auto indicesBeforePush = copyVariableIndices(); |
277 | 561 | m_context.pushSolver(); |
278 | 561 | _op.condition().accept(*this); |
279 | 561 | checkIfConditionIsConstant(_op.condition()); |
280 | 561 | m_context.popSolver(); |
281 | 561 | resetVariableIndices(std::move(indicesBeforePush)); |
282 | | |
283 | 561 | SMTEncoder::visit(_op); |
284 | | |
285 | 561 | return false; |
286 | 561 | } |
287 | | |
288 | | // Unrolls while or do-while loop |
289 | | bool BMC::visit(WhileStatement const& _node) |
290 | 141 | { |
291 | 141 | unsigned int bmcLoopIterations = m_settings.bmcLoopIterations.value_or(1); |
292 | 141 | smtutil::Expression broke(false); |
293 | 141 | smtutil::Expression loopCondition(true); |
294 | 141 | if (_node.isDoWhile()) |
295 | 28 | { |
296 | 56 | for (unsigned int i = 0; i < bmcLoopIterations; ++i) |
297 | 28 | { |
298 | 28 | m_loopCheckpoints.emplace(); |
299 | | |
300 | 28 | auto indicesBefore = copyVariableIndices(); |
301 | 28 | _node.body().accept(*this); |
302 | | |
303 | 28 | auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); |
304 | | |
305 | 28 | auto indicesBreak = copyVariableIndices(); |
306 | 28 | _node.condition().accept(*this); |
307 | 28 | mergeVariables( |
308 | 28 | !brokeInCurrentIteration, |
309 | 28 | copyVariableIndices(), |
310 | 28 | indicesBreak |
311 | 28 | ); |
312 | | |
313 | 28 | mergeVariables( |
314 | 28 | broke || !loopCondition, |
315 | 28 | indicesBefore, |
316 | 28 | copyVariableIndices() |
317 | 28 | ); |
318 | 28 | loopCondition = loopCondition && expr(_node.condition()); |
319 | 28 | broke = broke || brokeInCurrentIteration; |
320 | 28 | m_loopCheckpoints.pop(); |
321 | 28 | } |
322 | 28 | if (bmcLoopIterations > 0) |
323 | 28 | m_context.addAssertion(!loopCondition || broke); |
324 | 28 | } |
325 | 113 | else { |
326 | 113 | smtutil::Expression loopConditionOnPreviousIterations(true); |
327 | 226 | for (unsigned int i = 0; i < bmcLoopIterations; ++i) |
328 | 113 | { |
329 | 113 | m_loopCheckpoints.emplace(); |
330 | 113 | auto indicesBefore = copyVariableIndices(); |
331 | 113 | _node.condition().accept(*this); |
332 | 113 | loopCondition = expr(_node.condition()); |
333 | | |
334 | 113 | auto indicesAfterCondition = copyVariableIndices(); |
335 | | |
336 | 113 | pushPathCondition(loopCondition); |
337 | 113 | _node.body().accept(*this); |
338 | 113 | popPathCondition(); |
339 | | |
340 | 113 | auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); |
341 | | |
342 | | // merges indices modified when accepting loop condition that no longer holds |
343 | 113 | mergeVariables( |
344 | 113 | !loopCondition, |
345 | 113 | indicesAfterCondition, |
346 | 113 | copyVariableIndices() |
347 | 113 | ); |
348 | | |
349 | | // handles breaks in previous iterations |
350 | | // breaks in current iterations are handled when traversing loop checkpoints |
351 | | // handles case when the loop condition no longer holds but bmc loop iterations still unrolls the loop |
352 | 113 | mergeVariables( |
353 | 113 | broke || !loopConditionOnPreviousIterations, |
354 | 113 | indicesBefore, |
355 | 113 | copyVariableIndices() |
356 | 113 | ); |
357 | 113 | m_loopCheckpoints.pop(); |
358 | 113 | broke = broke || brokeInCurrentIteration; |
359 | 113 | loopConditionOnPreviousIterations = loopConditionOnPreviousIterations && loopCondition; |
360 | 113 | } |
361 | 113 | if (bmcLoopIterations > 0) |
362 | 113 | { |
363 | | //after loop iterations are done, we check the loop condition last final time |
364 | 113 | auto indices = copyVariableIndices(); |
365 | 113 | m_loopCheckpoints.emplace(); |
366 | 113 | _node.condition().accept(*this); |
367 | 113 | m_loopCheckpoints.pop(); |
368 | 113 | loopCondition = expr(_node.condition()); |
369 | | // assert that the loop is complete |
370 | 113 | m_context.addAssertion(!loopCondition || broke || !loopConditionOnPreviousIterations); |
371 | 113 | mergeVariables( |
372 | 113 | broke || !loopConditionOnPreviousIterations, |
373 | 113 | indices, |
374 | 113 | copyVariableIndices() |
375 | 113 | ); |
376 | 113 | } |
377 | 113 | } |
378 | 141 | m_loopExecutionHappened = true; |
379 | 141 | return false; |
380 | 141 | } |
381 | | |
382 | | // Unrolls for loop |
383 | | bool BMC::visit(ForStatement const& _node) |
384 | 582 | { |
385 | 582 | if (_node.initializationExpression()) |
386 | 515 | _node.initializationExpression()->accept(*this); |
387 | | |
388 | 582 | smtutil::Expression broke(false); |
389 | 582 | smtutil::Expression forCondition(true); |
390 | 582 | smtutil::Expression forConditionOnPreviousIterations(true); |
391 | 582 | unsigned int bmcLoopIterations = m_settings.bmcLoopIterations.value_or(1); |
392 | 1.16k | for (unsigned int i = 0; i < bmcLoopIterations; ++i) |
393 | 582 | { |
394 | 582 | auto indicesBefore = copyVariableIndices(); |
395 | 582 | m_loopCheckpoints.emplace(); |
396 | 582 | if (_node.condition()) |
397 | 541 | { |
398 | 541 | _node.condition()->accept(*this); |
399 | | // values in loop condition might change during loop iteration |
400 | 541 | forCondition = expr(*_node.condition()); |
401 | 541 | } |
402 | 582 | auto indicesAfterCondition = copyVariableIndices(); |
403 | | |
404 | 582 | pushPathCondition(forCondition); |
405 | 582 | _node.body().accept(*this); |
406 | | |
407 | 582 | auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); |
408 | | |
409 | | // accept loop expression if there was no break |
410 | 582 | if (_node.loopExpression()) |
411 | 513 | { |
412 | 513 | auto indicesBreak = copyVariableIndices(); |
413 | 513 | _node.loopExpression()->accept(*this); |
414 | 513 | mergeVariables( |
415 | 513 | !brokeInCurrentIteration, |
416 | 513 | copyVariableIndices(), |
417 | 513 | indicesBreak |
418 | 513 | ); |
419 | 513 | } |
420 | 582 | popPathCondition(); |
421 | | |
422 | | // merges indices modified when accepting loop condition that does no longer hold |
423 | 582 | mergeVariables( |
424 | 582 | !forCondition, |
425 | 582 | indicesAfterCondition, |
426 | 582 | copyVariableIndices() |
427 | 582 | ); |
428 | | |
429 | | // handles breaks in previous iterations |
430 | | // breaks in current iterations are handled when traversing loop checkpoints |
431 | | // handles case when the loop condition no longer holds but bmc loop iterations still unrolls the loop |
432 | 582 | mergeVariables( |
433 | 582 | broke || !forConditionOnPreviousIterations, |
434 | 582 | indicesBefore, |
435 | 582 | copyVariableIndices() |
436 | 582 | ); |
437 | 582 | m_loopCheckpoints.pop(); |
438 | 582 | broke = broke || brokeInCurrentIteration; |
439 | 582 | forConditionOnPreviousIterations = forConditionOnPreviousIterations && forCondition; |
440 | 582 | } |
441 | 582 | if (bmcLoopIterations > 0) |
442 | 582 | { |
443 | | //after loop iterations are done, we check the loop condition last final time |
444 | 582 | auto indices = copyVariableIndices(); |
445 | 582 | if (_node.condition()) |
446 | 541 | { |
447 | 541 | m_loopCheckpoints.emplace(); |
448 | 541 | _node.condition()->accept(*this); |
449 | 541 | forCondition = expr(*_node.condition()); |
450 | 541 | m_loopCheckpoints.pop(); |
451 | 541 | } |
452 | | // assert that the loop is complete |
453 | 582 | m_context.addAssertion(!forCondition || broke || !forConditionOnPreviousIterations); |
454 | 582 | mergeVariables( |
455 | 582 | broke || !forConditionOnPreviousIterations, |
456 | 582 | indices, |
457 | 582 | copyVariableIndices() |
458 | 582 | ); |
459 | 582 | } |
460 | 582 | m_loopExecutionHappened = true; |
461 | 582 | return false; |
462 | 582 | } |
463 | | |
464 | | // merges variables based on loop control statements |
465 | | // returns expression indicating whether there was a break in current loop unroll iteration |
466 | | smtutil::Expression BMC::mergeVariablesFromLoopCheckpoints() |
467 | 723 | { |
468 | 723 | smtutil::Expression continues(false); |
469 | 723 | smtutil::Expression brokeInCurrentIteration(false); |
470 | 723 | for (auto const& loopControl: m_loopCheckpoints.top()) |
471 | 141 | { |
472 | | // use SSAs associated with this break statement only if |
473 | | // loop didn't break or continue earlier in the iteration |
474 | | // loop condition is included in break path conditions |
475 | 141 | mergeVariables( |
476 | 141 | !brokeInCurrentIteration && !continues && loopControl.pathConditions, |
477 | 141 | loopControl.variableIndices, |
478 | 141 | copyVariableIndices() |
479 | 141 | ); |
480 | 141 | if (loopControl.kind == LoopControlKind::Break) |
481 | 82 | brokeInCurrentIteration = |
482 | 82 | brokeInCurrentIteration || loopControl.pathConditions; |
483 | 59 | else if (loopControl.kind == LoopControlKind::Continue) |
484 | 59 | continues = continues || loopControl.pathConditions; |
485 | 141 | } |
486 | 723 | return brokeInCurrentIteration; |
487 | 723 | } |
488 | | |
489 | | bool BMC::visit(TryStatement const& _tryStatement) |
490 | 146 | { |
491 | 146 | FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall()); |
492 | 146 | solAssert(externalCall && externalCall->annotation().tryCall, ""); |
493 | | |
494 | 146 | externalCall->accept(*this); |
495 | 146 | if (_tryStatement.successClause()->parameters()) |
496 | 33 | expressionToTupleAssignment(_tryStatement.successClause()->parameters()->parameters(), *externalCall); |
497 | | |
498 | 146 | smtutil::Expression clauseId = m_context.newVariable("clause_choice_" + std::to_string(m_context.newUniqueId()), smtutil::SortProvider::uintSort); |
499 | 146 | auto const& clauses = _tryStatement.clauses(); |
500 | 146 | m_context.addAssertion(clauseId >= 0 && clauseId < clauses.size()); |
501 | 146 | solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause"); |
502 | 146 | std::vector<std::pair<VariableIndices, smtutil::Expression>> clausesVisitResults; |
503 | 444 | for (size_t i = 0; i < clauses.size(); ++i) |
504 | 298 | clausesVisitResults.push_back(visitBranch(clauses[i].get())); |
505 | | |
506 | | // merge the information from all clauses |
507 | 146 | smtutil::Expression pathCondition = clausesVisitResults.front().second; |
508 | 146 | auto currentIndices = clausesVisitResults[0].first; |
509 | 298 | for (size_t i = 1; i < clauses.size(); ++i) |
510 | 152 | { |
511 | 152 | mergeVariables(clauseId == i, clausesVisitResults[i].first, currentIndices); |
512 | 152 | currentIndices = copyVariableIndices(); |
513 | 152 | pathCondition = pathCondition || clausesVisitResults[i].second; |
514 | 152 | } |
515 | 146 | setPathCondition(pathCondition); |
516 | | |
517 | 146 | return false; |
518 | 146 | } |
519 | | |
520 | | bool BMC::visit(Break const&) |
521 | 82 | { |
522 | 82 | LoopControl control = { |
523 | 82 | LoopControlKind::Break, |
524 | 82 | currentPathConditions(), |
525 | 82 | copyVariableIndices() |
526 | 82 | }; |
527 | 82 | m_loopCheckpoints.top().emplace_back(control); |
528 | 82 | return false; |
529 | 82 | } |
530 | | |
531 | | bool BMC::visit(Continue const&) |
532 | 59 | { |
533 | 59 | LoopControl control = { |
534 | 59 | LoopControlKind::Continue, |
535 | 59 | currentPathConditions(), |
536 | 59 | copyVariableIndices() |
537 | 59 | }; |
538 | 59 | m_loopCheckpoints.top().emplace_back(control); |
539 | 59 | return false; |
540 | 59 | } |
541 | | |
542 | | void BMC::endVisit(UnaryOperation const& _op) |
543 | 4.88k | { |
544 | 4.88k | SMTEncoder::endVisit(_op); |
545 | | |
546 | | // User-defined operators are essentially function calls. |
547 | 4.88k | if (auto funDef = *_op.annotation().userDefinedFunction) |
548 | 13 | { |
549 | 13 | std::vector<Expression const*> arguments; |
550 | 13 | arguments.push_back(&_op.subExpression()); |
551 | | // pushCallStack and defineExpr inside createReturnedExpression should be called on the expression |
552 | | // in case of a user-defined operator call |
553 | 13 | inlineFunctionCall(funDef, _op, std::nullopt, arguments); |
554 | 13 | return; |
555 | 13 | } |
556 | | |
557 | 4.87k | if ( |
558 | 4.87k | _op.annotation().type->category() == Type::Category::RationalNumber || |
559 | 4.87k | _op.annotation().type->category() == Type::Category::FixedPoint |
560 | 4.87k | ) |
561 | 1.29k | return; |
562 | | |
563 | 3.58k | if (_op.getOperator() == Token::Sub && smt::isInteger(*_op.annotation().type)) |
564 | 161 | { |
565 | 161 | addVerificationTarget( |
566 | 161 | VerificationTargetType::Underflow, |
567 | 161 | expr(_op), |
568 | 161 | &_op |
569 | 161 | ); |
570 | 161 | addVerificationTarget( |
571 | 161 | VerificationTargetType::Overflow, |
572 | 161 | expr(_op), |
573 | 161 | &_op |
574 | 161 | ); |
575 | 161 | } |
576 | 3.58k | } |
577 | | |
578 | | void BMC::endVisit(BinaryOperation const& _op) |
579 | 25.1k | { |
580 | 25.1k | SMTEncoder::endVisit(_op); |
581 | | |
582 | 25.1k | if (auto funDef = *_op.annotation().userDefinedFunction) |
583 | 33 | { |
584 | 33 | std::vector<Expression const*> arguments; |
585 | 33 | arguments.push_back(&_op.leftExpression()); |
586 | 33 | arguments.push_back(&_op.rightExpression()); |
587 | | |
588 | | // pushCallStack and defineExpr inside createReturnedExpression should be called on the expression |
589 | | // in case of a user-defined operator call |
590 | 33 | inlineFunctionCall(funDef, _op, std::nullopt, arguments); |
591 | 33 | } |
592 | 25.1k | } |
593 | | |
594 | | void BMC::endVisit(FunctionCall const& _funCall) |
595 | 16.1k | { |
596 | 16.1k | auto functionCallKind = *_funCall.annotation().kind; |
597 | | |
598 | 16.1k | if (functionCallKind != FunctionCallKind::FunctionCall) |
599 | 3.04k | { |
600 | 3.04k | SMTEncoder::endVisit(_funCall); |
601 | 3.04k | return; |
602 | 3.04k | } |
603 | | |
604 | 13.1k | FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); |
605 | 13.1k | switch (funType.kind()) |
606 | 13.1k | { |
607 | 2.28k | case FunctionType::Kind::Assert: |
608 | 2.28k | visitAssert(_funCall); |
609 | 2.28k | SMTEncoder::endVisit(_funCall); |
610 | 2.28k | break; |
611 | 448 | case FunctionType::Kind::Require: |
612 | 448 | visitRequire(_funCall); |
613 | 448 | SMTEncoder::endVisit(_funCall); |
614 | 448 | break; |
615 | 2.00k | case FunctionType::Kind::Internal: |
616 | 2.90k | case FunctionType::Kind::External: |
617 | 3.11k | case FunctionType::Kind::DelegateCall: |
618 | 3.39k | case FunctionType::Kind::BareCall: |
619 | 3.39k | case FunctionType::Kind::BareCallCode: |
620 | 3.40k | case FunctionType::Kind::BareDelegateCall: |
621 | 3.41k | case FunctionType::Kind::BareStaticCall: |
622 | 3.72k | case FunctionType::Kind::Creation: |
623 | 3.72k | SMTEncoder::endVisit(_funCall); |
624 | 3.72k | internalOrExternalFunctionCall(_funCall); |
625 | 3.72k | break; |
626 | 17 | case FunctionType::Kind::Send: |
627 | 110 | case FunctionType::Kind::Transfer: |
628 | 110 | { |
629 | 110 | auto value = _funCall.arguments().front(); |
630 | 110 | solAssert(value, ""); |
631 | 110 | smtutil::Expression thisBalance = state().balance(); |
632 | | |
633 | 110 | addVerificationTarget( |
634 | 110 | VerificationTargetType::Balance, |
635 | 110 | thisBalance < expr(*value), |
636 | 110 | &_funCall |
637 | 110 | ); |
638 | | |
639 | 110 | SMTEncoder::endVisit(_funCall); |
640 | 110 | break; |
641 | 110 | } |
642 | 140 | case FunctionType::Kind::KECCAK256: |
643 | 162 | case FunctionType::Kind::ECRecover: |
644 | 179 | case FunctionType::Kind::SHA256: |
645 | 286 | case FunctionType::Kind::RIPEMD160: |
646 | 288 | case FunctionType::Kind::BlobHash: |
647 | 316 | case FunctionType::Kind::BlockHash: |
648 | 372 | case FunctionType::Kind::AddMod: |
649 | 389 | case FunctionType::Kind::MulMod: |
650 | 501 | case FunctionType::Kind::Unwrap: |
651 | 697 | case FunctionType::Kind::Wrap: |
652 | 697 | [[fallthrough]]; |
653 | 6.57k | default: |
654 | 6.57k | SMTEncoder::endVisit(_funCall); |
655 | 6.57k | break; |
656 | 13.1k | } |
657 | 13.1k | } |
658 | | |
659 | | void BMC::endVisit(Return const& _return) |
660 | 5.79k | { |
661 | 5.79k | SMTEncoder::endVisit(_return); |
662 | 5.79k | setPathCondition(smtutil::Expression(false)); |
663 | 5.79k | } |
664 | | |
665 | | /// Visitor helpers. |
666 | | |
667 | | void BMC::visitAssert(FunctionCall const& _funCall) |
668 | 2.28k | { |
669 | 2.28k | auto const& args = _funCall.arguments(); |
670 | 2.28k | solAssert(args.size() == 1, ""); |
671 | 2.28k | solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); |
672 | 2.28k | addVerificationTarget( |
673 | 2.28k | VerificationTargetType::Assert, |
674 | 2.28k | expr(*args.front()), |
675 | 2.28k | &_funCall |
676 | 2.28k | ); |
677 | 2.28k | } |
678 | | |
679 | | void BMC::visitRequire(FunctionCall const& _funCall) |
680 | 448 | { |
681 | 448 | auto const& args = _funCall.arguments(); |
682 | 448 | solAssert(args.size() >= 1, ""); |
683 | 448 | solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); |
684 | 448 | checkIfConditionIsConstant(*args.front()); |
685 | 448 | } |
686 | | |
687 | | void BMC::visitAddMulMod(FunctionCall const& _funCall) |
688 | 73 | { |
689 | 73 | solAssert(_funCall.arguments().at(2), ""); |
690 | 73 | addVerificationTarget( |
691 | 73 | VerificationTargetType::DivByZero, |
692 | 73 | expr(*_funCall.arguments().at(2)), |
693 | 73 | &_funCall |
694 | 73 | ); |
695 | | |
696 | 73 | SMTEncoder::visitAddMulMod(_funCall); |
697 | 73 | } |
698 | | |
699 | | void BMC::inlineFunctionCall( |
700 | | FunctionDefinition const* _funDef, |
701 | | Expression const& _callStackExpr, |
702 | | std::optional<Expression const*> _boundArgumentCall, |
703 | | std::vector<Expression const*> const& _arguments |
704 | | ) |
705 | 2.27k | { |
706 | 2.27k | solAssert(_funDef, ""); |
707 | | |
708 | 2.27k | if (visitedFunction(_funDef)) |
709 | 345 | { |
710 | 345 | auto const& returnParams = _funDef->returnParameters(); |
711 | 345 | for (auto param: returnParams) |
712 | 355 | { |
713 | 355 | m_context.newValue(*param); |
714 | 355 | m_context.setUnknownValue(*param); |
715 | 355 | } |
716 | 345 | } |
717 | 1.93k | else |
718 | 1.93k | { |
719 | 1.93k | initializeFunctionCallParameters(*_funDef, symbolicArguments(_funDef->parameters(), _arguments, _boundArgumentCall)); |
720 | | |
721 | | // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) |
722 | | // is that there we don't have `_callStackExpr`. |
723 | 1.93k | pushCallStack({_funDef, &_callStackExpr}); |
724 | 1.93k | pushPathCondition(currentPathConditions()); |
725 | 1.93k | auto oldChecked = std::exchange(m_checked, true); |
726 | 1.93k | _funDef->accept(*this); |
727 | 1.93k | m_checked = oldChecked; |
728 | 1.93k | popPathCondition(); |
729 | 1.93k | } |
730 | | |
731 | 2.27k | createReturnedExpressions(_funDef, _callStackExpr); |
732 | 2.27k | } |
733 | | |
734 | | void BMC::inlineFunctionCall(FunctionCall const& _funCall) |
735 | 2.23k | { |
736 | 2.23k | solAssert(shouldInlineFunctionCall(_funCall, currentScopeContract(), m_currentContract), ""); |
737 | | |
738 | 2.23k | auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
739 | 2.23k | Expression const* expr = &_funCall.expression(); |
740 | 2.23k | auto funType = dynamic_cast<FunctionType const*>(expr->annotation().type); |
741 | 2.23k | std::optional<Expression const*> boundArgumentCall = |
742 | 2.23k | funType->hasBoundFirstArgument() ? std::make_optional(expr) : std::nullopt; |
743 | | |
744 | 2.23k | std::vector<Expression const*> arguments; |
745 | 2.23k | for (auto& arg: _funCall.sortedArguments()) |
746 | 1.04k | arguments.push_back(&(*arg)); |
747 | | |
748 | | // pushCallStack and defineExpr inside createReturnedExpression should be called |
749 | | // on the FunctionCall object for the normal function call case |
750 | 2.23k | inlineFunctionCall(funDef, _funCall, boundArgumentCall, arguments); |
751 | 2.23k | } |
752 | | |
753 | | void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall) |
754 | 3.72k | { |
755 | 3.72k | auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); |
756 | 3.72k | if (shouldInlineFunctionCall(_funCall, currentScopeContract(), m_currentContract)) |
757 | 2.23k | inlineFunctionCall(_funCall); |
758 | 1.49k | else if (publicGetter(_funCall.expression())) |
759 | 187 | { |
760 | | // Do nothing here. |
761 | | // The processing happens in SMT Encoder, but we need to prevent the resetting of the state variables. |
762 | 187 | } |
763 | 1.30k | else if (funType.kind() == FunctionType::Kind::Internal) |
764 | 134 | m_unsupportedErrors.warning( |
765 | 134 | 5729_error, |
766 | 134 | _funCall.location(), |
767 | 134 | "BMC does not yet implement this type of function call." |
768 | 134 | ); |
769 | 1.17k | else if (funType.kind() == FunctionType::Kind::BareStaticCall) |
770 | 14 | { |
771 | | // Do nothing here. |
772 | | // Neither storage nor balances should be modified. |
773 | 14 | } |
774 | 1.16k | else |
775 | 1.16k | { |
776 | 1.16k | m_externalFunctionCallHappened = true; |
777 | 1.16k | resetStorageVariables(); |
778 | 1.16k | resetBalances(); |
779 | 1.16k | } |
780 | 3.72k | } |
781 | | |
782 | | std::pair<smtutil::Expression, smtutil::Expression> BMC::arithmeticOperation( |
783 | | Token _op, |
784 | | smtutil::Expression const& _left, |
785 | | smtutil::Expression const& _right, |
786 | | Type const* _commonType, |
787 | | Expression const& _expression |
788 | | ) |
789 | 14.1k | { |
790 | | // Unchecked does not disable div by 0 checks. |
791 | 14.1k | if (_op == Token::Div || _op == Token::Mod) |
792 | 1.17k | addVerificationTarget( |
793 | 1.17k | VerificationTargetType::DivByZero, |
794 | 1.17k | _right, |
795 | 1.17k | &_expression |
796 | 1.17k | ); |
797 | | |
798 | 14.1k | auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); |
799 | | |
800 | 14.1k | if (!m_checked) |
801 | 136 | return values; |
802 | | |
803 | 14.0k | auto const* intType = dynamic_cast<IntegerType const*>(_commonType); |
804 | 14.0k | if (!intType) |
805 | 220 | intType = TypeProvider::uint256(); |
806 | | |
807 | | // Mod does not need underflow/overflow checks. |
808 | 14.0k | if (_op == Token::Mod) |
809 | 505 | return values; |
810 | | |
811 | | // The order matters here: |
812 | | // If _op is Div and intType is signed, we only care about overflow. |
813 | 13.5k | if (_op == Token::Div) |
814 | 662 | { |
815 | 662 | if (intType->isSigned()) |
816 | | // Signed division can only overflow. |
817 | 76 | addVerificationTarget(VerificationTargetType::Overflow, values.second, &_expression); |
818 | 586 | else |
819 | | // Unsigned division cannot underflow/overflow. |
820 | 586 | return values; |
821 | 662 | } |
822 | 12.8k | else if (intType->isSigned()) |
823 | 689 | { |
824 | 689 | addVerificationTarget(VerificationTargetType::Overflow, values.second, &_expression); |
825 | 689 | addVerificationTarget(VerificationTargetType::Underflow, values.second, &_expression); |
826 | 689 | } |
827 | 12.1k | else if (_op == Token::Sub) |
828 | 5.52k | addVerificationTarget(VerificationTargetType::Underflow, values.second, &_expression); |
829 | 6.64k | else if (_op == Token::Add || _op == Token::Mul) |
830 | 6.64k | addVerificationTarget(VerificationTargetType::Overflow, values.second, &_expression); |
831 | 0 | else |
832 | 6.64k | solAssert(false, ""); |
833 | | |
834 | 12.9k | return values; |
835 | 13.5k | } |
836 | | |
837 | | void BMC::reset() |
838 | 17.0k | { |
839 | 17.0k | m_externalFunctionCallHappened = false; |
840 | 17.0k | m_loopExecutionHappened = false; |
841 | 17.0k | } |
842 | | |
843 | | std::pair<std::vector<smtutil::Expression>, std::vector<std::string>> BMC::modelExpressions() |
844 | 3.63k | { |
845 | 3.63k | std::vector<smtutil::Expression> expressionsToEvaluate; |
846 | 3.63k | std::vector<std::string> expressionNames; |
847 | 3.63k | for (auto const& var: m_context.variables()) |
848 | 8.82k | if (var.first->type()->isValueType()) |
849 | 6.52k | { |
850 | 6.52k | expressionsToEvaluate.emplace_back(currentValue(*var.first)); |
851 | 6.52k | expressionNames.push_back(var.first->name()); |
852 | 6.52k | } |
853 | 3.63k | for (auto const& var: m_context.globalSymbols()) |
854 | 340 | { |
855 | 340 | auto const& type = var.second->type(); |
856 | 340 | if ( |
857 | 340 | type->isValueType() && |
858 | 340 | smt::smtKind(*type) != smtutil::Kind::Function |
859 | 340 | ) |
860 | 20 | { |
861 | 20 | expressionsToEvaluate.emplace_back(var.second->currentValue()); |
862 | 20 | expressionNames.push_back(var.first); |
863 | 20 | } |
864 | 340 | } |
865 | 3.63k | for (auto const& uf: m_uninterpretedTerms) |
866 | 9.99k | if (uf->annotation().type->isValueType()) |
867 | 6.14k | { |
868 | 6.14k | expressionsToEvaluate.emplace_back(expr(*uf)); |
869 | 6.14k | std::string expressionName; |
870 | 6.14k | if (uf->location().hasText()) |
871 | 6.14k | expressionName = m_charStreamProvider.charStream(*uf->location().sourceName).text( |
872 | 6.14k | uf->location() |
873 | 6.14k | ); |
874 | 6.14k | expressionNames.push_back(std::move(expressionName)); |
875 | 6.14k | } |
876 | | |
877 | 3.63k | return {expressionsToEvaluate, expressionNames}; |
878 | 3.63k | } |
879 | | |
880 | | /// Verification targets. |
881 | | |
882 | | std::string BMC::targetDescription(BMCVerificationTarget const& _target) |
883 | 0 | { |
884 | 0 | if ( |
885 | 0 | _target.type == VerificationTargetType::Underflow || |
886 | 0 | _target.type == VerificationTargetType::Overflow |
887 | 0 | ) |
888 | 0 | { |
889 | 0 | auto const* intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type); |
890 | 0 | if (!intType) |
891 | 0 | intType = TypeProvider::uint256(); |
892 | |
|
893 | 0 | if (_target.type == VerificationTargetType::Underflow) |
894 | 0 | return "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")"; |
895 | 0 | return "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")"; |
896 | 0 | } |
897 | 0 | else if (_target.type == VerificationTargetType::DivByZero) |
898 | 0 | return "Division by zero"; |
899 | 0 | else if (_target.type == VerificationTargetType::Assert) |
900 | 0 | return "Assertion violation"; |
901 | 0 | else if (_target.type == VerificationTargetType::Balance) |
902 | 0 | return "Insufficient funds"; |
903 | 0 | solAssert(false); |
904 | 0 | } |
905 | | |
906 | | void BMC::checkVerificationTargets() |
907 | 32.8k | { |
908 | 32.8k | for (auto& target: m_verificationTargets) |
909 | 3.62k | checkVerificationTarget(target); |
910 | 32.8k | } |
911 | | |
912 | | void BMC::checkVerificationTarget(BMCVerificationTarget& _target) |
913 | 3.62k | { |
914 | 3.62k | if ( |
915 | 3.62k | m_solvedTargets.count(_target.expression) && |
916 | 3.62k | m_solvedTargets.at(_target.expression).count(_target.type) |
917 | 3.62k | ) |
918 | 33 | return; |
919 | | |
920 | 3.58k | switch (_target.type) |
921 | 3.58k | { |
922 | 0 | case VerificationTargetType::Underflow: |
923 | 0 | checkUnderflow(_target); |
924 | 0 | break; |
925 | 0 | case VerificationTargetType::Overflow: |
926 | 0 | checkOverflow(_target); |
927 | 0 | break; |
928 | 1.22k | case VerificationTargetType::DivByZero: |
929 | 1.22k | checkDivByZero(_target); |
930 | 1.22k | break; |
931 | 107 | case VerificationTargetType::Balance: |
932 | 107 | checkBalance(_target); |
933 | 107 | break; |
934 | 2.25k | case VerificationTargetType::Assert: |
935 | 2.25k | checkAssert(_target); |
936 | 2.25k | break; |
937 | 0 | case VerificationTargetType::ConstantCondition: |
938 | 0 | smtAssert(false, "Checks for constant condition are handled separately"); |
939 | 0 | default: |
940 | 0 | smtAssert(false); |
941 | 3.58k | } |
942 | 3.58k | } |
943 | | |
944 | | void BMC::checkIfConditionIsConstant(Expression const& _condition) |
945 | 1.88k | { |
946 | 1.88k | if ( |
947 | 1.88k | !m_settings.targets.has(VerificationTargetType::ConstantCondition) || |
948 | 1.88k | (m_currentContract && !shouldEncode(*m_currentContract)) |
949 | 1.88k | ) |
950 | 15 | return; |
951 | | |
952 | | // We ignore called functions here because they have specific input values. |
953 | | // Also, expressions inside loop can have different values in different iterations. |
954 | 1.86k | if (!isRootFunction() || isInsideLoop()) |
955 | 394 | return; |
956 | | |
957 | | // Do not check for const-ness if this is a literal. |
958 | 1.47k | if (dynamic_cast<Literal const*>(&_condition)) |
959 | 77 | return; |
960 | | |
961 | 1.39k | auto [canBeTrue, canBeFalse] = checkBooleanNotConstant(currentPathConditions() && m_context.assertions(), expr(_condition)); |
962 | | |
963 | | // Report based on the result of the checks |
964 | 1.39k | if (canBeTrue == CheckResult::ERROR || canBeFalse == CheckResult::ERROR) |
965 | 0 | m_errorReporter.warning(8592_error, _condition.location(), "BMC: Error trying to invoke SMT solver."); |
966 | 1.39k | else if (canBeTrue == CheckResult::CONFLICTING || canBeFalse == CheckResult::CONFLICTING) |
967 | 0 | m_errorReporter.warning(3356_error, _condition.location(), "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound."); |
968 | 1.39k | else if (canBeTrue == CheckResult::UNKNOWN || canBeFalse == CheckResult::UNKNOWN) |
969 | 1.39k | { |
970 | | // Not enough information to make definite claims. |
971 | 1.39k | } |
972 | 0 | else if (canBeTrue == CheckResult::SATISFIABLE && canBeFalse == CheckResult::SATISFIABLE) |
973 | 0 | { |
974 | | // Condition can be both true and false for some program runs. |
975 | 0 | } |
976 | | |
977 | 0 | else if (canBeTrue == CheckResult::UNSATISFIABLE && canBeFalse == CheckResult::UNSATISFIABLE) |
978 | 0 | m_errorReporter.warning(2512_error, _condition.location(), "BMC: Condition unreachable.", SMTEncoder::callStackMessage(m_callStack)); |
979 | 0 | else |
980 | 0 | { |
981 | 0 | std::string description; |
982 | 0 | if (canBeFalse == smtutil::CheckResult::UNSATISFIABLE) |
983 | 0 | { |
984 | 0 | smtAssert(canBeTrue == smtutil::CheckResult::SATISFIABLE); |
985 | 0 | description = "BMC: Condition is always true."; |
986 | 0 | } |
987 | 0 | else |
988 | 0 | { |
989 | 0 | smtAssert(canBeTrue == smtutil::CheckResult::UNSATISFIABLE); |
990 | 0 | smtAssert(canBeFalse == smtutil::CheckResult::SATISFIABLE); |
991 | 0 | description = "BMC: Condition is always false."; |
992 | 0 | } |
993 | 0 | m_errorReporter.warning( |
994 | 0 | 6838_error, |
995 | 0 | _condition.location(), |
996 | 0 | description, |
997 | 0 | SMTEncoder::callStackMessage(m_callStack) |
998 | 0 | ); |
999 | 0 | } |
1000 | | |
1001 | 1.39k | } |
1002 | | |
1003 | | void BMC::checkUnderflow(BMCVerificationTarget& _target) |
1004 | 0 | { |
1005 | 0 | solAssert( |
1006 | 0 | _target.type == VerificationTargetType::Underflow, |
1007 | 0 | "" |
1008 | 0 | ); |
1009 | | |
1010 | 0 | auto const* intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type); |
1011 | 0 | if (!intType) |
1012 | 0 | intType = TypeProvider::uint256(); |
1013 | |
|
1014 | 0 | checkCondition( |
1015 | 0 | _target, |
1016 | 0 | _target.constraints && _target.value < smt::minValue(*intType), |
1017 | 0 | _target.callStack, |
1018 | 0 | _target.modelExpressions, |
1019 | 0 | _target.expression->location(), |
1020 | 0 | 4144_error, |
1021 | 0 | 8312_error, |
1022 | 0 | "<result>", |
1023 | 0 | &_target.value |
1024 | 0 | ); |
1025 | 0 | } |
1026 | | |
1027 | | void BMC::checkOverflow(BMCVerificationTarget& _target) |
1028 | 0 | { |
1029 | 0 | solAssert( |
1030 | 0 | _target.type == VerificationTargetType::Overflow, |
1031 | 0 | "" |
1032 | 0 | ); |
1033 | | |
1034 | 0 | auto const* intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type); |
1035 | 0 | if (!intType) |
1036 | 0 | intType = TypeProvider::uint256(); |
1037 | |
|
1038 | 0 | checkCondition( |
1039 | 0 | _target, |
1040 | 0 | _target.constraints && _target.value > smt::maxValue(*intType), |
1041 | 0 | _target.callStack, |
1042 | 0 | _target.modelExpressions, |
1043 | 0 | _target.expression->location(), |
1044 | 0 | 2661_error, |
1045 | 0 | 8065_error, |
1046 | 0 | "<result>", |
1047 | 0 | &_target.value |
1048 | 0 | ); |
1049 | 0 | } |
1050 | | |
1051 | | void BMC::checkDivByZero(BMCVerificationTarget& _target) |
1052 | 1.22k | { |
1053 | 1.22k | solAssert(_target.type == VerificationTargetType::DivByZero, ""); |
1054 | | |
1055 | 1.22k | checkCondition( |
1056 | 1.22k | _target, |
1057 | 1.22k | _target.constraints && (_target.value == 0), |
1058 | 1.22k | _target.callStack, |
1059 | 1.22k | _target.modelExpressions, |
1060 | 1.22k | _target.expression->location(), |
1061 | 1.22k | 3046_error, |
1062 | 1.22k | 5272_error, |
1063 | 1.22k | "<result>", |
1064 | 1.22k | &_target.value |
1065 | 1.22k | ); |
1066 | 1.22k | } |
1067 | | |
1068 | | void BMC::checkBalance(BMCVerificationTarget& _target) |
1069 | 107 | { |
1070 | 107 | solAssert(_target.type == VerificationTargetType::Balance, ""); |
1071 | | |
1072 | 107 | checkCondition( |
1073 | 107 | _target, |
1074 | 107 | _target.constraints && _target.value, |
1075 | 107 | _target.callStack, |
1076 | 107 | _target.modelExpressions, |
1077 | 107 | _target.expression->location(), |
1078 | 107 | 1236_error, |
1079 | 107 | 4010_error, |
1080 | 107 | "address(this).balance" |
1081 | 107 | ); |
1082 | 107 | } |
1083 | | |
1084 | | void BMC::checkAssert(BMCVerificationTarget& _target) |
1085 | 2.25k | { |
1086 | 2.25k | solAssert(_target.type == VerificationTargetType::Assert, ""); |
1087 | | |
1088 | 2.25k | checkCondition( |
1089 | 2.25k | _target, |
1090 | 2.25k | _target.constraints && !_target.value, |
1091 | 2.25k | _target.callStack, |
1092 | 2.25k | _target.modelExpressions, |
1093 | 2.25k | _target.expression->location(), |
1094 | 2.25k | 4661_error, |
1095 | 2.25k | 7812_error |
1096 | 2.25k | ); |
1097 | 2.25k | } |
1098 | | |
1099 | | void BMC::addVerificationTarget( |
1100 | | VerificationTargetType _type, |
1101 | | smtutil::Expression const& _value, |
1102 | | Expression const* _expression |
1103 | | ) |
1104 | 17.5k | { |
1105 | 17.5k | smtAssert(_type != VerificationTargetType::ConstantCondition, "Checks for constant condition are handled separately"); |
1106 | 17.5k | if (!m_settings.targets.has(_type) || (m_currentContract && !shouldAnalyzeVerificationTargetsFor(*m_currentContract))) |
1107 | 13.9k | return; |
1108 | | |
1109 | 3.63k | BMCVerificationTarget target{ |
1110 | 3.63k | { |
1111 | 3.63k | _type, |
1112 | 3.63k | _value, |
1113 | 3.63k | currentPathConditions() && m_context.assertions() |
1114 | 3.63k | }, |
1115 | 3.63k | _expression, |
1116 | 3.63k | m_callStack, |
1117 | 3.63k | modelExpressions() |
1118 | 3.63k | }; |
1119 | 3.63k | m_verificationTargets.emplace_back(std::move(target)); |
1120 | 3.63k | } |
1121 | | |
1122 | | /// Solving. |
1123 | | |
1124 | | void BMC::checkCondition( |
1125 | | BMCVerificationTarget const& _target, |
1126 | | smtutil::Expression _condition, |
1127 | | std::vector<SMTEncoder::CallStackEntry> const& _callStack, |
1128 | | std::pair<std::vector<smtutil::Expression>, std::vector<std::string>> const& _modelExpressions, |
1129 | | SourceLocation const& _location, |
1130 | | ErrorId _errorHappens, |
1131 | | ErrorId _errorMightHappen, |
1132 | | std::string const& _additionalValueName, |
1133 | | smtutil::Expression const* _additionalValue |
1134 | | ) |
1135 | 3.58k | { |
1136 | 3.58k | m_interface->push(); |
1137 | 3.58k | m_interface->addAssertion(_condition); |
1138 | | |
1139 | 3.58k | std::vector<smtutil::Expression> expressionsToEvaluate; |
1140 | 3.58k | std::vector<std::string> expressionNames; |
1141 | 3.58k | tie(expressionsToEvaluate, expressionNames) = _modelExpressions; |
1142 | 3.58k | if (!_callStack.empty()) |
1143 | 3.58k | if (_additionalValue) |
1144 | 1.22k | { |
1145 | 1.22k | expressionsToEvaluate.emplace_back(*_additionalValue); |
1146 | 1.22k | expressionNames.push_back(_additionalValueName); |
1147 | 1.22k | } |
1148 | 3.58k | smtutil::CheckResult result; |
1149 | 3.58k | std::vector<std::string> values; |
1150 | 3.58k | tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); |
1151 | | |
1152 | 3.58k | std::string extraComment = SMTEncoder::extraComment(); |
1153 | 3.58k | if (m_loopExecutionHappened) |
1154 | 98 | extraComment += |
1155 | 98 | "False negatives are possible when unrolling loops.\n" |
1156 | 98 | "This is due to the possibility that the BMC loop iteration setting is" |
1157 | 98 | " smaller than the actual number of iterations needed to complete a loop."; |
1158 | 3.58k | if (m_externalFunctionCallHappened) |
1159 | 86 | extraComment += |
1160 | 86 | "\nNote that external function calls are not inlined," |
1161 | 86 | " even if the source code of the function is available." |
1162 | 86 | " This is due to the possibility that the actual called contract" |
1163 | 86 | " has the same ABI but implements the function differently."; |
1164 | | |
1165 | 3.58k | SecondarySourceLocation secondaryLocation{}; |
1166 | 3.58k | secondaryLocation.append(extraComment, SourceLocation{}); |
1167 | | |
1168 | 3.58k | switch (result) |
1169 | 3.58k | { |
1170 | 0 | case smtutil::CheckResult::SATISFIABLE: |
1171 | 0 | { |
1172 | 0 | solAssert(!_callStack.empty(), ""); |
1173 | 0 | std::ostringstream message; |
1174 | 0 | message << "BMC: " << targetDescription(_target) << " happens here."; |
1175 | |
|
1176 | 0 | std::ostringstream modelMessage; |
1177 | | // Sometimes models have complex smtlib2 expressions that SMTLib2Interface fails to parse. |
1178 | 0 | if (values.size() == expressionNames.size()) |
1179 | 0 | { |
1180 | 0 | modelMessage << "Counterexample:\n"; |
1181 | 0 | std::map<std::string, std::string> sortedModel; |
1182 | 0 | for (size_t i = 0; i < values.size(); ++i) |
1183 | 0 | if (expressionsToEvaluate.at(i).name != values.at(i)) |
1184 | 0 | sortedModel[expressionNames.at(i)] = values.at(i); |
1185 | |
|
1186 | 0 | for (auto const& eval: sortedModel) |
1187 | 0 | modelMessage << " " << eval.first << " = " << eval.second << "\n"; |
1188 | 0 | } |
1189 | |
|
1190 | 0 | m_errorReporter.warning( |
1191 | 0 | _errorHappens, |
1192 | 0 | _location, |
1193 | 0 | message.str(), |
1194 | 0 | SecondarySourceLocation().append(modelMessage.str(), SourceLocation{}) |
1195 | 0 | .append(SMTEncoder::callStackMessage(_callStack)) |
1196 | 0 | .append(std::move(secondaryLocation)) |
1197 | 0 | ); |
1198 | 0 | break; |
1199 | 0 | } |
1200 | 0 | case smtutil::CheckResult::UNSATISFIABLE: |
1201 | 0 | { |
1202 | 0 | m_safeTargets[_target.expression].insert(_target); |
1203 | 0 | break; |
1204 | 0 | } |
1205 | 3.58k | case smtutil::CheckResult::UNKNOWN: |
1206 | 3.58k | { |
1207 | 3.58k | ++m_unprovedAmt; |
1208 | 3.58k | if (m_settings.showUnproved) |
1209 | 0 | m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + targetDescription(_target) + " might happen here.", secondaryLocation); |
1210 | 3.58k | break; |
1211 | 0 | } |
1212 | 0 | case smtutil::CheckResult::CONFLICTING: |
1213 | 0 | m_errorReporter.warning(1584_error, _location, "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound."); |
1214 | 0 | break; |
1215 | 0 | case smtutil::CheckResult::ERROR: |
1216 | 0 | m_errorReporter.warning(1823_error, _location, "BMC: Error during interaction with the SMT solver."); |
1217 | 0 | break; |
1218 | 3.58k | } |
1219 | | |
1220 | 3.58k | m_interface->pop(); |
1221 | 3.58k | } |
1222 | | |
1223 | | BMC::ConstantExpressionCheckResult BMC::checkBooleanNotConstant( |
1224 | | smtutil::Expression const& _constraints, |
1225 | | smtutil::Expression const& _condition |
1226 | | ) |
1227 | 1.39k | { |
1228 | 1.39k | m_interface->push(); |
1229 | 1.39k | m_interface->addAssertion(_constraints && _condition); |
1230 | 1.39k | auto positiveResult = checkSatisfiable(); |
1231 | 1.39k | m_interface->pop(); |
1232 | | |
1233 | 1.39k | m_interface->push(); |
1234 | 1.39k | m_interface->addAssertion(_constraints && !_condition); |
1235 | 1.39k | auto negatedResult = checkSatisfiable(); |
1236 | 1.39k | m_interface->pop(); |
1237 | | |
1238 | 1.39k | return {.canBeTrue = positiveResult, .canBeFalse = negatedResult}; |
1239 | 1.39k | } |
1240 | | |
1241 | | std::pair<smtutil::CheckResult, std::vector<std::string>> |
1242 | | BMC::checkSatisfiableAndGenerateModel(std::vector<smtutil::Expression> const& _expressionsToEvaluate) |
1243 | 6.37k | { |
1244 | 6.37k | smtutil::CheckResult result; |
1245 | 6.37k | std::vector<std::string> values; |
1246 | 6.37k | try |
1247 | 6.37k | { |
1248 | 6.37k | if (m_settings.printQuery) |
1249 | 0 | { |
1250 | 0 | auto portfolio = dynamic_cast<smtutil::SMTPortfolio*>(m_interface.get()); |
1251 | 0 | std::string smtlibCode = portfolio->dumpQuery(_expressionsToEvaluate); |
1252 | 0 | m_errorReporter.info( |
1253 | 0 | 6240_error, |
1254 | 0 | "BMC: Requested query:\n" + smtlibCode |
1255 | 0 | ); |
1256 | 0 | result = CheckResult::UNKNOWN; |
1257 | 0 | } |
1258 | 6.37k | else |
1259 | 6.37k | tie(result, values) = m_interface->check(_expressionsToEvaluate); |
1260 | 6.37k | } |
1261 | 6.37k | catch (smtutil::SolverError const& _e) |
1262 | 6.37k | { |
1263 | 0 | std::string description("BMC: Error querying SMT solver"); |
1264 | 0 | if (_e.comment()) |
1265 | 0 | description += ": " + *_e.comment(); |
1266 | 0 | m_errorReporter.warning(8140_error, description); |
1267 | 0 | result = smtutil::CheckResult::ERROR; |
1268 | 0 | } |
1269 | | |
1270 | 6.37k | for (std::string& value: values) |
1271 | 0 | { |
1272 | 0 | try |
1273 | 0 | { |
1274 | | // Parse and re-format nicely |
1275 | 0 | value = formatNumberReadable(bigint(value)); |
1276 | 0 | } |
1277 | 0 | catch (...) { } |
1278 | 0 | } |
1279 | | |
1280 | 6.37k | return make_pair(result, values); |
1281 | 6.37k | } |
1282 | | |
1283 | | smtutil::CheckResult BMC::checkSatisfiable() |
1284 | 2.79k | { |
1285 | 2.79k | return checkSatisfiableAndGenerateModel({}).first; |
1286 | 2.79k | } |
1287 | | |
1288 | | void BMC::assignment(smt::SymbolicVariable& _symVar, smtutil::Expression const& _value) |
1289 | 26.8k | { |
1290 | 26.8k | auto oldVar = _symVar.currentValue(); |
1291 | 26.8k | auto newVar = _symVar.increaseIndex(); |
1292 | 26.8k | m_context.addAssertion(smtutil::Expression::ite( |
1293 | 26.8k | currentPathConditions(), |
1294 | 26.8k | newVar == _value, |
1295 | 26.8k | newVar == oldVar |
1296 | 26.8k | )); |
1297 | 26.8k | } |
1298 | | |
1299 | | bool BMC::isInsideLoop() const |
1300 | 1.62k | { |
1301 | 1.62k | return !m_loopCheckpoints.empty(); |
1302 | 1.62k | } |