/src/solidity/libsolidity/formal/CHC.cpp
Line | Count | Source |
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/CHC.h> |
20 | | |
21 | | #include <libsolidity/formal/ArraySlicePredicate.h> |
22 | | #include <libsolidity/formal/ExpressionFormatter.h> |
23 | | #include <libsolidity/formal/EldaricaCHCSmtLib2Interface.h> |
24 | | #include <libsolidity/formal/ModelChecker.h> |
25 | | #include <libsolidity/formal/PredicateInstance.h> |
26 | | #include <libsolidity/formal/PredicateSort.h> |
27 | | #include <libsolidity/formal/SymbolicTypes.h> |
28 | | #include <libsolidity/formal/Z3CHCSmtLib2Interface.h> |
29 | | |
30 | | #include <libsolidity/ast/TypeProvider.h> |
31 | | |
32 | | #include <libsmtutil/CHCSmtLib2Interface.h> |
33 | | #include <liblangutil/CharStreamProvider.h> |
34 | | #include <libsolutil/Algorithms.h> |
35 | | #include <libsolutil/StringUtils.h> |
36 | | |
37 | | #include <range/v3/algorithm/for_each.hpp> |
38 | | #include <range/v3/algorithm/none_of.hpp> |
39 | | #include <range/v3/view.hpp> |
40 | | #include <range/v3/view/enumerate.hpp> |
41 | | #include <range/v3/view/reverse.hpp> |
42 | | |
43 | | #include <charconv> |
44 | | #include <queue> |
45 | | |
46 | | using namespace solidity; |
47 | | using namespace solidity::util; |
48 | | using namespace solidity::langutil; |
49 | | using namespace solidity::smtutil; |
50 | | using namespace solidity::frontend; |
51 | | using namespace solidity::frontend::smt; |
52 | | |
53 | | CHC::CHC( |
54 | | EncodingContext& _context, |
55 | | UniqueErrorReporter& _errorReporter, |
56 | | UniqueErrorReporter& _unsupportedErrorReporter, |
57 | | ErrorReporter& _provedSafeReporter, |
58 | | std::map<util::h256, std::string> const& _smtlib2Responses, |
59 | | ReadCallback::Callback const& _smtCallback, |
60 | | ModelCheckerSettings _settings, |
61 | | CharStreamProvider const& _charStreamProvider |
62 | | ): |
63 | 14.6k | SMTEncoder(_context, _settings, _errorReporter, _unsupportedErrorReporter, _provedSafeReporter, _charStreamProvider), |
64 | 14.6k | m_smtlib2Responses(_smtlib2Responses), |
65 | 14.6k | m_smtCallback(_smtCallback) |
66 | 14.6k | {} |
67 | | |
68 | | void CHC::analyze(SourceUnit const& _source) |
69 | 15.5k | { |
70 | | // At this point every enabled solver is available. |
71 | 15.5k | if (!m_settings.solvers.eld && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3) |
72 | 0 | { |
73 | 0 | m_errorReporter.warning( |
74 | 0 | 7649_error, |
75 | 0 | SourceLocation(), |
76 | 0 | "CHC analysis was not possible since no Horn solver was found and enabled." |
77 | 0 | " The accepted solvers for CHC are Eldarica and z3." |
78 | 0 | ); |
79 | 0 | return; |
80 | 0 | } |
81 | | |
82 | 15.5k | if (m_settings.solvers.eld && m_settings.solvers.z3) |
83 | 0 | m_errorReporter.warning( |
84 | 0 | 5798_error, |
85 | 0 | SourceLocation(), |
86 | 0 | "Multiple Horn solvers were selected for CHC." |
87 | 0 | " CHC only supports one solver at a time, therefore only z3 will be used." |
88 | 0 | " If you wish to use Eldarica, please enable Eldarica only." |
89 | 0 | ); |
90 | | |
91 | 15.5k | if (!shouldAnalyzeVerificationTargetsFor(_source)) |
92 | 0 | return; |
93 | | |
94 | 15.5k | resetSourceAnalysis(); |
95 | | |
96 | 15.5k | auto sources = sourceDependencies(_source); |
97 | 15.5k | collectFreeFunctions(sources); |
98 | 15.5k | createFreeConstants(sources); |
99 | 15.5k | state().prepareForSourceUnit(_source, encodeExternalCallsAsTrusted()); |
100 | | |
101 | 15.5k | for (auto const* source: sources) |
102 | 16.1k | defineInterfacesAndSummaries(*source); |
103 | 15.5k | for (auto const* source: sources) |
104 | 16.1k | source->accept(*this); |
105 | | |
106 | 15.5k | checkVerificationTargets(); |
107 | | |
108 | 15.5k | bool ranSolver = true; |
109 | | // If ranSolver is true here it's because an SMT solver callback was |
110 | | // actually given and the queries were solved, |
111 | | // or Eldarica was chosen and was present in the system. |
112 | 15.5k | if (auto const* smtLibInterface = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get())) |
113 | 15.5k | ranSolver = smtLibInterface->unhandledQueries().empty(); |
114 | 15.5k | if (!ranSolver) |
115 | 3.74k | m_errorReporter.warning( |
116 | 3.74k | 3996_error, |
117 | 3.74k | SourceLocation(), |
118 | 3.74k | "CHC analysis was not possible. No Horn solver was available." |
119 | 3.74k | " None of the installed solvers was enabled." |
120 | 3.74k | ); |
121 | 15.5k | } |
122 | | |
123 | | std::vector<std::string> CHC::unhandledQueries() const |
124 | 14.6k | { |
125 | 14.6k | if (auto smtlib2 = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get())) |
126 | 13.9k | return smtlib2->unhandledQueries(); |
127 | | |
128 | 661 | return {}; |
129 | 14.6k | } |
130 | | |
131 | | bool CHC::visit(ContractDefinition const& _contract) |
132 | 16.8k | { |
133 | 16.8k | if (!shouldEncode(_contract)) |
134 | 534 | return false; |
135 | | |
136 | | // Raises UnimplementedFeatureError in the presence of transient storage variables |
137 | 16.2k | TransientDataLocationChecker checker(_contract); |
138 | | |
139 | 16.2k | resetContractAnalysis(); |
140 | 16.2k | initContract(_contract); |
141 | 16.2k | clearIndices(&_contract); |
142 | | |
143 | 16.2k | m_scopes.push_back(&_contract); |
144 | | |
145 | 16.2k | m_stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract); |
146 | 16.2k | solAssert(m_currentContract, ""); |
147 | | |
148 | 16.2k | SMTEncoder::visit(_contract); |
149 | 16.2k | return false; |
150 | 16.8k | } |
151 | | |
152 | | void CHC::endVisit(ContractDefinition const& _contract) |
153 | 16.8k | { |
154 | 16.8k | if (!shouldEncode(_contract)) |
155 | 534 | return; |
156 | | |
157 | 16.2k | for (auto base: _contract.annotation().linearizedBaseContracts) |
158 | 18.0k | { |
159 | 18.0k | if (auto constructor = base->constructor()) |
160 | 2.29k | constructor->accept(*this); |
161 | 18.0k | defineContractInitializer(*base, _contract); |
162 | 18.0k | } |
163 | | |
164 | 16.2k | auto const& entry = *createConstructorBlock(_contract, "implicit_constructor_entry"); |
165 | | |
166 | | // In case constructors use uninitialized state variables, |
167 | | // they need to be zeroed. |
168 | | // This is not part of `initialConstraints` because it's only true here, |
169 | | // at the beginning of the deployment routine. |
170 | 16.2k | smtutil::Expression zeroes(true); |
171 | 16.2k | for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract)) |
172 | 9.05k | zeroes = zeroes && currentValue(*var) == smt::zeroValue(var->type()); |
173 | | |
174 | 16.2k | smtutil::Expression newAddress = encodeExternalCallsAsTrusted() ? |
175 | 0 | !state().addressActive(state().thisAddress()) : |
176 | 16.2k | smtutil::Expression(true); |
177 | | |
178 | | // The contract's address might already have funds before deployment, |
179 | | // so the balance must be at least `msg.value`, but not equals. |
180 | 16.2k | auto initialBalanceConstraint = state().balance(state().thisAddress()) >= state().txMember("msg.value"); |
181 | 16.2k | addRule(smtutil::Expression::implies( |
182 | 16.2k | initialConstraints(_contract) && zeroes && newAddress && initialBalanceConstraint, |
183 | 16.2k | predicate(entry) |
184 | 16.2k | ), entry.functor().name); |
185 | | |
186 | 16.2k | setCurrentBlock(entry); |
187 | | |
188 | 16.2k | if (encodeExternalCallsAsTrusted()) |
189 | 0 | { |
190 | 0 | auto const& entryAfterAddress = *createConstructorBlock(_contract, "implicit_constructor_entry_after_address"); |
191 | 0 | state().setAddressActive(state().thisAddress(), true); |
192 | |
|
193 | 0 | connectBlocks(m_currentBlock, predicate(entryAfterAddress)); |
194 | 0 | setCurrentBlock(entryAfterAddress); |
195 | 0 | } |
196 | | |
197 | 16.2k | solAssert(!m_errorDest, ""); |
198 | 16.2k | m_errorDest = m_constructorSummaries.at(&_contract); |
199 | | // We need to evaluate the base constructor calls (arguments) from derived -> base |
200 | 16.2k | auto baseArgs = baseArguments(_contract); |
201 | 16.2k | for (auto base: _contract.annotation().linearizedBaseContracts) |
202 | 18.0k | if (base != &_contract) |
203 | 1.76k | { |
204 | 1.76k | m_callGraph[&_contract].insert(base); |
205 | | |
206 | 1.76k | auto baseConstructor = base->constructor(); |
207 | 1.76k | if (baseConstructor && baseArgs.count(base)) |
208 | 332 | { |
209 | 332 | std::vector<ASTPointer<Expression>> const& args = baseArgs.at(base); |
210 | 332 | auto const& params = baseConstructor->parameters(); |
211 | 332 | solAssert(params.size() == args.size(), ""); |
212 | 689 | for (unsigned i = 0; i < params.size(); ++i) |
213 | 357 | { |
214 | 357 | args.at(i)->accept(*this); |
215 | 357 | if (params.at(i)) |
216 | 357 | { |
217 | 357 | solAssert(m_context.knownVariable(*params.at(i)), ""); |
218 | 357 | m_context.addAssertion(currentValue(*params.at(i)) == expr(*args.at(i), params.at(i)->type())); |
219 | 357 | } |
220 | 357 | } |
221 | 332 | } |
222 | 1.76k | } |
223 | 16.2k | m_errorDest = nullptr; |
224 | | // Then call initializer_Base from base -> derived |
225 | 16.2k | for (auto base: _contract.annotation().linearizedBaseContracts | ranges::views::reverse) |
226 | 18.0k | { |
227 | 18.0k | errorFlag().increaseIndex(); |
228 | 18.0k | m_context.addAssertion(smt::constructorCall(*m_contractInitializers.at(&_contract).at(base), m_context)); |
229 | 18.0k | connectBlocks(m_currentBlock, summary(_contract), errorFlag().currentValue() > 0); |
230 | 18.0k | m_context.addAssertion(errorFlag().currentValue() == 0); |
231 | 18.0k | } |
232 | | |
233 | 16.2k | if (encodeExternalCallsAsTrusted()) |
234 | 0 | state().writeStateVars(_contract, state().thisAddress()); |
235 | | |
236 | 16.2k | connectBlocks(m_currentBlock, summary(_contract)); |
237 | | |
238 | 16.2k | setCurrentBlock(*m_constructorSummaries.at(&_contract)); |
239 | | |
240 | 16.2k | solAssert(&_contract == m_currentContract, ""); |
241 | 16.2k | smtAssert(shouldEncode(_contract)); |
242 | 16.2k | auto constructor = _contract.constructor(); |
243 | 16.2k | auto txConstraints = state().txTypeConstraints(); |
244 | 16.2k | if (!constructor || !constructor->isPayable()) |
245 | 16.1k | txConstraints = txConstraints && state().txNonPayableConstraint(); |
246 | 16.2k | connectBlocks(m_currentBlock, interface(), txConstraints && errorFlag().currentValue() == 0); |
247 | 16.2k | if (shouldAnalyzeVerificationTargetsFor(_contract)) |
248 | 16.2k | m_queryPlaceholders[&_contract].push_back({txConstraints, errorFlag().currentValue(), m_currentBlock}); |
249 | | |
250 | 16.2k | solAssert(m_scopes.back() == &_contract, ""); |
251 | 16.2k | m_scopes.pop_back(); |
252 | | |
253 | 16.2k | SMTEncoder::endVisit(_contract); |
254 | 16.2k | } |
255 | | |
256 | | bool CHC::visit(FunctionDefinition const& _function) |
257 | 18.6k | { |
258 | | // Free functions need to be visited in the context of a contract. |
259 | 18.6k | if (!m_currentContract) |
260 | 1.20k | return false; |
261 | | |
262 | 17.4k | if ( |
263 | 17.4k | !_function.isImplemented() || |
264 | 17.2k | abstractAsNondet(_function) |
265 | 17.4k | ) |
266 | 190 | { |
267 | 190 | smtutil::Expression conj(true); |
268 | 190 | if ( |
269 | 190 | _function.stateMutability() == StateMutability::Pure || |
270 | 158 | _function.stateMutability() == StateMutability::View |
271 | 190 | ) |
272 | 84 | conj = conj && currentEqualInitialVarsConstraints(stateVariablesIncludingInheritedAndPrivate(_function)); |
273 | | |
274 | 190 | conj = conj && errorFlag().currentValue() == 0; |
275 | 190 | addRule(smtutil::Expression::implies(conj, summary(_function)), "summary_function_" + std::to_string(_function.id())); |
276 | 190 | return false; |
277 | 190 | } |
278 | | |
279 | | // No inlining. |
280 | 17.2k | solAssert(!m_currentFunction, "Function inlining should not happen in CHC."); |
281 | 17.2k | m_currentFunction = &_function; |
282 | | |
283 | 17.2k | m_scopes.push_back(&_function); |
284 | | |
285 | 17.2k | initFunction(_function); |
286 | | |
287 | 17.2k | auto functionEntryBlock = createBlock(m_currentFunction, PredicateType::FunctionBlock); |
288 | 17.2k | auto bodyBlock = createBlock(&m_currentFunction->body(), PredicateType::FunctionBlock); |
289 | | |
290 | 17.2k | auto functionPred = predicate(*functionEntryBlock); |
291 | 17.2k | auto bodyPred = predicate(*bodyBlock); |
292 | | |
293 | 17.2k | addRule(functionPred, functionPred.name); |
294 | | |
295 | 17.2k | solAssert(m_currentContract, ""); |
296 | 17.2k | m_context.addAssertion(initialConstraints(*m_currentContract, &_function)); |
297 | | |
298 | 17.2k | connectBlocks(functionPred, bodyPred); |
299 | | |
300 | 17.2k | setCurrentBlock(*bodyBlock); |
301 | | |
302 | 17.2k | solAssert(!m_errorDest, ""); |
303 | 17.2k | m_errorDest = m_summaries.at(m_currentContract).at(&_function); |
304 | 17.2k | SMTEncoder::visit(*m_currentFunction); |
305 | 17.2k | m_errorDest = nullptr; |
306 | | |
307 | 17.2k | return false; |
308 | 17.4k | } |
309 | | |
310 | | void CHC::endVisit(FunctionDefinition const& _function) |
311 | 18.6k | { |
312 | | // Free functions need to be visited in the context of a contract. |
313 | 18.6k | if (!m_currentContract) |
314 | 1.20k | return; |
315 | | |
316 | 17.4k | if ( |
317 | 17.4k | !_function.isImplemented() || |
318 | 17.2k | abstractAsNondet(_function) |
319 | 17.4k | ) |
320 | 190 | return; |
321 | | |
322 | 17.2k | solAssert(m_currentFunction && m_currentContract, ""); |
323 | | // No inlining. |
324 | 17.2k | solAssert(m_currentFunction == &_function, ""); |
325 | | |
326 | 17.2k | solAssert(m_scopes.back() == &_function, ""); |
327 | 17.2k | m_scopes.pop_back(); |
328 | | |
329 | 17.2k | connectBlocks(m_currentBlock, summary(_function)); |
330 | 17.2k | setCurrentBlock(*m_summaries.at(m_currentContract).at(&_function)); |
331 | | |
332 | | // Query placeholders for constructors are not created here because |
333 | | // of contracts without constructors. |
334 | | // Instead, those are created in endVisit(ContractDefinition). |
335 | 17.2k | if ( |
336 | 17.2k | !_function.isConstructor() && |
337 | 14.9k | _function.isPublic() && |
338 | 13.1k | contractFunctions(*m_currentContract).count(&_function) && |
339 | 12.7k | shouldEncode(*m_currentContract) |
340 | 17.2k | ) |
341 | 12.7k | { |
342 | 12.7k | defineExternalFunctionInterface(_function, *m_currentContract); |
343 | 12.7k | setCurrentBlock(*m_interfaces.at(m_currentContract)); |
344 | | |
345 | | // Create the rule |
346 | | // interface \land externalFunctionEntry => interface' |
347 | 12.7k | auto ifacePre = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context); |
348 | 12.7k | auto sum = externalSummary(_function); |
349 | | |
350 | 12.7k | connectBlocks(ifacePre, interface(), sum && errorFlag().currentValue() == 0); |
351 | 12.7k | if (shouldAnalyzeVerificationTargetsFor(*m_currentContract)) |
352 | 12.7k | m_queryPlaceholders[&_function].push_back({sum, errorFlag().currentValue(), ifacePre}); |
353 | 12.7k | } |
354 | | |
355 | 17.2k | m_currentFunction = nullptr; |
356 | | |
357 | 17.2k | SMTEncoder::endVisit(_function); |
358 | 17.2k | } |
359 | | |
360 | | bool CHC::visit(Block const& _block) |
361 | 19.0k | { |
362 | 19.0k | m_scopes.push_back(&_block); |
363 | 19.0k | return SMTEncoder::visit(_block); |
364 | 19.0k | } |
365 | | |
366 | | void CHC::endVisit(Block const& _block) |
367 | 19.0k | { |
368 | 19.0k | solAssert(m_scopes.back() == &_block, ""); |
369 | 19.0k | m_scopes.pop_back(); |
370 | 19.0k | SMTEncoder::endVisit(_block); |
371 | 19.0k | } |
372 | | |
373 | | bool CHC::visit(IfStatement const& _if) |
374 | 726 | { |
375 | 726 | solAssert(m_currentFunction, ""); |
376 | | |
377 | 726 | bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; |
378 | 726 | m_unknownFunctionCallSeen = false; |
379 | | |
380 | 726 | solAssert(m_currentFunction, ""); |
381 | 726 | auto const& functionBody = m_currentFunction->body(); |
382 | | |
383 | 726 | auto ifHeaderBlock = createBlock(&_if, PredicateType::FunctionBlock, "if_header_"); |
384 | 726 | auto trueBlock = createBlock(&_if.trueStatement(), PredicateType::FunctionBlock, "if_true_"); |
385 | 726 | auto falseBlock = _if.falseStatement() ? createBlock(_if.falseStatement(), PredicateType::FunctionBlock, "if_false_") : nullptr; |
386 | 726 | auto afterIfBlock = createBlock(&functionBody, PredicateType::FunctionBlock); |
387 | | |
388 | 726 | connectBlocks(m_currentBlock, predicate(*ifHeaderBlock)); |
389 | | |
390 | 726 | setCurrentBlock(*ifHeaderBlock); |
391 | 726 | _if.condition().accept(*this); |
392 | 726 | auto condition = expr(_if.condition()); |
393 | | |
394 | 726 | connectBlocks(m_currentBlock, predicate(*trueBlock), condition); |
395 | 726 | if (_if.falseStatement()) |
396 | 98 | connectBlocks(m_currentBlock, predicate(*falseBlock), !condition); |
397 | 628 | else |
398 | 628 | connectBlocks(m_currentBlock, predicate(*afterIfBlock), !condition); |
399 | | |
400 | 726 | setCurrentBlock(*trueBlock); |
401 | 726 | _if.trueStatement().accept(*this); |
402 | 726 | connectBlocks(m_currentBlock, predicate(*afterIfBlock)); |
403 | | |
404 | 726 | if (_if.falseStatement()) |
405 | 98 | { |
406 | 98 | setCurrentBlock(*falseBlock); |
407 | 98 | _if.falseStatement()->accept(*this); |
408 | 98 | connectBlocks(m_currentBlock, predicate(*afterIfBlock)); |
409 | 98 | } |
410 | | |
411 | 726 | setCurrentBlock(*afterIfBlock); |
412 | | |
413 | 726 | if (m_unknownFunctionCallSeen) |
414 | 5 | eraseKnowledge(); |
415 | | |
416 | 726 | m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; |
417 | | |
418 | 726 | return false; |
419 | 726 | } |
420 | | |
421 | | bool CHC::visit(WhileStatement const& _while) |
422 | 196 | { |
423 | 196 | bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; |
424 | 196 | m_unknownFunctionCallSeen = false; |
425 | | |
426 | 196 | solAssert(m_currentFunction, ""); |
427 | 196 | auto const& functionBody = m_currentFunction->body(); |
428 | | |
429 | 196 | auto namePrefix = std::string(_while.isDoWhile() ? "do_" : "") + "while"; |
430 | 196 | auto loopHeaderBlock = createBlock(&_while, PredicateType::FunctionBlock, namePrefix + "_header_"); |
431 | 196 | auto loopBodyBlock = createBlock(&_while.body(), PredicateType::FunctionBlock, namePrefix + "_body_"); |
432 | 196 | auto afterLoopBlock = createBlock(&functionBody, PredicateType::FunctionBlock); |
433 | | |
434 | 196 | auto outerBreakDest = m_breakDest; |
435 | 196 | auto outerContinueDest = m_continueDest; |
436 | 196 | m_breakDest = afterLoopBlock; |
437 | 196 | m_continueDest = loopHeaderBlock; |
438 | | |
439 | 196 | if (_while.isDoWhile()) |
440 | 34 | _while.body().accept(*this); |
441 | | |
442 | 196 | connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); |
443 | | |
444 | 196 | setCurrentBlock(*loopHeaderBlock); |
445 | | |
446 | 196 | _while.condition().accept(*this); |
447 | 196 | auto condition = expr(_while.condition()); |
448 | | |
449 | 196 | connectBlocks(m_currentBlock, predicate(*loopBodyBlock), condition); |
450 | 196 | connectBlocks(m_currentBlock, predicate(*afterLoopBlock), !condition); |
451 | | |
452 | | // Loop body visit. |
453 | 196 | setCurrentBlock(*loopBodyBlock); |
454 | 196 | _while.body().accept(*this); |
455 | | |
456 | 196 | m_breakDest = outerBreakDest; |
457 | 196 | m_continueDest = outerContinueDest; |
458 | | |
459 | | // Back edge. |
460 | 196 | connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); |
461 | 196 | setCurrentBlock(*afterLoopBlock); |
462 | | |
463 | 196 | if (m_unknownFunctionCallSeen) |
464 | 0 | eraseKnowledge(); |
465 | | |
466 | 196 | m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; |
467 | | |
468 | 196 | return false; |
469 | 196 | } |
470 | | |
471 | | bool CHC::visit(ForStatement const& _for) |
472 | 695 | { |
473 | 695 | m_scopes.push_back(&_for); |
474 | | |
475 | 695 | bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; |
476 | 695 | m_unknownFunctionCallSeen = false; |
477 | | |
478 | 695 | solAssert(m_currentFunction, ""); |
479 | 695 | auto const& functionBody = m_currentFunction->body(); |
480 | | |
481 | 695 | auto loopHeaderBlock = createBlock(&_for, PredicateType::FunctionBlock, "for_header_"); |
482 | 695 | auto loopBodyBlock = createBlock(&_for.body(), PredicateType::FunctionBlock, "for_body_"); |
483 | 695 | auto afterLoopBlock = createBlock(&functionBody, PredicateType::FunctionBlock); |
484 | 695 | auto postLoop = _for.loopExpression(); |
485 | 695 | auto postLoopBlock = postLoop ? createBlock(postLoop, PredicateType::FunctionBlock, "for_post_") : nullptr; |
486 | | |
487 | 695 | auto outerBreakDest = m_breakDest; |
488 | 695 | auto outerContinueDest = m_continueDest; |
489 | 695 | m_breakDest = afterLoopBlock; |
490 | 695 | m_continueDest = postLoop ? postLoopBlock : loopHeaderBlock; |
491 | | |
492 | 695 | if (auto init = _for.initializationExpression()) |
493 | 628 | init->accept(*this); |
494 | | |
495 | 695 | connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); |
496 | 695 | setCurrentBlock(*loopHeaderBlock); |
497 | | |
498 | 695 | auto condition = smtutil::Expression(true); |
499 | 695 | if (auto forCondition = _for.condition()) |
500 | 657 | { |
501 | 657 | forCondition->accept(*this); |
502 | 657 | condition = expr(*forCondition); |
503 | 657 | } |
504 | | |
505 | 695 | connectBlocks(m_currentBlock, predicate(*loopBodyBlock), condition); |
506 | 695 | connectBlocks(m_currentBlock, predicate(*afterLoopBlock), !condition); |
507 | | |
508 | | // Loop body visit. |
509 | 695 | setCurrentBlock(*loopBodyBlock); |
510 | 695 | _for.body().accept(*this); |
511 | | |
512 | 695 | if (postLoop) |
513 | 629 | { |
514 | 629 | connectBlocks(m_currentBlock, predicate(*postLoopBlock)); |
515 | 629 | setCurrentBlock(*postLoopBlock); |
516 | 629 | postLoop->accept(*this); |
517 | 629 | } |
518 | | |
519 | 695 | m_breakDest = outerBreakDest; |
520 | 695 | m_continueDest = outerContinueDest; |
521 | | |
522 | | // Back edge. |
523 | 695 | connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); |
524 | 695 | setCurrentBlock(*afterLoopBlock); |
525 | | |
526 | 695 | if (m_unknownFunctionCallSeen) |
527 | 0 | eraseKnowledge(); |
528 | | |
529 | 695 | m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; |
530 | | |
531 | 695 | return false; |
532 | 695 | } |
533 | | |
534 | | void CHC::endVisit(ForStatement const& _for) |
535 | 695 | { |
536 | 695 | solAssert(m_scopes.back() == &_for, ""); |
537 | 695 | m_scopes.pop_back(); |
538 | 695 | } |
539 | | |
540 | | void CHC::endVisit(UnaryOperation const& _op) |
541 | 4.18k | { |
542 | 4.18k | SMTEncoder::endVisit(_op); |
543 | | |
544 | 4.18k | if (auto funDef = *_op.annotation().userDefinedFunction) |
545 | 12 | { |
546 | 12 | std::vector<Expression const*> arguments; |
547 | 12 | arguments.push_back(&_op.subExpression()); |
548 | 12 | internalFunctionCall(funDef, std::nullopt, _op.userDefinedFunctionType(), arguments, state().thisAddress()); |
549 | | |
550 | 12 | createReturnedExpressions(funDef, _op); |
551 | 12 | return; |
552 | 12 | } |
553 | | |
554 | 4.17k | if ( |
555 | 4.17k | _op.annotation().type->category() == Type::Category::RationalNumber || |
556 | 3.16k | _op.annotation().type->category() == Type::Category::FixedPoint |
557 | 4.17k | ) |
558 | 1.07k | return; |
559 | | |
560 | 3.10k | if (_op.getOperator() == Token::Sub && smt::isInteger(*_op.annotation().type)) |
561 | 131 | { |
562 | 131 | auto const* intType = dynamic_cast<IntegerType const*>(_op.annotation().type); |
563 | 131 | if (!intType) |
564 | 0 | intType = TypeProvider::uint256(); |
565 | | |
566 | 131 | verificationTargetEncountered(&_op, VerificationTargetType::Underflow, expr(_op) < intType->minValue()); |
567 | 131 | verificationTargetEncountered(&_op, VerificationTargetType::Overflow, expr(_op) > intType->maxValue()); |
568 | 131 | } |
569 | 3.10k | } |
570 | | |
571 | | void CHC::endVisit(BinaryOperation const& _op) |
572 | 18.2k | { |
573 | 18.2k | SMTEncoder::endVisit(_op); |
574 | | |
575 | 18.2k | if (auto funDef = *_op.annotation().userDefinedFunction) |
576 | 30 | { |
577 | 30 | std::vector<Expression const*> arguments; |
578 | 30 | arguments.push_back(&_op.leftExpression()); |
579 | 30 | arguments.push_back(&_op.rightExpression()); |
580 | 30 | internalFunctionCall(funDef, std::nullopt, _op.userDefinedFunctionType(), arguments, state().thisAddress()); |
581 | | |
582 | 30 | createReturnedExpressions(funDef, _op); |
583 | 30 | } |
584 | 18.2k | } |
585 | | |
586 | | void CHC::endVisit(FunctionCall const& _funCall) |
587 | 14.7k | { |
588 | 14.7k | auto functionCallKind = *_funCall.annotation().kind; |
589 | | |
590 | 14.7k | if (functionCallKind != FunctionCallKind::FunctionCall) |
591 | 3.02k | { |
592 | 3.02k | SMTEncoder::endVisit(_funCall); |
593 | 3.02k | return; |
594 | 3.02k | } |
595 | | |
596 | 11.6k | FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); |
597 | 11.6k | switch (funType.kind()) |
598 | 11.6k | { |
599 | 2.47k | case FunctionType::Kind::Assert: |
600 | 2.47k | visitAssert(_funCall); |
601 | 2.47k | SMTEncoder::endVisit(_funCall); |
602 | 2.47k | break; |
603 | 1.80k | case FunctionType::Kind::Internal: |
604 | 1.80k | internalFunctionCall(_funCall); |
605 | 1.80k | break; |
606 | 938 | case FunctionType::Kind::External: |
607 | 949 | case FunctionType::Kind::BareStaticCall: |
608 | 1.25k | case FunctionType::Kind::BareCall: |
609 | 1.25k | externalFunctionCall(_funCall); |
610 | 1.25k | SMTEncoder::endVisit(_funCall); |
611 | 1.25k | break; |
612 | 329 | case FunctionType::Kind::Creation: |
613 | 329 | visitDeployment(_funCall); |
614 | 329 | break; |
615 | 180 | case FunctionType::Kind::DelegateCall: |
616 | 180 | case FunctionType::Kind::BareCallCode: |
617 | 192 | case FunctionType::Kind::BareDelegateCall: |
618 | 192 | SMTEncoder::endVisit(_funCall); |
619 | 192 | unknownFunctionCall(_funCall); |
620 | 192 | break; |
621 | 15 | case FunctionType::Kind::Send: |
622 | 84 | case FunctionType::Kind::Transfer: |
623 | 84 | { |
624 | 84 | auto value = _funCall.arguments().front(); |
625 | 84 | solAssert(value, ""); |
626 | 84 | smtutil::Expression thisBalance = state().balance(); |
627 | | |
628 | 84 | verificationTargetEncountered( |
629 | 84 | &_funCall, |
630 | 84 | VerificationTargetType::Balance, |
631 | 84 | thisBalance < expr(*value) |
632 | 84 | ); |
633 | | |
634 | 84 | SMTEncoder::endVisit(_funCall); |
635 | 84 | break; |
636 | 15 | } |
637 | 137 | case FunctionType::Kind::KECCAK256: |
638 | 155 | case FunctionType::Kind::ECRecover: |
639 | 168 | case FunctionType::Kind::SHA256: |
640 | 258 | case FunctionType::Kind::RIPEMD160: |
641 | 259 | case FunctionType::Kind::BlobHash: |
642 | 284 | case FunctionType::Kind::BlockHash: |
643 | 325 | case FunctionType::Kind::AddMod: |
644 | 339 | case FunctionType::Kind::MulMod: |
645 | 429 | case FunctionType::Kind::Unwrap: |
646 | 592 | case FunctionType::Kind::Wrap: |
647 | 592 | [[fallthrough]]; |
648 | 5.55k | default: |
649 | 5.55k | SMTEncoder::endVisit(_funCall); |
650 | 5.55k | break; |
651 | 11.6k | } |
652 | | |
653 | 11.6k | auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
654 | 11.6k | createReturnedExpressions(funDef, _funCall); |
655 | 11.6k | } |
656 | | |
657 | | void CHC::endVisit(Break const& _break) |
658 | 99 | { |
659 | 99 | solAssert(m_breakDest, ""); |
660 | 99 | connectBlocks(m_currentBlock, predicate(*m_breakDest)); |
661 | | |
662 | | // Add an unreachable ghost node to collect unreachable statements after a break. |
663 | 99 | auto breakGhost = createBlock(&_break, PredicateType::FunctionBlock, "break_ghost_"); |
664 | 99 | m_currentBlock = predicate(*breakGhost); |
665 | 99 | } |
666 | | |
667 | | void CHC::endVisit(Continue const& _continue) |
668 | 85 | { |
669 | 85 | solAssert(m_continueDest, ""); |
670 | 85 | connectBlocks(m_currentBlock, predicate(*m_continueDest)); |
671 | | |
672 | | // Add an unreachable ghost node to collect unreachable statements after a continue. |
673 | 85 | auto continueGhost = createBlock(&_continue, PredicateType::FunctionBlock, "continue_ghost_"); |
674 | 85 | m_currentBlock = predicate(*continueGhost); |
675 | 85 | } |
676 | | |
677 | | void CHC::endVisit(IndexRangeAccess const& _range) |
678 | 579 | { |
679 | 579 | createExpr(_range); |
680 | | |
681 | 579 | auto baseArray = std::dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(_range.baseExpression())); |
682 | 579 | auto sliceArray = std::dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(_range)); |
683 | 579 | solAssert(baseArray && sliceArray, ""); |
684 | | |
685 | 579 | auto const& sliceData = ArraySlicePredicate::create(sliceArray->sort(), m_context); |
686 | 579 | if (!sliceData.first) |
687 | 121 | { |
688 | 121 | for (auto pred: sliceData.second.predicates) |
689 | 363 | m_interface->registerRelation(pred->functor()); |
690 | 121 | for (auto const& rule: sliceData.second.rules) |
691 | 484 | addRule(rule, ""); |
692 | 121 | } |
693 | | |
694 | 579 | auto start = _range.startExpression() ? expr(*_range.startExpression()) : 0; |
695 | 579 | auto end = _range.endExpression() ? expr(*_range.endExpression()) : baseArray->length(); |
696 | 579 | auto slicePred = (*sliceData.second.predicates.at(0))({ |
697 | 579 | baseArray->elements(), |
698 | 579 | sliceArray->elements(), |
699 | 579 | start, |
700 | 579 | end |
701 | 579 | }); |
702 | | |
703 | 579 | m_context.addAssertion(slicePred); |
704 | 579 | m_context.addAssertion(sliceArray->length() == end - start); |
705 | 579 | } |
706 | | |
707 | | void CHC::endVisit(Return const& _return) |
708 | 5.72k | { |
709 | 5.72k | SMTEncoder::endVisit(_return); |
710 | | |
711 | 5.72k | connectBlocks(m_currentBlock, predicate(*m_returnDests.back())); |
712 | | |
713 | | // Add an unreachable ghost node to collect unreachable statements after a return. |
714 | 5.72k | auto returnGhost = createBlock(&_return, PredicateType::FunctionBlock, "return_ghost_"); |
715 | 5.72k | m_currentBlock = predicate(*returnGhost); |
716 | 5.72k | } |
717 | | |
718 | | bool CHC::visit(TryCatchClause const& _tryStatement) |
719 | 298 | { |
720 | 298 | m_scopes.push_back(&_tryStatement); |
721 | 298 | return SMTEncoder::visit(_tryStatement); |
722 | 298 | } |
723 | | |
724 | | void CHC::endVisit(TryCatchClause const& _tryStatement) |
725 | 298 | { |
726 | 298 | solAssert(m_scopes.back() == &_tryStatement, ""); |
727 | 298 | m_scopes.pop_back(); |
728 | 298 | } |
729 | | |
730 | | bool CHC::visit(TryStatement const& _tryStatement) |
731 | 144 | { |
732 | 144 | FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall()); |
733 | 144 | solAssert(externalCall && externalCall->annotation().tryCall, ""); |
734 | 144 | solAssert(m_currentFunction, ""); |
735 | | |
736 | 144 | auto tryHeaderBlock = createBlock(&_tryStatement, PredicateType::FunctionBlock, "try_header_"); |
737 | 144 | auto afterTryBlock = createBlock(&m_currentFunction->body(), PredicateType::FunctionBlock); |
738 | | |
739 | 144 | auto const& clauses = _tryStatement.clauses(); |
740 | 144 | solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause"); |
741 | 298 | auto clauseBlocks = applyMap(clauses, [this](ASTPointer<TryCatchClause> clause) { |
742 | 298 | return createBlock(clause.get(), PredicateType::FunctionBlock, "try_clause_" + std::to_string(clause->id())); |
743 | 298 | }); |
744 | | |
745 | 144 | connectBlocks(m_currentBlock, predicate(*tryHeaderBlock)); |
746 | 144 | setCurrentBlock(*tryHeaderBlock); |
747 | | // Visit everything, except the actual external call. |
748 | 144 | externalCall->expression().accept(*this); |
749 | 144 | ASTNode::listAccept(externalCall->arguments(), *this); |
750 | | // Branch directly to all catch clauses, since in these cases, any effects of the external call are reverted. |
751 | 298 | for (size_t i = 1; i < clauseBlocks.size(); ++i) |
752 | 154 | connectBlocks(m_currentBlock, predicate(*clauseBlocks[i])); |
753 | | // Only now visit the actual call to record its effects and connect to the success clause. |
754 | 144 | endVisit(*externalCall); |
755 | 144 | if (_tryStatement.successClause()->parameters()) |
756 | 36 | expressionToTupleAssignment(_tryStatement.successClause()->parameters()->parameters(), *externalCall); |
757 | | |
758 | 144 | connectBlocks(m_currentBlock, predicate(*clauseBlocks[0])); |
759 | | |
760 | 442 | for (size_t i = 0; i < clauses.size(); ++i) |
761 | 298 | { |
762 | 298 | setCurrentBlock(*clauseBlocks[i]); |
763 | 298 | clauses[i]->accept(*this); |
764 | 298 | connectBlocks(m_currentBlock, predicate(*afterTryBlock)); |
765 | 298 | } |
766 | 144 | setCurrentBlock(*afterTryBlock); |
767 | | |
768 | 144 | return false; |
769 | 144 | } |
770 | | |
771 | | void CHC::pushInlineFrame(CallableDeclaration const& _callable) |
772 | 17.8k | { |
773 | 17.8k | m_returnDests.push_back(createBlock(&_callable, PredicateType::FunctionBlock, "return_")); |
774 | 17.8k | } |
775 | | |
776 | | void CHC::popInlineFrame(CallableDeclaration const& _callable) |
777 | 17.8k | { |
778 | 17.8k | solAssert(!m_returnDests.empty(), ""); |
779 | 17.8k | auto const& ret = *m_returnDests.back(); |
780 | 17.8k | solAssert(ret.programNode() == &_callable, ""); |
781 | 17.8k | connectBlocks(m_currentBlock, predicate(ret)); |
782 | 17.8k | setCurrentBlock(ret); |
783 | 17.8k | m_returnDests.pop_back(); |
784 | 17.8k | } |
785 | | |
786 | | void CHC::visitAssert(FunctionCall const& _funCall) |
787 | 2.47k | { |
788 | 2.47k | auto const& args = _funCall.arguments(); |
789 | 2.47k | solAssert(args.size() == 1, ""); |
790 | 2.47k | solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); |
791 | | |
792 | 2.47k | solAssert(m_currentContract, ""); |
793 | 2.47k | solAssert(m_currentFunction, ""); |
794 | 2.47k | auto errorCondition = !m_context.expression(*args.front())->currentValue(); |
795 | 2.47k | verificationTargetEncountered(&_funCall, VerificationTargetType::Assert, errorCondition); |
796 | 2.47k | } |
797 | | |
798 | | void CHC::visitPublicGetter(FunctionCall const& _funCall) |
799 | 312 | { |
800 | 312 | createExpr(_funCall); |
801 | 312 | if (encodeExternalCallsAsTrusted()) |
802 | 0 | { |
803 | 0 | auto const& access = dynamic_cast<MemberAccess const&>(_funCall.expression()); |
804 | 0 | auto const& contractType = dynamic_cast<ContractType const&>(*access.expression().annotation().type); |
805 | 0 | state().writeStateVars(*m_currentContract, state().thisAddress()); |
806 | 0 | state().readStateVars(contractType.contractDefinition(), expr(access.expression())); |
807 | 0 | } |
808 | 312 | SMTEncoder::visitPublicGetter(_funCall); |
809 | 312 | } |
810 | | |
811 | | void CHC::visitAddMulMod(FunctionCall const& _funCall) |
812 | 55 | { |
813 | 55 | solAssert(_funCall.arguments().at(2), ""); |
814 | | |
815 | 55 | verificationTargetEncountered(&_funCall, VerificationTargetType::DivByZero, expr(*_funCall.arguments().at(2)) == 0); |
816 | | |
817 | 55 | SMTEncoder::visitAddMulMod(_funCall); |
818 | 55 | } |
819 | | |
820 | | void CHC::visitDeployment(FunctionCall const& _funCall) |
821 | 329 | { |
822 | 329 | if (!encodeExternalCallsAsTrusted()) |
823 | 329 | { |
824 | 329 | SMTEncoder::endVisit(_funCall); |
825 | 329 | unknownFunctionCall(_funCall); |
826 | 329 | return; |
827 | 329 | } |
828 | | |
829 | 0 | auto [callExpr, callOptions] = functionCallExpression(_funCall); |
830 | 0 | auto funType = dynamic_cast<FunctionType const*>(callExpr->annotation().type); |
831 | 0 | ContractDefinition const* contract = |
832 | 0 | &dynamic_cast<ContractType const&>(*funType->returnParameterTypes().front()).contractDefinition(); |
833 | | |
834 | | // copy state variables from m_currentContract to state.storage. |
835 | 0 | state().writeStateVars(*m_currentContract, state().thisAddress()); |
836 | 0 | errorFlag().increaseIndex(); |
837 | |
|
838 | 0 | Expression const* value = valueOption(callOptions); |
839 | 0 | if (value) |
840 | 0 | decreaseBalanceFromOptionsValue(*value); |
841 | |
|
842 | 0 | auto originalTx = state().tx(); |
843 | 0 | newTxConstraints(value); |
844 | |
|
845 | 0 | auto prevThisAddr = state().thisAddress(); |
846 | 0 | auto newAddr = state().newThisAddress(); |
847 | |
|
848 | 0 | if (auto constructor = contract->constructor()) |
849 | 0 | { |
850 | 0 | auto const& args = _funCall.sortedArguments(); |
851 | 0 | auto const& params = constructor->parameters(); |
852 | 0 | solAssert(args.size() == params.size(), ""); |
853 | 0 | for (auto [arg, param]: ranges::zip_view(args, params)) |
854 | 0 | m_context.addAssertion(expr(*arg, param->type()) == m_context.variable(*param)->currentValue()); |
855 | 0 | } |
856 | 0 | for (auto var: stateVariablesIncludingInheritedAndPrivate(*contract)) |
857 | 0 | m_context.variable(*var)->increaseIndex(); |
858 | 0 | Predicate const& constructorSummary = *m_constructorSummaries.at(contract); |
859 | 0 | m_context.addAssertion(smt::constructorCall(constructorSummary, m_context, false)); |
860 | |
|
861 | 0 | solAssert(m_errorDest, ""); |
862 | 0 | connectBlocks( |
863 | 0 | m_currentBlock, |
864 | 0 | predicate(*m_errorDest), |
865 | 0 | errorFlag().currentValue() > 0 |
866 | 0 | ); |
867 | 0 | m_context.addAssertion(errorFlag().currentValue() == 0); |
868 | |
|
869 | 0 | m_context.addAssertion(state().newThisAddress() == prevThisAddr); |
870 | | |
871 | | // copy state variables from state.storage to m_currentContract. |
872 | 0 | state().readStateVars(*m_currentContract, state().thisAddress()); |
873 | |
|
874 | 0 | state().newTx(); |
875 | 0 | m_context.addAssertion(originalTx == state().tx()); |
876 | |
|
877 | 0 | defineExpr(_funCall, newAddr); |
878 | 0 | } |
879 | | |
880 | | void CHC::internalFunctionCall( |
881 | | FunctionDefinition const* _funDef, |
882 | | std::optional<Expression const*> _boundArgumentCall, |
883 | | FunctionType const* _funType, |
884 | | std::vector<Expression const*> const& _arguments, |
885 | | smtutil::Expression _contractAddressValue |
886 | | ) |
887 | 1.84k | { |
888 | 1.84k | solAssert(m_currentContract, ""); |
889 | 1.84k | solAssert(_funType, ""); |
890 | | |
891 | 1.84k | if (_funDef) |
892 | 1.73k | { |
893 | 1.73k | if (m_currentFunction && !m_currentFunction->isConstructor()) |
894 | 1.45k | m_callGraph[m_currentFunction].insert(_funDef); |
895 | 288 | else |
896 | 288 | m_callGraph[m_currentContract].insert(_funDef); |
897 | 1.73k | } |
898 | | |
899 | 1.84k | m_context.addAssertion(predicate(_funDef, _boundArgumentCall, _funType, _arguments, _contractAddressValue)); |
900 | | |
901 | 1.84k | solAssert(m_errorDest, ""); |
902 | 1.84k | connectBlocks( |
903 | 1.84k | m_currentBlock, |
904 | 1.84k | predicate(*m_errorDest), |
905 | 1.84k | errorFlag().currentValue() > 0 && currentPathConditions() |
906 | 1.84k | ); |
907 | 1.84k | m_context.addAssertion(smtutil::Expression::implies(currentPathConditions(), errorFlag().currentValue() == 0)); |
908 | 1.84k | m_context.addAssertion(errorFlag().increaseIndex() == 0); |
909 | 1.84k | } |
910 | | |
911 | | void CHC::internalFunctionCall(FunctionCall const& _funCall) |
912 | 1.80k | { |
913 | 1.80k | solAssert(m_currentContract, ""); |
914 | | |
915 | 1.80k | auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
916 | 1.80k | if (funDef) |
917 | 1.69k | { |
918 | 1.69k | if (m_currentFunction && !m_currentFunction->isConstructor()) |
919 | 1.40k | m_callGraph[m_currentFunction].insert(funDef); |
920 | 288 | else |
921 | 288 | m_callGraph[m_currentContract].insert(funDef); |
922 | 1.69k | } |
923 | | |
924 | 1.80k | Expression const* calledExpr = &_funCall.expression(); |
925 | 1.80k | auto funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type); |
926 | | |
927 | 1.80k | std::vector<Expression const*> arguments; |
928 | 1.80k | for (auto& arg: _funCall.sortedArguments()) |
929 | 786 | arguments.push_back(&(*arg)); |
930 | | |
931 | 1.80k | std::optional<Expression const*> boundArgumentCall = |
932 | 1.80k | funType->hasBoundFirstArgument() ? std::make_optional(calledExpr) : std::nullopt; |
933 | 1.80k | internalFunctionCall(funDef, boundArgumentCall, funType, arguments, contractAddressValue(_funCall)); |
934 | 1.80k | } |
935 | | |
936 | | void CHC::addNondetCalls(ContractDefinition const& _contract) |
937 | 0 | { |
938 | 0 | for (auto var: _contract.stateVariables()) |
939 | 0 | if (auto contractType = dynamic_cast<ContractType const*>(var->type())) |
940 | 0 | { |
941 | 0 | auto const& symbVar = m_context.variable(*var); |
942 | 0 | m_context.addAssertion(symbVar->currentValue() == symbVar->valueAtIndex(0)); |
943 | 0 | nondetCall(contractType->contractDefinition(), *var); |
944 | 0 | } |
945 | 0 | } |
946 | | |
947 | | void CHC::nondetCall(ContractDefinition const& _contract, VariableDeclaration const& _var) |
948 | 0 | { |
949 | 0 | auto address = m_context.variable(_var)->currentValue(); |
950 | | // Load the called contract's state variables from the global state. |
951 | 0 | state().readStateVars(_contract, address); |
952 | |
|
953 | 0 | m_context.addAssertion(state().state() == state().state(0)); |
954 | 0 | auto preCallState = std::vector<smtutil::Expression>{state().state()} + currentStateVariables(_contract); |
955 | |
|
956 | 0 | state().newState(); |
957 | 0 | for (auto const* var: _contract.stateVariables()) |
958 | 0 | m_context.variable(*var)->increaseIndex(); |
959 | |
|
960 | 0 | Predicate const& callPredicate = *createSymbolicBlock( |
961 | 0 | nondetInterfaceSort(_contract, state()), |
962 | 0 | "nondet_call_" + uniquePrefix(), |
963 | 0 | PredicateType::FunctionSummary, |
964 | 0 | &_var, |
965 | 0 | m_currentContract |
966 | 0 | ); |
967 | 0 | auto postCallState = std::vector<smtutil::Expression>{state().state()} + currentStateVariables(_contract); |
968 | 0 | std::vector<smtutil::Expression> stateExprs = commonStateExpressions(errorFlag().increaseIndex(), address); |
969 | |
|
970 | 0 | auto nondet = (*m_nondetInterfaces.at(&_contract))(stateExprs + preCallState + postCallState); |
971 | 0 | auto nondetCall = callPredicate(stateExprs + preCallState + postCallState); |
972 | |
|
973 | 0 | addRule(smtutil::Expression::implies(nondet, nondetCall), nondetCall.name); |
974 | |
|
975 | 0 | m_context.addAssertion(nondetCall); |
976 | | |
977 | | // Load the called contract's state variables into the global state. |
978 | 0 | state().writeStateVars(_contract, address); |
979 | 0 | } |
980 | | |
981 | | void CHC::externalFunctionCall(FunctionCall const& _funCall) |
982 | 1.25k | { |
983 | | /// In external function calls we do not add a "predicate call" |
984 | | /// because we do not trust their function body anyway, |
985 | | /// so we just add the nondet_interface predicate. |
986 | | |
987 | 1.25k | solAssert(m_currentContract, ""); |
988 | | |
989 | 1.25k | auto [callExpr, callOptions] = functionCallExpression(_funCall); |
990 | 1.25k | FunctionType const& funType = dynamic_cast<FunctionType const&>(*callExpr->annotation().type); |
991 | | |
992 | 1.25k | auto kind = funType.kind(); |
993 | 1.25k | solAssert( |
994 | 1.25k | kind == FunctionType::Kind::External || |
995 | 1.25k | kind == FunctionType::Kind::BareCall || |
996 | 1.25k | kind == FunctionType::Kind::BareStaticCall, |
997 | 1.25k | "" |
998 | 1.25k | ); |
999 | | |
1000 | | |
1001 | | // Only consider high level external calls in trusted mode. |
1002 | 1.25k | if ( |
1003 | 1.25k | kind == FunctionType::Kind::External && |
1004 | 938 | (encodeExternalCallsAsTrusted() || isExternalCallToThis(callExpr)) |
1005 | 1.25k | ) |
1006 | 532 | { |
1007 | 532 | externalFunctionCallToTrustedCode(_funCall); |
1008 | 532 | return; |
1009 | 532 | } |
1010 | | |
1011 | | // Low level calls are still encoded nondeterministically. |
1012 | | |
1013 | 719 | auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
1014 | 719 | if (function) |
1015 | 275 | for (auto var: function->returnParameters()) |
1016 | 174 | m_context.variable(*var)->increaseIndex(); |
1017 | | |
1018 | | // If we see a low level call in trusted mode, |
1019 | | // we need to havoc the global state. |
1020 | 719 | if ( |
1021 | 719 | kind == FunctionType::Kind::BareCall && |
1022 | 302 | encodeExternalCallsAsTrusted() |
1023 | 719 | ) |
1024 | 0 | state().newStorage(); |
1025 | | |
1026 | | // No reentrancy from constructor calls. |
1027 | 719 | if (!m_currentFunction || m_currentFunction->isConstructor()) |
1028 | 227 | return; |
1029 | | |
1030 | 492 | if (Expression const* value = valueOption(callOptions)) |
1031 | 27 | decreaseBalanceFromOptionsValue(*value); |
1032 | | |
1033 | 492 | auto preCallState = std::vector<smtutil::Expression>{state().state()} + currentStateVariables(); |
1034 | | |
1035 | 492 | if (!usesStaticCall(_funCall)) |
1036 | 428 | { |
1037 | 428 | state().newState(); |
1038 | 428 | for (auto const* var: m_stateVariables) |
1039 | 155 | m_context.variable(*var)->increaseIndex(); |
1040 | 428 | } |
1041 | | |
1042 | 492 | Predicate const& callPredicate = *createSymbolicBlock( |
1043 | 492 | nondetInterfaceSort(*m_currentContract, state()), |
1044 | 492 | "nondet_call_" + uniquePrefix(), |
1045 | 492 | PredicateType::ExternalCallUntrusted, |
1046 | 492 | &_funCall |
1047 | 492 | ); |
1048 | 492 | auto postCallState = std::vector<smtutil::Expression>{state().state()} + currentStateVariables(); |
1049 | 492 | std::vector<smtutil::Expression> stateExprs = commonStateExpressions(errorFlag().increaseIndex(), state().thisAddress()); |
1050 | | |
1051 | 492 | auto nondet = (*m_nondetInterfaces.at(m_currentContract))(stateExprs + preCallState + postCallState); |
1052 | 492 | auto nondetCall = callPredicate(stateExprs + preCallState + postCallState); |
1053 | | |
1054 | 492 | addRule(smtutil::Expression::implies(nondet, nondetCall), nondetCall.name); |
1055 | | |
1056 | 492 | m_context.addAssertion(nondetCall); |
1057 | 492 | solAssert(m_errorDest, ""); |
1058 | 492 | connectBlocks(m_currentBlock, predicate(*m_errorDest), errorFlag().currentValue() > 0 && currentPathConditions()); |
1059 | | |
1060 | | // To capture the possibility of a reentrant call, we record in the call graph that the current function |
1061 | | // can call any of the external methods of the current contract. |
1062 | 492 | if (m_currentFunction) |
1063 | 492 | for (auto const* definedFunction: contractFunctions(*m_currentContract)) |
1064 | 809 | if (!definedFunction->isConstructor() && definedFunction->isPublic()) |
1065 | 717 | m_callGraph[m_currentFunction].insert(definedFunction); |
1066 | | |
1067 | 492 | m_context.addAssertion(errorFlag().currentValue() == 0); |
1068 | 492 | } |
1069 | | |
1070 | | void CHC::externalFunctionCallToTrustedCode(FunctionCall const& _funCall) |
1071 | 532 | { |
1072 | 532 | if (publicGetter(_funCall.expression())) |
1073 | 117 | visitPublicGetter(_funCall); |
1074 | | |
1075 | 532 | solAssert(m_currentContract, ""); |
1076 | | |
1077 | 532 | auto [callExpr, callOptions] = functionCallExpression(_funCall); |
1078 | 532 | FunctionType const& funType = dynamic_cast<FunctionType const&>(*callExpr->annotation().type); |
1079 | | |
1080 | 532 | auto kind = funType.kind(); |
1081 | 532 | solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, ""); |
1082 | | |
1083 | 532 | auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
1084 | 532 | if (!function) |
1085 | 117 | return; |
1086 | | |
1087 | | // Remember the external call in the call graph to properly detect verification targets for the current function |
1088 | 415 | if (m_currentFunction && !m_currentFunction->isConstructor()) |
1089 | 402 | m_callGraph[m_currentFunction].insert(function); |
1090 | 13 | else |
1091 | 13 | m_callGraph[m_currentContract].insert(function); |
1092 | | |
1093 | | // External call creates a new transaction. |
1094 | 415 | auto originalTx = state().tx(); |
1095 | 415 | Expression const* value = valueOption(callOptions); |
1096 | 415 | newTxConstraints(value); |
1097 | | |
1098 | 415 | auto calledAddress = contractAddressValue(_funCall); |
1099 | 415 | if (value) |
1100 | 7 | { |
1101 | 7 | decreaseBalanceFromOptionsValue(*value); |
1102 | 7 | state().addBalance(calledAddress, expr(*value)); |
1103 | 7 | } |
1104 | | |
1105 | 415 | if (encodeExternalCallsAsTrusted()) |
1106 | 0 | { |
1107 | | // The order here is important!! Write should go first. |
1108 | | |
1109 | | // Load the caller contract's state variables into the global state. |
1110 | 0 | state().writeStateVars(*m_currentContract, state().thisAddress()); |
1111 | | // Load the called contract's state variables from the global state. |
1112 | 0 | state().readStateVars(*function->annotation().contract, contractAddressValue(_funCall)); |
1113 | 0 | } |
1114 | | |
1115 | 415 | std::vector<Expression const*> arguments; |
1116 | 415 | for (auto& arg: _funCall.sortedArguments()) |
1117 | 193 | arguments.push_back(&(*arg)); |
1118 | 415 | smtutil::Expression pred = predicate(function, std::nullopt, &funType, arguments, calledAddress); |
1119 | | |
1120 | 415 | auto txConstraints = state().txTypeConstraints() && state().txFunctionConstraints(*function); |
1121 | 415 | m_context.addAssertion(pred && txConstraints); |
1122 | | // restore the original transaction data |
1123 | 415 | state().newTx(); |
1124 | 415 | m_context.addAssertion(originalTx == state().tx()); |
1125 | | |
1126 | 415 | solAssert(m_errorDest, ""); |
1127 | 415 | connectBlocks( |
1128 | 415 | m_currentBlock, |
1129 | 415 | predicate(*m_errorDest), |
1130 | 415 | (errorFlag().currentValue() > 0) |
1131 | 415 | ); |
1132 | 415 | m_context.addAssertion(errorFlag().currentValue() == 0); |
1133 | | |
1134 | 415 | if (!usesStaticCall(_funCall)) |
1135 | 367 | if (encodeExternalCallsAsTrusted()) |
1136 | 0 | { |
1137 | | // The order here is important!! Write should go first. |
1138 | | |
1139 | | // Load the called contract's state variables into the global state. |
1140 | 0 | state().writeStateVars(*function->annotation().contract, contractAddressValue(_funCall)); |
1141 | | // Load the caller contract's state variables from the global state. |
1142 | 0 | state().readStateVars(*m_currentContract, state().thisAddress()); |
1143 | 0 | } |
1144 | 415 | } |
1145 | | |
1146 | | void CHC::unknownFunctionCall(FunctionCall const&) |
1147 | 521 | { |
1148 | | /// Function calls are not handled at the moment, |
1149 | | /// so always erase knowledge. |
1150 | | /// TODO remove when function calls get predicates/blocks. |
1151 | 521 | eraseKnowledge(); |
1152 | | |
1153 | | /// Used to erase outer scope knowledge in loops and ifs. |
1154 | | /// TODO remove when function calls get predicates/blocks. |
1155 | 521 | m_unknownFunctionCallSeen = true; |
1156 | 521 | } |
1157 | | |
1158 | | void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) |
1159 | 218 | { |
1160 | 218 | FunctionType const& funType = dynamic_cast<FunctionType const&>(*_arrayPop.expression().annotation().type); |
1161 | 218 | solAssert(funType.kind() == FunctionType::Kind::ArrayPop, ""); |
1162 | | |
1163 | 218 | auto memberAccess = dynamic_cast<MemberAccess const*>(cleanExpression(_arrayPop.expression())); |
1164 | 218 | solAssert(memberAccess, ""); |
1165 | 218 | auto symbArray = std::dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(memberAccess->expression())); |
1166 | 218 | solAssert(symbArray, ""); |
1167 | | |
1168 | 218 | verificationTargetEncountered(&_arrayPop, VerificationTargetType::PopEmptyArray, symbArray->length() <= 0); |
1169 | 218 | } |
1170 | | |
1171 | | void CHC::makeOutOfBoundsVerificationTarget(IndexAccess const& _indexAccess) |
1172 | 16.1k | { |
1173 | 16.1k | if (_indexAccess.annotation().type->category() == Type::Category::TypeType) |
1174 | 0 | return; |
1175 | | |
1176 | 16.1k | auto baseType = _indexAccess.baseExpression().annotation().type; |
1177 | | |
1178 | 16.1k | std::optional<smtutil::Expression> length; |
1179 | 16.1k | if (smt::isArray(*baseType)) |
1180 | 14.3k | length = dynamic_cast<smt::SymbolicArrayVariable const&>( |
1181 | 14.3k | *m_context.expression(_indexAccess.baseExpression()) |
1182 | 14.3k | ).length(); |
1183 | 1.78k | else if (auto const* type = dynamic_cast<FixedBytesType const*>(baseType)) |
1184 | 534 | length = smtutil::Expression(static_cast<size_t>(type->numBytes())); |
1185 | | |
1186 | 16.1k | std::optional<smtutil::Expression> target; |
1187 | 16.1k | if ( |
1188 | 16.1k | auto index = _indexAccess.indexExpression(); |
1189 | 16.1k | index && length |
1190 | 16.1k | ) |
1191 | 14.8k | target = expr(*index) < 0 || expr(*index) >= *length; |
1192 | | |
1193 | 16.1k | if (target) |
1194 | 14.8k | verificationTargetEncountered(&_indexAccess, VerificationTargetType::OutOfBounds, *target); |
1195 | 16.1k | } |
1196 | | |
1197 | | std::pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation( |
1198 | | Token _op, |
1199 | | smtutil::Expression const& _left, |
1200 | | smtutil::Expression const& _right, |
1201 | | Type const* _commonType, |
1202 | | frontend::Expression const& _expression |
1203 | | ) |
1204 | 10.1k | { |
1205 | | // Unchecked does not disable div by 0 checks. |
1206 | 10.1k | if (_op == Token::Mod || _op == Token::Div) |
1207 | 861 | verificationTargetEncountered(&_expression, VerificationTargetType::DivByZero, _right == 0); |
1208 | | |
1209 | 10.1k | auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); |
1210 | | |
1211 | 10.1k | if (!m_checked) |
1212 | 114 | return values; |
1213 | | |
1214 | 10.0k | IntegerType const* intType = nullptr; |
1215 | 10.0k | if (auto const* type = dynamic_cast<IntegerType const*>(_commonType)) |
1216 | 9.75k | intType = type; |
1217 | 305 | else |
1218 | 305 | intType = TypeProvider::uint256(); |
1219 | | |
1220 | | // Mod does not need underflow/overflow checks. |
1221 | | // Div only needs overflow check for signed types. |
1222 | 10.0k | if (_op == Token::Mod || (_op == Token::Div && !intType->isSigned())) |
1223 | 819 | return values; |
1224 | | |
1225 | 9.24k | if (_op == Token::Div) |
1226 | 32 | verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); |
1227 | 9.20k | else if (intType->isSigned()) |
1228 | 419 | { |
1229 | 419 | verificationTargetEncountered(&_expression, VerificationTargetType::Underflow, values.second < intType->minValue()); |
1230 | 419 | verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); |
1231 | 419 | } |
1232 | 8.79k | else if (_op == Token::Sub) |
1233 | 3.80k | verificationTargetEncountered(&_expression, VerificationTargetType::Underflow, values.second < intType->minValue()); |
1234 | 4.98k | else if (_op == Token::Add || _op == Token::Mul) |
1235 | 4.98k | verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); |
1236 | 0 | else |
1237 | 4.98k | solAssert(false, ""); |
1238 | 9.24k | return values; |
1239 | 10.0k | } |
1240 | | |
1241 | | void CHC::resetSourceAnalysis() |
1242 | 15.5k | { |
1243 | 15.5k | SMTEncoder::resetSourceAnalysis(); |
1244 | | |
1245 | 15.5k | m_unprovedTargets.clear(); |
1246 | 15.5k | m_invariants.clear(); |
1247 | 15.5k | m_functionTargetIds.clear(); |
1248 | 15.5k | m_verificationTargets.clear(); |
1249 | 15.5k | m_queryPlaceholders.clear(); |
1250 | 15.5k | m_callGraph.clear(); |
1251 | 15.5k | m_summaries.clear(); |
1252 | 15.5k | m_externalSummaries.clear(); |
1253 | 15.5k | m_interfaces.clear(); |
1254 | 15.5k | m_nondetInterfaces.clear(); |
1255 | 15.5k | m_constructorSummaries.clear(); |
1256 | 15.5k | m_contractInitializers.clear(); |
1257 | 15.5k | Predicate::reset(); |
1258 | 15.5k | ArraySlicePredicate::reset(); |
1259 | 15.5k | m_blockCounter = 0; |
1260 | | |
1261 | 15.5k | solAssert(m_settings.solvers.smtlib2 || m_settings.solvers.eld || m_settings.solvers.z3); |
1262 | 15.5k | if (!m_interface) |
1263 | 13.9k | { |
1264 | 13.9k | if (m_settings.solvers.z3) |
1265 | 0 | m_interface = std::make_unique<Z3CHCSmtLib2Interface>( |
1266 | 0 | m_smtCallback, |
1267 | 0 | m_settings.timeout, |
1268 | 0 | m_settings.invariants != ModelCheckerInvariants::None() |
1269 | 0 | ); |
1270 | 13.9k | else if (m_settings.solvers.eld) |
1271 | 0 | m_interface = std::make_unique<EldaricaCHCSmtLib2Interface>( |
1272 | 0 | m_smtCallback, |
1273 | 0 | m_settings.timeout, |
1274 | 0 | m_settings.invariants != ModelCheckerInvariants::None() |
1275 | 0 | ); |
1276 | 13.9k | else |
1277 | 13.9k | m_interface = std::make_unique<CHCSmtLib2Interface>(m_smtlib2Responses, m_smtCallback, m_settings.timeout); |
1278 | 13.9k | } |
1279 | | |
1280 | 15.5k | auto smtlib2Interface = dynamic_cast<CHCSmtLib2Interface*>(m_interface.get()); |
1281 | 15.5k | solAssert(smtlib2Interface); |
1282 | 15.5k | smtlib2Interface->reset(); |
1283 | 15.5k | m_context.setSolver(smtlib2Interface); |
1284 | | |
1285 | 15.5k | m_context.reset(); |
1286 | 15.5k | m_context.resetUniqueId(); |
1287 | 15.5k | m_context.setAssertionAccumulation(false); |
1288 | 15.5k | } |
1289 | | |
1290 | | void CHC::resetContractAnalysis() |
1291 | 16.2k | { |
1292 | 16.2k | m_stateVariables.clear(); |
1293 | 16.2k | m_unknownFunctionCallSeen = false; |
1294 | 16.2k | m_breakDest = nullptr; |
1295 | 16.2k | m_continueDest = nullptr; |
1296 | 16.2k | m_returnDests.clear(); |
1297 | 16.2k | errorFlag().resetIndex(); |
1298 | 16.2k | } |
1299 | | |
1300 | | void CHC::eraseKnowledge() |
1301 | 526 | { |
1302 | 526 | resetStorageVariables(); |
1303 | 526 | resetBalances(); |
1304 | 526 | } |
1305 | | |
1306 | | void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function) |
1307 | 202k | { |
1308 | 202k | SMTEncoder::clearIndices(_contract, _function); |
1309 | 202k | for (auto const* var: m_stateVariables) |
1310 | | /// SSA index 0 is reserved for state variables at the beginning |
1311 | | /// of the current transaction. |
1312 | 92.8k | m_context.variable(*var)->increaseIndex(); |
1313 | 202k | if (_function) |
1314 | 101k | { |
1315 | 101k | for (auto const& var: _function->parameters() + _function->returnParameters()) |
1316 | 104k | m_context.variable(*var)->increaseIndex(); |
1317 | 101k | for (auto const& var: localVariablesIncludingModifiers(*_function, _contract)) |
1318 | 42.5k | m_context.variable(*var)->increaseIndex(); |
1319 | 101k | } |
1320 | | |
1321 | 202k | state().newState(); |
1322 | 202k | } |
1323 | | |
1324 | | void CHC::setCurrentBlock(Predicate const& _block) |
1325 | 152k | { |
1326 | 152k | if (m_context.solverStackHeight() > 0) |
1327 | 152k | m_context.popSolver(); |
1328 | 152k | solAssert(m_currentContract, ""); |
1329 | 152k | clearIndices(m_currentContract, m_currentFunction); |
1330 | 152k | m_context.pushSolver(); |
1331 | 152k | m_currentBlock = predicate(_block); |
1332 | 152k | } |
1333 | | |
1334 | | std::set<unsigned> CHC::transactionVerificationTargetsIds(ASTNode const* _txRoot) |
1335 | 28.6k | { |
1336 | 28.6k | std::set<unsigned> verificationTargetsIds; |
1337 | 28.6k | struct ASTNodeCompare: EncodingContext::IdCompare |
1338 | 28.6k | { |
1339 | 28.6k | bool operator<(ASTNodeCompare _other) const { return operator()(node, _other.node); } |
1340 | 28.6k | ASTNode const* node; |
1341 | 28.6k | }; |
1342 | 32.0k | solidity::util::BreadthFirstSearch<ASTNodeCompare>{{{{}, _txRoot}}}.run([&](auto _node, auto&& _addChild) { |
1343 | 32.0k | verificationTargetsIds.insert(m_functionTargetIds[_node.node].begin(), m_functionTargetIds[_node.node].end()); |
1344 | 32.0k | for (ASTNode const* called: m_callGraph[_node.node]) |
1345 | 4.98k | _addChild({{}, called}); |
1346 | 32.0k | }); |
1347 | 28.6k | return verificationTargetsIds; |
1348 | 28.6k | } |
1349 | | |
1350 | | bool CHC::usesStaticCall(FunctionDefinition const* _funDef, FunctionType const* _funType) |
1351 | 2.15k | { |
1352 | 2.15k | auto kind = _funType->kind(); |
1353 | 2.15k | return (_funDef && (_funDef->stateMutability() == StateMutability::Pure || _funDef->stateMutability() == StateMutability::View)) || kind == FunctionType::Kind::BareStaticCall; |
1354 | 2.15k | } |
1355 | | |
1356 | | bool CHC::usesStaticCall(FunctionCall const& _funCall) |
1357 | 907 | { |
1358 | 907 | FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); |
1359 | 907 | auto kind = funType.kind(); |
1360 | 907 | auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); |
1361 | 907 | return (function && (function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View)) || kind == FunctionType::Kind::BareStaticCall; |
1362 | 907 | } |
1363 | | |
1364 | | std::optional<CHC::CHCNatspecOption> CHC::natspecOptionFromString(std::string const& _option) |
1365 | 110 | { |
1366 | 110 | static std::map<std::string, CHCNatspecOption> options{ |
1367 | 110 | {"abstract-function-nondet", CHCNatspecOption::AbstractFunctionNondet} |
1368 | 110 | }; |
1369 | 110 | if (options.count(_option)) |
1370 | 4 | return options.at(_option); |
1371 | 106 | return {}; |
1372 | 110 | } |
1373 | | |
1374 | | std::set<CHC::CHCNatspecOption> CHC::smtNatspecTags(FunctionDefinition const& _function) |
1375 | 34.4k | { |
1376 | 34.4k | std::set<CHC::CHCNatspecOption> options; |
1377 | 34.4k | std::string smtStr = "custom:smtchecker"; |
1378 | 34.4k | bool errorSeen = false; |
1379 | 34.4k | for (auto const& [tag, value]: _function.annotation().docTags) |
1380 | 514 | if (tag == smtStr) |
1381 | 110 | { |
1382 | 110 | std::string const& content = value.content; |
1383 | 110 | if (auto option = natspecOptionFromString(content)) |
1384 | 4 | options.insert(*option); |
1385 | 106 | else if (!errorSeen) |
1386 | 60 | { |
1387 | 60 | errorSeen = true; |
1388 | 60 | m_errorReporter.warning(3130_error, _function.location(), "Unknown option for \"" + smtStr + "\": \"" + content + "\""); |
1389 | 60 | } |
1390 | 110 | } |
1391 | 34.4k | return options; |
1392 | 34.4k | } |
1393 | | |
1394 | | bool CHC::abstractAsNondet(FunctionDefinition const& _function) |
1395 | 34.4k | { |
1396 | 34.4k | return smtNatspecTags(_function).count(CHCNatspecOption::AbstractFunctionNondet); |
1397 | 34.4k | } |
1398 | | |
1399 | | SortPointer CHC::sort(FunctionDefinition const& _function) |
1400 | 65.7k | { |
1401 | 65.7k | return functionBodySort(_function, m_currentContract, state()); |
1402 | 65.7k | } |
1403 | | |
1404 | | bool CHC::encodeExternalCallsAsTrusted() |
1405 | 79.7k | { |
1406 | 79.7k | return m_settings.externalCalls.isTrusted(); |
1407 | 79.7k | } |
1408 | | |
1409 | | SortPointer CHC::sort(ASTNode const* _node) |
1410 | 95.5k | { |
1411 | 95.5k | if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node)) |
1412 | 65.7k | return sort(*funDef); |
1413 | | |
1414 | 29.7k | solAssert(m_currentFunction, ""); |
1415 | 29.7k | return functionBodySort(*m_currentFunction, m_currentContract, state()); |
1416 | 95.5k | } |
1417 | | |
1418 | | Predicate const* CHC::createSymbolicBlock(SortPointer _sort, std::string const& _name, PredicateType _predType, ASTNode const* _node, ContractDefinition const* _contractContext) |
1419 | 269k | { |
1420 | 269k | auto const* block = Predicate::create(_sort, _name, _predType, m_context, _node, _contractContext, m_scopes); |
1421 | 269k | m_interface->registerRelation(block->functor()); |
1422 | 269k | return block; |
1423 | 269k | } |
1424 | | |
1425 | | void CHC::defineInterfacesAndSummaries(SourceUnit const& _source) |
1426 | 16.1k | { |
1427 | 16.1k | for (auto const& node: _source.nodes()) |
1428 | 39.0k | if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get())) |
1429 | 16.8k | { |
1430 | 16.8k | std::string suffix = contract->name() + "_" + std::to_string(contract->id()); |
1431 | 16.8k | m_interfaces[contract] = createSymbolicBlock(interfaceSort(*contract, state()), "interface_" + uniquePrefix() + "_" + suffix, PredicateType::Interface, contract, contract); |
1432 | 16.8k | m_nondetInterfaces[contract] = createSymbolicBlock(nondetInterfaceSort(*contract, state()), "nondet_interface_" + uniquePrefix() + "_" + suffix, PredicateType::NondetInterface, contract, contract); |
1433 | 16.8k | m_constructorSummaries[contract] = createConstructorBlock(*contract, "summary_constructor"); |
1434 | | |
1435 | 16.8k | for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*contract)) |
1436 | 9.10k | if (!m_context.knownVariable(*var)) |
1437 | 8.41k | createVariable(*var); |
1438 | | |
1439 | | /// Base nondeterministic interface that allows |
1440 | | /// 0 steps to be taken, used as base for the inductive |
1441 | | /// rule for each function. |
1442 | 16.8k | auto const& iface = *m_nondetInterfaces.at(contract); |
1443 | 16.8k | addRule(smtutil::Expression::implies(errorFlag().currentValue() == 0, smt::nondetInterface(iface, *contract, m_context, 0, 0)), "base_nondet"); |
1444 | | |
1445 | 16.8k | auto const& resolved = contractFunctions(*contract); |
1446 | 16.8k | for (auto const* function: contractFunctionsWithoutVirtual(*contract) + allFreeFunctions()) |
1447 | 18.2k | { |
1448 | 18.2k | for (auto var: function->parameters()) |
1449 | 8.46k | createVariable(*var); |
1450 | 18.2k | for (auto var: function->returnParameters()) |
1451 | 9.39k | createVariable(*var); |
1452 | 18.2k | for (auto const* var: localVariablesIncludingModifiers(*function, contract)) |
1453 | 4.48k | createVariable(*var); |
1454 | | |
1455 | 18.2k | m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract)); |
1456 | | |
1457 | 18.2k | if ( |
1458 | 18.2k | !function->isConstructor() && |
1459 | 15.8k | function->isPublic() && |
1460 | | // Public library functions should have interfaces only for the libraries |
1461 | | // they're declared in. |
1462 | 13.9k | (!function->libraryFunction() || (function->scope() == contract)) && |
1463 | 13.7k | resolved.count(function) |
1464 | 18.2k | ) |
1465 | 13.3k | { |
1466 | 13.3k | m_externalSummaries[contract].emplace(function, createSummaryBlock(*function, *contract)); |
1467 | | |
1468 | 13.3k | auto state1 = stateVariablesAtIndex(1, *contract); |
1469 | 13.3k | auto state2 = stateVariablesAtIndex(2, *contract); |
1470 | | |
1471 | 13.3k | auto errorPre = errorFlag().currentValue(); |
1472 | 13.3k | auto nondetPre = smt::nondetInterface(iface, *contract, m_context, 0, 1); |
1473 | 13.3k | auto errorPost = errorFlag().increaseIndex(); |
1474 | 13.3k | auto nondetPost = smt::nondetInterface(iface, *contract, m_context, 0, 2); |
1475 | | |
1476 | 13.3k | std::vector<smtutil::Expression> args = |
1477 | 13.3k | commonStateExpressions(errorPost, state().thisAddress()) + |
1478 | 13.3k | std::vector<smtutil::Expression>{state().tx(), state().state(1)}; |
1479 | 13.3k | args += state1 + |
1480 | 13.3k | applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); }) + |
1481 | 13.3k | std::vector<smtutil::Expression>{state().state(2)} + |
1482 | 13.3k | state2 + |
1483 | 13.3k | applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) + |
1484 | 13.3k | applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }); |
1485 | | |
1486 | 13.3k | connectBlocks(nondetPre, nondetPost, errorPre == 0 && (*m_externalSummaries.at(contract).at(function))(args)); |
1487 | 13.3k | } |
1488 | 18.2k | } |
1489 | 16.8k | } |
1490 | 16.1k | } |
1491 | | |
1492 | | void CHC::defineExternalFunctionInterface(FunctionDefinition const& _function, ContractDefinition const& _contract) |
1493 | 12.7k | { |
1494 | | // Create a rule that represents an external call to this function. |
1495 | | // This contains more things than the function body itself, |
1496 | | // such as balance updates because of ``msg.value``. |
1497 | 12.7k | auto functionEntryBlock = createBlock(&_function, PredicateType::FunctionBlock); |
1498 | 12.7k | auto functionPred = predicate(*functionEntryBlock); |
1499 | 12.7k | addRule(functionPred, functionPred.name); |
1500 | 12.7k | setCurrentBlock(*functionEntryBlock); |
1501 | | |
1502 | 12.7k | m_context.addAssertion(initialConstraints(_contract, &_function)); |
1503 | 12.7k | m_context.addAssertion(state().txTypeConstraints() && state().txFunctionConstraints(_function)); |
1504 | | |
1505 | | // The contract may have received funds through a selfdestruct or |
1506 | | // block.coinbase, which do not trigger calls into the contract. |
1507 | | // So the only constraint we can add here is that the balance of |
1508 | | // the contract grows by at least `msg.value`. |
1509 | 12.7k | SymbolicIntVariable k{TypeProvider::uint256(), TypeProvider::uint256(), "funds_" + std::to_string(m_context.newUniqueId()), m_context}; |
1510 | 12.7k | m_context.addAssertion(k.currentValue() >= state().txMember("msg.value")); |
1511 | | // Assume that address(this).balance cannot overflow. |
1512 | 12.7k | m_context.addAssertion(smt::symbolicUnknownConstraints(state().balance(state().thisAddress()) + k.currentValue(), TypeProvider::uint256())); |
1513 | 12.7k | state().addBalance(state().thisAddress(), k.currentValue()); |
1514 | | |
1515 | 12.7k | if (encodeExternalCallsAsTrusted()) |
1516 | 0 | { |
1517 | | // If the contract has state variables that are addresses to other contracts, |
1518 | | // we need to encode the fact that those contracts may have been called in between |
1519 | | // transactions to _contract. |
1520 | | // |
1521 | | // We do that by adding nondet_interface constraints for those contracts, |
1522 | | // in the last line of this if block. |
1523 | | // |
1524 | | // If there are state variables of container types like structs or arrays |
1525 | | // that indirectly contain contract types, we havoc the state for simplicity, |
1526 | | // in the first part of this block. |
1527 | | // TODO: This could actually be supported. |
1528 | | // For structs: simply collect the SMT expressions of all the indirect contract type members. |
1529 | | // For arrays: more involved, needs to traverse the array symbolically and do the same for each contract. |
1530 | | // For mappings: way more complicated if the element type is a contract. |
1531 | 0 | auto hasContractOrAddressSubType = [&](VariableDeclaration const* _var) -> bool { |
1532 | 0 | bool foundContract = false; |
1533 | 0 | solidity::util::BreadthFirstSearch<Type const*> bfs{{_var->type()}}; |
1534 | 0 | bfs.run([&](auto _type, auto&& _addChild) { |
1535 | 0 | if ( |
1536 | 0 | _type->category() == Type::Category::Address || |
1537 | 0 | _type->category() == Type::Category::Contract |
1538 | 0 | ) |
1539 | 0 | { |
1540 | 0 | foundContract = true; |
1541 | 0 | bfs.abort(); |
1542 | 0 | } |
1543 | 0 | if (auto const* mapType = dynamic_cast<MappingType const*>(_type)) |
1544 | 0 | _addChild(mapType->valueType()); |
1545 | 0 | else if (auto const* arrayType = dynamic_cast<ArrayType const*>(_type)) |
1546 | 0 | _addChild(arrayType->baseType()); |
1547 | 0 | else if (auto const* structType = dynamic_cast<StructType const*>(_type)) |
1548 | 0 | for (auto const& member: structType->nativeMembers(nullptr)) |
1549 | 0 | _addChild(member.type); |
1550 | 0 | }); |
1551 | 0 | return foundContract; |
1552 | 0 | }; |
1553 | 0 | bool found = false; |
1554 | 0 | for (auto var: m_currentContract->stateVariables()) |
1555 | 0 | if ( |
1556 | 0 | var->type()->category() != Type::Category::Address && |
1557 | 0 | var->type()->category() != Type::Category::Contract && |
1558 | 0 | hasContractOrAddressSubType(var) |
1559 | 0 | ) |
1560 | 0 | { |
1561 | 0 | found = true; |
1562 | 0 | break; |
1563 | 0 | } |
1564 | |
|
1565 | 0 | if (found) |
1566 | 0 | state().newStorage(); |
1567 | 0 | else |
1568 | 0 | addNondetCalls(*m_currentContract); |
1569 | 0 | } |
1570 | | |
1571 | 12.7k | errorFlag().increaseIndex(); |
1572 | 12.7k | m_context.addAssertion(summaryCall(_function)); |
1573 | | |
1574 | 12.7k | connectBlocks(functionPred, externalSummary(_function)); |
1575 | 12.7k | } |
1576 | | |
1577 | | void CHC::defineContractInitializer(ContractDefinition const& _contract, ContractDefinition const& _contextContract) |
1578 | 18.0k | { |
1579 | 18.0k | m_contractInitializers[&_contextContract][&_contract] = createConstructorBlock(_contract, "contract_initializer"); |
1580 | 18.0k | auto const& implicitConstructorPredicate = *createConstructorBlock(_contract, "contract_initializer_entry"); |
1581 | | |
1582 | 18.0k | auto implicitFact = smt::constructor(implicitConstructorPredicate, m_context); |
1583 | 18.0k | addRule(smtutil::Expression::implies(initialConstraints(_contract), implicitFact), implicitFact.name); |
1584 | 18.0k | setCurrentBlock(implicitConstructorPredicate); |
1585 | | |
1586 | 18.0k | auto prevErrorDest = m_errorDest; |
1587 | 18.0k | m_errorDest = m_contractInitializers.at(&_contextContract).at(&_contract); |
1588 | 18.0k | for (auto var: _contract.stateVariables()) |
1589 | 9.05k | if (var->value()) |
1590 | 2.89k | { |
1591 | 2.89k | var->value()->accept(*this); |
1592 | 2.89k | assignment(*var, *var->value()); |
1593 | 2.89k | } |
1594 | 18.0k | m_errorDest = prevErrorDest; |
1595 | | |
1596 | 18.0k | auto const& afterInit = *createConstructorBlock(_contract, "contract_initializer_after_init"); |
1597 | 18.0k | connectBlocks(m_currentBlock, predicate(afterInit)); |
1598 | 18.0k | setCurrentBlock(afterInit); |
1599 | | |
1600 | 18.0k | if (auto constructor = _contract.constructor()) |
1601 | 2.29k | { |
1602 | 2.29k | errorFlag().increaseIndex(); |
1603 | 2.29k | m_context.addAssertion(smt::functionCall(*m_summaries.at(&_contextContract).at(constructor), &_contextContract, m_context)); |
1604 | 2.29k | connectBlocks(m_currentBlock, initializer(_contract, _contextContract), errorFlag().currentValue() > 0); |
1605 | 2.29k | m_context.addAssertion(errorFlag().currentValue() == 0); |
1606 | 2.29k | } |
1607 | | |
1608 | 18.0k | connectBlocks(m_currentBlock, initializer(_contract, _contextContract)); |
1609 | 18.0k | } |
1610 | | |
1611 | | smtutil::Expression CHC::interface() |
1612 | 29.0k | { |
1613 | 29.0k | solAssert(m_currentContract, ""); |
1614 | 29.0k | return interface(*m_currentContract); |
1615 | 29.0k | } |
1616 | | |
1617 | | smtutil::Expression CHC::interface(ContractDefinition const& _contract) |
1618 | 29.0k | { |
1619 | 29.0k | return ::interface(*m_interfaces.at(&_contract), _contract, m_context); |
1620 | 29.0k | } |
1621 | | |
1622 | | smtutil::Expression CHC::error() |
1623 | 37.1k | { |
1624 | 37.1k | return (*m_errorPredicate)({}); |
1625 | 37.1k | } |
1626 | | |
1627 | | smtutil::Expression CHC::initializer(ContractDefinition const& _contract, ContractDefinition const& _contractContext) |
1628 | 20.3k | { |
1629 | 20.3k | return predicate(*m_contractInitializers.at(&_contractContext).at(&_contract)); |
1630 | 20.3k | } |
1631 | | |
1632 | | smtutil::Expression CHC::summary(ContractDefinition const& _contract) |
1633 | 34.2k | { |
1634 | 34.2k | return predicate(*m_constructorSummaries.at(&_contract)); |
1635 | 34.2k | } |
1636 | | |
1637 | | smtutil::Expression CHC::summary(FunctionDefinition const& _function, ContractDefinition const& _contract) |
1638 | 17.4k | { |
1639 | 17.4k | return smt::function(*m_summaries.at(&_contract).at(&_function), &_contract, m_context); |
1640 | 17.4k | } |
1641 | | |
1642 | | smtutil::Expression CHC::summary(FunctionDefinition const& _function) |
1643 | 17.4k | { |
1644 | 17.4k | solAssert(m_currentContract, ""); |
1645 | 17.4k | return summary(_function, *m_currentContract); |
1646 | 17.4k | } |
1647 | | |
1648 | | smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function, ContractDefinition const& _contract) |
1649 | 12.7k | { |
1650 | 12.7k | return smt::functionCall(*m_summaries.at(&_contract).at(&_function), &_contract, m_context); |
1651 | 12.7k | } |
1652 | | |
1653 | | smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function) |
1654 | 12.7k | { |
1655 | 12.7k | solAssert(m_currentContract, ""); |
1656 | 12.7k | return summaryCall(_function, *m_currentContract); |
1657 | 12.7k | } |
1658 | | |
1659 | | smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function, ContractDefinition const& _contract) |
1660 | 25.5k | { |
1661 | 25.5k | return smt::function(*m_externalSummaries.at(&_contract).at(&_function), &_contract, m_context); |
1662 | 25.5k | } |
1663 | | |
1664 | | smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function) |
1665 | 25.5k | { |
1666 | 25.5k | solAssert(m_currentContract, ""); |
1667 | 25.5k | return externalSummary(_function, *m_currentContract); |
1668 | 25.5k | } |
1669 | | |
1670 | | Predicate const* CHC::createBlock(ASTNode const* _node, PredicateType _predType, std::string const& _prefix) |
1671 | 95.5k | { |
1672 | 95.5k | auto block = createSymbolicBlock( |
1673 | 95.5k | sort(_node), |
1674 | 95.5k | "block_" + uniquePrefix() + "_" + _prefix + predicateName(_node), |
1675 | 95.5k | _predType, |
1676 | 95.5k | _node, |
1677 | 95.5k | m_currentContract |
1678 | 95.5k | ); |
1679 | | |
1680 | 95.5k | solAssert(m_currentFunction, ""); |
1681 | 95.5k | return block; |
1682 | 95.5k | } |
1683 | | |
1684 | | Predicate const* CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract, PredicateType _type) |
1685 | 33.7k | { |
1686 | 33.7k | return createSymbolicBlock( |
1687 | 33.7k | functionSort(_function, &_contract, state()), |
1688 | 33.7k | "summary_" + uniquePrefix() + "_" + predicateName(&_function, &_contract), |
1689 | 33.7k | _type, |
1690 | 33.7k | &_function, |
1691 | 33.7k | &_contract |
1692 | 33.7k | ); |
1693 | 33.7k | } |
1694 | | |
1695 | | Predicate const* CHC::createConstructorBlock(ContractDefinition const& _contract, std::string const& _prefix) |
1696 | 87.2k | { |
1697 | 87.2k | return createSymbolicBlock( |
1698 | 87.2k | constructorSort(_contract, state()), |
1699 | 87.2k | _prefix + "_" + uniquePrefix() + "_" + contractSuffix(_contract), |
1700 | 87.2k | PredicateType::ConstructorSummary, |
1701 | 87.2k | &_contract, |
1702 | 87.2k | &_contract |
1703 | 87.2k | ); |
1704 | 87.2k | } |
1705 | | |
1706 | | void CHC::createErrorBlock() |
1707 | 18.5k | { |
1708 | 18.5k | m_errorPredicate = createSymbolicBlock( |
1709 | 18.5k | arity0FunctionSort(), |
1710 | 18.5k | "error_target_" + std::to_string(m_context.newUniqueId()), |
1711 | 18.5k | PredicateType::Error |
1712 | 18.5k | ); |
1713 | 18.5k | } |
1714 | | |
1715 | | void CHC::connectBlocks(smtutil::Expression const& _from, smtutil::Expression const& _to, smtutil::Expression const& _constraints) |
1716 | 233k | { |
1717 | 233k | smtutil::Expression edge = smtutil::Expression::implies( |
1718 | 233k | _from && m_context.assertions() && _constraints, |
1719 | 233k | _to |
1720 | 233k | ); |
1721 | 233k | addRule(edge, _from.name + "_to_" + _to.name); |
1722 | 233k | } |
1723 | | |
1724 | | smtutil::Expression CHC::initialConstraints(ContractDefinition const& _contract, FunctionDefinition const* _function) |
1725 | 64.2k | { |
1726 | 64.2k | smtutil::Expression conj = state().state() == state().state(0); |
1727 | 64.2k | conj = conj && errorFlag().currentValue() == 0; |
1728 | 64.2k | conj = conj && currentEqualInitialVarsConstraints(stateVariablesIncludingInheritedAndPrivate(_contract)); |
1729 | | |
1730 | 64.2k | FunctionDefinition const* function = _function ? _function : _contract.constructor(); |
1731 | 64.2k | if (function) |
1732 | 34.0k | conj = conj && currentEqualInitialVarsConstraints(applyMap(function->parameters(), [](auto&& _var) -> VariableDeclaration const* { return _var.get(); })); |
1733 | | |
1734 | 64.2k | return conj; |
1735 | 64.2k | } |
1736 | | |
1737 | | std::vector<smtutil::Expression> CHC::initialStateVariables() |
1738 | 0 | { |
1739 | 0 | return stateVariablesAtIndex(0); |
1740 | 0 | } |
1741 | | |
1742 | | std::vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index) |
1743 | 0 | { |
1744 | 0 | solAssert(m_currentContract, ""); |
1745 | 0 | return stateVariablesAtIndex(_index, *m_currentContract); |
1746 | 0 | } |
1747 | | |
1748 | | std::vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract) |
1749 | 26.6k | { |
1750 | 26.6k | return applyMap( |
1751 | 26.6k | SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract), |
1752 | 26.6k | [&](auto _var) { return valueAtIndex(*_var, _index); } |
1753 | 26.6k | ); |
1754 | 26.6k | } |
1755 | | |
1756 | | std::vector<smtutil::Expression> CHC::currentStateVariables() |
1757 | 984 | { |
1758 | 984 | solAssert(m_currentContract, ""); |
1759 | 984 | return currentStateVariables(*m_currentContract); |
1760 | 984 | } |
1761 | | |
1762 | | std::vector<smtutil::Expression> CHC::currentStateVariables(ContractDefinition const& _contract) |
1763 | 5.29k | { |
1764 | 5.29k | return applyMap(SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); }); |
1765 | 5.29k | } |
1766 | | |
1767 | | smtutil::Expression CHC::currentEqualInitialVarsConstraints(std::vector<VariableDeclaration const*> const& _vars) const |
1768 | 98.4k | { |
1769 | 98.4k | return fold(_vars, smtutil::Expression(true), [this](auto&& _conj, auto _var) { |
1770 | 50.6k | return std::move(_conj) && currentValue(*_var) == m_context.variable(*_var)->valueAtIndex(0); |
1771 | 50.6k | }); |
1772 | 98.4k | } |
1773 | | |
1774 | | std::string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract) |
1775 | 129k | { |
1776 | 129k | std::string prefix; |
1777 | 129k | if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node)) |
1778 | 99.4k | { |
1779 | 99.4k | prefix += TokenTraits::toString(funDef->kind()); |
1780 | 99.4k | if (!funDef->name().empty()) |
1781 | 89.9k | prefix += "_" + funDef->name() + "_"; |
1782 | 99.4k | } |
1783 | 29.7k | else if (m_currentFunction && !m_currentFunction->name().empty()) |
1784 | 26.6k | prefix += m_currentFunction->name(); |
1785 | | |
1786 | 129k | auto contract = _contract ? _contract : m_currentContract; |
1787 | 129k | solAssert(contract, ""); |
1788 | 129k | return prefix + "_" + std::to_string(_node->id()) + "_" + std::to_string(contract->id()); |
1789 | 129k | } |
1790 | | |
1791 | | smtutil::Expression CHC::predicate(Predicate const& _block) |
1792 | 366k | { |
1793 | 366k | switch (_block.type()) |
1794 | 366k | { |
1795 | 12.7k | case PredicateType::Interface: |
1796 | 12.7k | solAssert(m_currentContract, ""); |
1797 | 12.7k | return ::interface(_block, *m_currentContract, m_context); |
1798 | 157k | case PredicateType::ConstructorSummary: |
1799 | 157k | return constructor(_block, m_context); |
1800 | 38.1k | case PredicateType::FunctionSummary: |
1801 | 38.1k | case PredicateType::InternalCall: |
1802 | 38.1k | case PredicateType::ExternalCallTrusted: |
1803 | 38.1k | case PredicateType::ExternalCallUntrusted: |
1804 | 38.1k | return smt::function(_block, m_currentContract, m_context); |
1805 | 138k | case PredicateType::FunctionBlock: |
1806 | 157k | case PredicateType::FunctionErrorBlock: |
1807 | 157k | solAssert(m_currentFunction, ""); |
1808 | 157k | return functionBlock(_block, *m_currentFunction, m_currentContract, m_context); |
1809 | 0 | case PredicateType::Error: |
1810 | 0 | return _block({}); |
1811 | 0 | case PredicateType::NondetInterface: |
1812 | | // Nondeterministic interface predicates are handled differently. |
1813 | 0 | solAssert(false, ""); |
1814 | 0 | case PredicateType::Custom: |
1815 | | // Custom rules are handled separately. |
1816 | 0 | solAssert(false, ""); |
1817 | 366k | } |
1818 | 0 | solAssert(false, ""); |
1819 | 0 | } |
1820 | | |
1821 | | smtutil::Expression CHC::predicate( |
1822 | | FunctionDefinition const* _funDef, |
1823 | | std::optional<Expression const*> _boundArgumentCall, |
1824 | | FunctionType const* _funType, |
1825 | | std::vector<Expression const*> _arguments, |
1826 | | smtutil::Expression _contractAddressValue |
1827 | | ) |
1828 | 2.25k | { |
1829 | 2.25k | solAssert(_funType, ""); |
1830 | 2.25k | auto kind = _funType->kind(); |
1831 | 2.25k | solAssert(kind == FunctionType::Kind::Internal || kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, ""); |
1832 | 2.25k | if (!_funDef) |
1833 | 105 | return smtutil::Expression(true); |
1834 | | |
1835 | 2.15k | errorFlag().increaseIndex(); |
1836 | | |
1837 | 2.15k | std::vector<smtutil::Expression> args = |
1838 | 2.15k | commonStateExpressions(errorFlag().currentValue(), _contractAddressValue) + |
1839 | 2.15k | std::vector<smtutil::Expression>{state().tx(), state().state()}; |
1840 | | |
1841 | 2.15k | auto const* contract = _funDef->annotation().contract; |
1842 | 2.15k | auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; |
1843 | 2.15k | solAssert(kind != FunctionType::Kind::Internal || _funDef->isFree() || (contract && contract->isLibrary()) || util::contains(hierarchy, contract), ""); |
1844 | | |
1845 | 2.15k | if (kind == FunctionType::Kind::Internal) |
1846 | 1.73k | contract = m_currentContract; |
1847 | | |
1848 | 2.15k | args += currentStateVariables(*contract); |
1849 | 2.15k | args += symbolicArguments(_funDef->parameters(), _arguments, _boundArgumentCall); |
1850 | 2.15k | if (!usesStaticCall(_funDef, _funType)) |
1851 | 1.28k | { |
1852 | 1.28k | state().newState(); |
1853 | 1.28k | for (auto const& var: stateVariablesIncludingInheritedAndPrivate(*contract)) |
1854 | 652 | m_context.variable(*var)->increaseIndex(); |
1855 | 1.28k | } |
1856 | 2.15k | args += std::vector<smtutil::Expression>{state().state()}; |
1857 | 2.15k | args += currentStateVariables(*contract); |
1858 | | |
1859 | 2.15k | for (auto var: _funDef->parameters() + _funDef->returnParameters()) |
1860 | 2.99k | { |
1861 | 2.99k | if (m_context.knownVariable(*var)) |
1862 | 2.99k | m_context.variable(*var)->increaseIndex(); |
1863 | 0 | else |
1864 | 0 | createVariable(*var); |
1865 | 2.99k | args.push_back(currentValue(*var)); |
1866 | 2.99k | } |
1867 | | |
1868 | 2.15k | Predicate const& summary = *m_summaries.at(contract).at(_funDef); |
1869 | 2.15k | auto from = smt::function(summary, contract, m_context); |
1870 | 2.15k | Predicate const& callPredicate = *createSummaryBlock( |
1871 | 2.15k | *_funDef, |
1872 | 2.15k | *contract, |
1873 | 2.15k | kind == FunctionType::Kind::Internal ? PredicateType::InternalCall : PredicateType::ExternalCallTrusted |
1874 | 2.15k | ); |
1875 | 2.15k | auto to = smt::function(callPredicate, contract, m_context); |
1876 | 2.15k | addRule(smtutil::Expression::implies(from, to), to.name); |
1877 | | |
1878 | 2.15k | return callPredicate(args); |
1879 | 2.25k | } |
1880 | | |
1881 | | void CHC::addRule(smtutil::Expression const& _rule, std::string const& _ruleName) |
1882 | 336k | { |
1883 | 336k | m_interface->addRule(_rule, _ruleName); |
1884 | 336k | } |
1885 | | |
1886 | | CHCSolverInterface::QueryResult CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location) |
1887 | 18.5k | { |
1888 | 18.5k | if (m_settings.printQuery) |
1889 | 0 | { |
1890 | 0 | auto smtLibInterface = dynamic_cast<CHCSmtLib2Interface*>(m_interface.get()); |
1891 | 0 | solAssert(smtLibInterface, "Requested to print queries but CHCSmtLib2Interface not available"); |
1892 | 0 | std::string smtLibCode = smtLibInterface->dumpQuery(_query); |
1893 | 0 | m_errorReporter.info( |
1894 | 0 | 2339_error, |
1895 | 0 | "CHC: Requested query:\n" + smtLibCode |
1896 | 0 | ); |
1897 | 0 | return {.answer = CheckResult::UNKNOWN, .invariants = {}, .cex = {}}; |
1898 | 0 | } |
1899 | 18.5k | auto result = m_interface->query(_query); |
1900 | 18.5k | switch (result.answer) |
1901 | 18.5k | { |
1902 | 0 | case CheckResult::SATISFIABLE: |
1903 | 0 | case CheckResult::UNSATISFIABLE: |
1904 | 18.5k | case CheckResult::UNKNOWN: |
1905 | 18.5k | break; |
1906 | 0 | case CheckResult::CONFLICTING: |
1907 | 0 | m_errorReporter.warning(1988_error, _location, "CHC: At least two SMT solvers provided conflicting answers. Results might not be sound."); |
1908 | 0 | break; |
1909 | 0 | case CheckResult::ERROR: |
1910 | 0 | m_errorReporter.warning(1218_error, _location, "CHC: Error during interaction with the solver."); |
1911 | 0 | break; |
1912 | 18.5k | } |
1913 | 18.5k | return result; |
1914 | 18.5k | } |
1915 | | |
1916 | | void CHC::verificationTargetEncountered( |
1917 | | ASTNode const* const _errorNode, |
1918 | | VerificationTargetType _type, |
1919 | | smtutil::Expression const& _errorCondition |
1920 | | ) |
1921 | 28.4k | { |
1922 | 28.4k | if (!m_settings.targets.has(_type)) |
1923 | 9.92k | return; |
1924 | | |
1925 | 18.5k | if (!(m_currentContract || m_currentFunction)) |
1926 | 21 | return; |
1927 | | |
1928 | 18.5k | bool scopeIsFunction = m_currentFunction && !m_currentFunction->isConstructor(); |
1929 | 18.5k | auto errorId = newErrorId(); |
1930 | 18.5k | solAssert(m_verificationTargets.count(errorId) == 0, "Error ID is not unique!"); |
1931 | 18.5k | m_verificationTargets.emplace(errorId, CHCVerificationTarget{{_type, _errorCondition, smtutil::Expression(true)}, errorId, _errorNode}); |
1932 | 18.5k | if (scopeIsFunction) |
1933 | 17.3k | m_functionTargetIds[m_currentFunction].push_back(errorId); |
1934 | 1.16k | else |
1935 | 1.16k | m_functionTargetIds[m_currentContract].push_back(errorId); |
1936 | 18.5k | auto previousError = errorFlag().currentValue(); |
1937 | 18.5k | errorFlag().increaseIndex(); |
1938 | 18.5k | auto extendedErrorCondition = currentPathConditions() && _errorCondition; |
1939 | | |
1940 | 18.5k | Predicate const* localBlock = m_currentFunction ? |
1941 | 18.4k | createBlock(m_currentFunction, PredicateType::FunctionErrorBlock) : |
1942 | 18.5k | createConstructorBlock(*m_currentContract, "local_error"); |
1943 | | |
1944 | 18.5k | auto pred = predicate(*localBlock); |
1945 | 18.5k | connectBlocks( |
1946 | 18.5k | m_currentBlock, |
1947 | 18.5k | pred, |
1948 | 18.5k | extendedErrorCondition && errorFlag().currentValue() == errorId |
1949 | 18.5k | ); |
1950 | 18.5k | solAssert(m_errorDest, ""); |
1951 | 18.5k | addRule(smtutil::Expression::implies(pred, predicate(*m_errorDest)), pred.name); |
1952 | | |
1953 | 18.5k | m_context.addAssertion(errorFlag().currentValue() == previousError); |
1954 | 18.5k | } |
1955 | | |
1956 | | std::pair<std::string, ErrorId> CHC::targetDescription(CHCVerificationTarget const& _target) |
1957 | 18.5k | { |
1958 | 18.5k | if (_target.type == VerificationTargetType::PopEmptyArray) |
1959 | 218 | { |
1960 | 218 | solAssert(dynamic_cast<FunctionCall const*>(_target.errorNode), ""); |
1961 | 218 | return {"Empty array \"pop\"", 2529_error}; |
1962 | 218 | } |
1963 | 18.2k | else if (_target.type == VerificationTargetType::OutOfBounds) |
1964 | 14.8k | { |
1965 | 14.8k | solAssert(dynamic_cast<IndexAccess const*>(_target.errorNode), ""); |
1966 | 14.8k | return {"Out of bounds access", 6368_error}; |
1967 | 14.8k | } |
1968 | 3.43k | else if ( |
1969 | 3.43k | _target.type == VerificationTargetType::Underflow || |
1970 | 3.43k | _target.type == VerificationTargetType::Overflow |
1971 | 3.43k | ) |
1972 | 0 | { |
1973 | 0 | auto const* expr = dynamic_cast<Expression const*>(_target.errorNode); |
1974 | 0 | solAssert(expr, ""); |
1975 | 0 | auto const* intType = dynamic_cast<IntegerType const*>(expr->annotation().type); |
1976 | 0 | if (!intType) |
1977 | 0 | intType = TypeProvider::uint256(); |
1978 | |
|
1979 | 0 | if (_target.type == VerificationTargetType::Underflow) |
1980 | 0 | return { |
1981 | 0 | "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")", |
1982 | 0 | 3944_error |
1983 | 0 | }; |
1984 | | |
1985 | 0 | return { |
1986 | 0 | "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")", |
1987 | 0 | 4984_error |
1988 | 0 | }; |
1989 | 0 | } |
1990 | 3.43k | else if (_target.type == VerificationTargetType::DivByZero) |
1991 | 894 | return {"Division by zero", 4281_error}; |
1992 | 2.54k | else if (_target.type == VerificationTargetType::Assert) |
1993 | 2.45k | return {"Assertion violation", 6328_error}; |
1994 | 84 | else if (_target.type == VerificationTargetType::Balance) |
1995 | 84 | return {"Insufficient funds", 8656_error}; |
1996 | 0 | else |
1997 | 84 | solAssert(false); |
1998 | 18.5k | } |
1999 | | |
2000 | | void CHC::checkVerificationTargets() |
2001 | 15.5k | { |
2002 | | // The verification conditions have been collected per function where they have been encountered (m_verificationTargets). |
2003 | | // Also, all possible contexts in which an external function can be called has been recorded (m_queryPlaceholders). |
2004 | | // Here we combine every context in which an external function can be called with all possible verification conditions |
2005 | | // in its call graph. Each such combination forms a unique verification target. |
2006 | 15.5k | std::map<unsigned, std::vector<CHCQueryPlaceholder>> targetEntryPoints; |
2007 | 15.5k | for (auto const& [function, placeholders]: m_queryPlaceholders) |
2008 | 28.6k | { |
2009 | 28.6k | auto functionTargets = transactionVerificationTargetsIds(function); |
2010 | 28.6k | for (auto const& placeholder: placeholders) |
2011 | 29.0k | for (unsigned id: functionTargets) |
2012 | 18.6k | targetEntryPoints[id].push_back(placeholder); |
2013 | 28.6k | } |
2014 | | |
2015 | 15.5k | std::set<unsigned> checkedErrorIds; |
2016 | 15.5k | for (auto const& [targetId, placeholders]: targetEntryPoints) |
2017 | 18.5k | { |
2018 | 18.5k | auto const& target = m_verificationTargets.at(targetId); |
2019 | 18.5k | auto [errorType, errorReporterId] = targetDescription(target); |
2020 | | |
2021 | 18.5k | checkAndReportTarget(target, placeholders, errorReporterId, errorType + " happens here.", errorType + " might happen here."); |
2022 | 18.5k | checkedErrorIds.insert(target.errorId); |
2023 | 18.5k | } |
2024 | | |
2025 | 15.5k | auto toReport = m_unsafeTargets; |
2026 | 15.5k | if (m_settings.showUnproved) |
2027 | 0 | for (auto const& [node, targets]: m_unprovedTargets) |
2028 | 0 | for (auto const& [target, info]: targets) |
2029 | 0 | toReport[node].emplace(target, info); |
2030 | | |
2031 | 15.5k | for (auto const& [node, targets]: toReport) |
2032 | 0 | for (auto const& [target, info]: targets) |
2033 | 0 | m_errorReporter.warning( |
2034 | 0 | info.error, |
2035 | 0 | info.location, |
2036 | 0 | info.message |
2037 | 0 | ); |
2038 | | |
2039 | 15.5k | if (!m_settings.showUnproved && !m_unprovedTargets.empty()) |
2040 | 3.74k | m_errorReporter.warning( |
2041 | 3.74k | 5840_error, |
2042 | 3.74k | {}, |
2043 | 3.74k | "CHC: " + |
2044 | 3.74k | std::to_string(m_unprovedTargets.size()) + |
2045 | 3.74k | " verification condition(s) could not be proved." + |
2046 | 3.74k | " Enable the model checker option \"show unproved\" to see all of them." + |
2047 | 3.74k | " Consider choosing a specific contract to be verified in order to reduce the solving problems." + |
2048 | 3.74k | " Consider increasing the timeout per query." |
2049 | 3.74k | ); |
2050 | | |
2051 | 15.5k | if (!m_settings.showProvedSafe && !m_safeTargets.empty()) |
2052 | 0 | { |
2053 | 0 | std::size_t provedSafeNum = 0; |
2054 | 0 | for (auto&& [_, targets]: m_safeTargets) |
2055 | 0 | provedSafeNum += targets.size(); |
2056 | 0 | m_errorReporter.info( |
2057 | 0 | 1391_error, |
2058 | 0 | "CHC: " + |
2059 | 0 | std::to_string(provedSafeNum) + |
2060 | 0 | " verification condition(s) proved safe!" + |
2061 | 0 | " Enable the model checker option \"show proved safe\" to see all of them." |
2062 | 0 | ); |
2063 | 0 | } |
2064 | 15.5k | else if (m_settings.showProvedSafe) |
2065 | 0 | for (auto const& [node, targets]: m_safeTargets) |
2066 | 0 | for (auto const& target: targets) |
2067 | 0 | m_provedSafeReporter.info( |
2068 | 0 | 9576_error, |
2069 | 0 | node->location(), |
2070 | 0 | "CHC: " + |
2071 | 0 | targetDescription(target).first + |
2072 | 0 | " check is safe!" |
2073 | 0 | ); |
2074 | | |
2075 | 15.5k | if (!m_settings.invariants.invariants.empty()) |
2076 | 15.5k | { |
2077 | 15.5k | std::string msg; |
2078 | 15.5k | for (auto pred: m_invariants | ranges::views::keys) |
2079 | 0 | { |
2080 | 0 | ASTNode const* node = pred->programNode(); |
2081 | 0 | std::string what; |
2082 | 0 | if (auto contract = dynamic_cast<ContractDefinition const*>(node)) |
2083 | 0 | what = contract->fullyQualifiedName(); |
2084 | 0 | else |
2085 | 0 | solAssert(false, ""); |
2086 | |
|
2087 | 0 | std::string invType; |
2088 | 0 | if (pred->type() == PredicateType::Interface) |
2089 | 0 | invType = "Contract invariant(s)"; |
2090 | 0 | else if (pred->type() == PredicateType::NondetInterface) |
2091 | 0 | invType = "Reentrancy property(ies)"; |
2092 | 0 | else |
2093 | 0 | solAssert(false, ""); |
2094 | |
|
2095 | 0 | msg += invType + " for " + what + ":\n"; |
2096 | 0 | for (auto const& inv: m_invariants.at(pred)) |
2097 | 0 | msg += inv + "\n"; |
2098 | 0 | } |
2099 | 15.5k | if (msg.find("<errorCode>") != std::string::npos) |
2100 | 0 | { |
2101 | 0 | std::set<unsigned> seenErrors; |
2102 | 0 | msg += "<errorCode> = 0 -> no errors\n"; |
2103 | 0 | for (auto const& [id, target]: m_verificationTargets) |
2104 | 0 | if (!seenErrors.count(target.errorId)) |
2105 | 0 | { |
2106 | 0 | seenErrors.insert(target.errorId); |
2107 | 0 | std::string loc = std::string(m_charStreamProvider.charStream(*target.errorNode->location().sourceName).text(target.errorNode->location())); |
2108 | 0 | msg += "<errorCode> = " + std::to_string(target.errorId) + " -> " + ModelCheckerTargets::targetTypeToString.at(target.type) + " at " + loc + "\n"; |
2109 | |
|
2110 | 0 | } |
2111 | 0 | } |
2112 | 15.5k | if (!msg.empty()) |
2113 | 0 | m_errorReporter.info(1180_error, msg); |
2114 | 15.5k | } |
2115 | | |
2116 | | // There can be targets in internal functions that are not reachable from the external interface. |
2117 | | // These are safe by definition and are not even checked by the CHC engine, but this information |
2118 | | // must still be reported safe by the BMC engine. |
2119 | 15.5k | std::set<unsigned> allErrorIds; |
2120 | 15.5k | for (auto const& entry: m_functionTargetIds) |
2121 | 29.5k | for (unsigned id: entry.second) |
2122 | 18.5k | allErrorIds.insert(id); |
2123 | | |
2124 | 15.5k | std::set<unsigned> unreachableErrorIds; |
2125 | 15.5k | set_difference( |
2126 | 15.5k | allErrorIds.begin(), |
2127 | 15.5k | allErrorIds.end(), |
2128 | 15.5k | checkedErrorIds.begin(), |
2129 | 15.5k | checkedErrorIds.end(), |
2130 | 15.5k | inserter(unreachableErrorIds, unreachableErrorIds.begin()) |
2131 | 15.5k | ); |
2132 | 15.5k | for (auto id: unreachableErrorIds) |
2133 | 34 | m_safeTargets[m_verificationTargets.at(id).errorNode].insert(m_verificationTargets.at(id)); |
2134 | 15.5k | } |
2135 | | |
2136 | | namespace |
2137 | | { |
2138 | | std::map<Predicate const*, std::set<std::string>> collectInvariants( |
2139 | | CHCSmtLib2Interface::Invariants const& _invariants, |
2140 | | std::set<Predicate const*> const& _predicates, |
2141 | | ModelCheckerInvariants const& _invariantsSetting |
2142 | | ) |
2143 | 0 | { |
2144 | 0 | std::set<std::string> targets; |
2145 | 0 | if (_invariantsSetting.has(InvariantType::Contract)) |
2146 | 0 | targets.insert("interface_"); |
2147 | 0 | if (_invariantsSetting.has(InvariantType::Reentrancy)) |
2148 | 0 | targets.insert("nondet_interface_"); |
2149 | |
|
2150 | 0 | std::map<Predicate const*, std::set<std::string>> invariants; |
2151 | 0 | for (auto const* pred: _predicates) |
2152 | 0 | { |
2153 | 0 | smtAssert(pred); |
2154 | 0 | auto const& predName = pred->functor().name; |
2155 | 0 | if (!_invariants.contains(predName)) |
2156 | 0 | continue; |
2157 | 0 | if (ranges::none_of(targets, [&](auto const& _target) { return predName.starts_with(_target); })) |
2158 | 0 | continue; |
2159 | | |
2160 | 0 | smtAssert(pred->contextContract()); |
2161 | |
|
2162 | 0 | auto const& [definition, formalArguments] = _invariants.at(predName); |
2163 | |
|
2164 | 0 | auto r = substitute(definition, pred->expressionSubstitution(formalArguments)); |
2165 | | // No point in reporting true/false as invariants. |
2166 | 0 | if (r.name != "true" && r.name != "false") |
2167 | 0 | invariants[pred].insert(toSolidityStr(r)); |
2168 | 0 | } |
2169 | 0 | return invariants; |
2170 | 0 | } |
2171 | | } // namespace |
2172 | | |
2173 | | void CHC::checkAndReportTarget( |
2174 | | CHCVerificationTarget const& _target, |
2175 | | std::vector<CHCQueryPlaceholder> const& _placeholders, |
2176 | | ErrorId _errorReporterId, |
2177 | | std::string _satMsg, |
2178 | | std::string _unknownMsg |
2179 | | ) |
2180 | 18.5k | { |
2181 | 18.5k | if (m_unsafeTargets.count(_target.errorNode) && m_unsafeTargets.at(_target.errorNode).count(_target.type)) |
2182 | 0 | return; |
2183 | | |
2184 | 18.5k | createErrorBlock(); |
2185 | 18.5k | for (auto const& placeholder: _placeholders) |
2186 | 18.6k | connectBlocks( |
2187 | 18.6k | placeholder.fromPredicate, |
2188 | 18.6k | error(), |
2189 | 18.6k | placeholder.constraints && placeholder.errorExpression == _target.errorId |
2190 | 18.6k | ); |
2191 | 18.5k | auto const& location = _target.errorNode->location(); |
2192 | 18.5k | auto [result, invariants, model] = query(error(), location); |
2193 | 18.5k | if (result == CheckResult::UNSATISFIABLE) |
2194 | 0 | { |
2195 | 0 | m_safeTargets[_target.errorNode].insert(_target); |
2196 | 0 | std::set<Predicate const*> predicates; |
2197 | 0 | for (auto const* pred: m_interfaces | ranges::views::values) |
2198 | 0 | predicates.insert(pred); |
2199 | 0 | for (auto const* pred: m_nondetInterfaces | ranges::views::values) |
2200 | 0 | predicates.insert(pred); |
2201 | 0 | std::map<Predicate const*, std::set<std::string>> invariantStrings = collectInvariants(invariants, predicates, m_settings.invariants); |
2202 | 0 | for (auto pred: invariantStrings | ranges::views::keys) |
2203 | 0 | m_invariants[pred] += std::move(invariantStrings.at(pred)); |
2204 | 0 | } |
2205 | 18.5k | else if (result == CheckResult::SATISFIABLE) |
2206 | 0 | { |
2207 | 0 | solAssert(!_satMsg.empty()); |
2208 | 0 | if (auto it = m_safeTargets.find(_target.errorNode); it != m_safeTargets.end()) |
2209 | 0 | { |
2210 | 0 | std::erase_if(it->second, [&](auto const& target) { return target.type == _target.type; }); |
2211 | 0 | if (it->second.empty()) |
2212 | 0 | m_safeTargets.erase(it); |
2213 | 0 | } |
2214 | 0 | auto cex = generateCounterexample(model, error().name); |
2215 | 0 | if (cex) |
2216 | 0 | m_unsafeTargets[_target.errorNode][_target.type] = { |
2217 | 0 | _errorReporterId, |
2218 | 0 | location, |
2219 | 0 | "CHC: " + _satMsg + "\nCounterexample:\n" + *cex |
2220 | 0 | }; |
2221 | 0 | else |
2222 | 0 | m_unsafeTargets[_target.errorNode][_target.type] = { |
2223 | 0 | _errorReporterId, |
2224 | 0 | location, |
2225 | 0 | "CHC: " + _satMsg |
2226 | 0 | }; |
2227 | 0 | } |
2228 | 18.5k | else if (!_unknownMsg.empty()) |
2229 | 18.5k | m_unprovedTargets[_target.errorNode][_target.type] = { |
2230 | 18.5k | _errorReporterId, |
2231 | 18.5k | location, |
2232 | 18.5k | "CHC: " + _unknownMsg |
2233 | 18.5k | }; |
2234 | 18.5k | } |
2235 | | |
2236 | | /** |
2237 | | The counterexample DAG has the following properties: |
2238 | | 1) The root node represents the reachable error predicate. |
2239 | | 2) The root node has 1 or 2 children: |
2240 | | - One of them is the summary of the function that was called and led to that node. |
2241 | | If this is the only child, this function must be the constructor. |
2242 | | - If it has 2 children, the function is not the constructor and the other child is the interface node, |
2243 | | that is, it represents the state of the contract before the function described above was called. |
2244 | | 3) Interface nodes also have property 2. |
2245 | | |
2246 | | We run a BFS on the DAG from the root node collecting the reachable function summaries from the given node. |
2247 | | When a function summary is seen, the search continues with that summary as the new root for its subgraph. |
2248 | | The result of the search is a callgraph containing: |
2249 | | - Functions calls needed to reach the root node, that is, transaction entry points. |
2250 | | - Functions called by other functions (internal calls or external calls/internal transactions). |
2251 | | The BFS visit order and the shape of the DAG described in the previous paragraph guarantee that the order of |
2252 | | the function summaries in the callgraph of the error node is the reverse transaction trace. |
2253 | | |
2254 | | The first function summary seen contains the values for the state, input and output variables at the |
2255 | | error point. |
2256 | | */ |
2257 | | std::optional<std::string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const& _graph, std::string const& _root) |
2258 | 0 | { |
2259 | 0 | std::optional<unsigned> rootId; |
2260 | 0 | for (auto const& [id, node]: _graph.nodes) |
2261 | 0 | if (node.name == _root) |
2262 | 0 | { |
2263 | 0 | rootId = id; |
2264 | 0 | break; |
2265 | 0 | } |
2266 | 0 | if (!rootId) |
2267 | 0 | return {}; |
2268 | | |
2269 | 0 | std::vector<std::string> path; |
2270 | 0 | std::string localState; |
2271 | |
|
2272 | 0 | auto callGraph = summaryCalls(_graph, *rootId); |
2273 | |
|
2274 | 0 | auto nodePred = [&](auto _node) { return Predicate::predicate(_graph.nodes.at(_node).name); }; |
2275 | 0 | auto nodeArgs = [&](auto _node) { return _graph.nodes.at(_node).arguments; }; |
2276 | |
|
2277 | 0 | bool first = true; |
2278 | 0 | for (auto summaryId: callGraph.at(*rootId)) |
2279 | 0 | { |
2280 | 0 | CHCSolverInterface::CexNode const& summaryNode = _graph.nodes.at(summaryId); |
2281 | 0 | Predicate const* summaryPredicate = Predicate::predicate(summaryNode.name); |
2282 | 0 | auto const& summaryArgs = summaryNode.arguments; |
2283 | |
|
2284 | 0 | if (!summaryPredicate->programVariable()) |
2285 | 0 | { |
2286 | 0 | auto stateVars = summaryPredicate->stateVariables(); |
2287 | 0 | solAssert(stateVars.has_value(), ""); |
2288 | 0 | auto stateValues = summaryPredicate->summaryStateValues(summaryArgs); |
2289 | 0 | solAssert(stateValues.size() == stateVars->size(), ""); |
2290 | |
|
2291 | 0 | if (first) |
2292 | 0 | { |
2293 | 0 | first = false; |
2294 | | /// Generate counterexample message local to the failed target. |
2295 | 0 | localState = formatVariableModel(*stateVars, stateValues, ", ") + "\n"; |
2296 | |
|
2297 | 0 | if (auto calledFun = summaryPredicate->programFunction()) |
2298 | 0 | { |
2299 | 0 | auto inValues = summaryPredicate->summaryPostInputValues(summaryArgs); |
2300 | 0 | auto const& inParams = calledFun->parameters(); |
2301 | 0 | if (auto inStr = formatVariableModel(inParams, inValues, "\n"); !inStr.empty()) |
2302 | 0 | localState += inStr + "\n"; |
2303 | 0 | auto outValues = summaryPredicate->summaryPostOutputValues(summaryArgs); |
2304 | 0 | auto const& outParams = calledFun->returnParameters(); |
2305 | 0 | if (auto outStr = formatVariableModel(outParams, outValues, "\n"); !outStr.empty()) |
2306 | 0 | localState += outStr + "\n"; |
2307 | |
|
2308 | 0 | std::optional<unsigned> localErrorId; |
2309 | 0 | solidity::util::BreadthFirstSearch<unsigned> bfs{{summaryId}}; |
2310 | 0 | bfs.run([&](auto _nodeId, auto&& _addChild) { |
2311 | 0 | auto const& children = _graph.edges.at(_nodeId); |
2312 | 0 | if ( |
2313 | 0 | children.size() == 1 && |
2314 | 0 | nodePred(children.front())->isFunctionErrorBlock() |
2315 | 0 | ) |
2316 | 0 | { |
2317 | 0 | localErrorId = children.front(); |
2318 | 0 | bfs.abort(); |
2319 | 0 | } |
2320 | 0 | ranges::for_each(children, _addChild); |
2321 | 0 | }); |
2322 | |
|
2323 | 0 | if (localErrorId.has_value()) |
2324 | 0 | { |
2325 | 0 | auto const* localError = nodePred(*localErrorId); |
2326 | 0 | solAssert(localError && localError->isFunctionErrorBlock(), ""); |
2327 | 0 | auto const [localValues, localVars] = localError->localVariableValues(nodeArgs(*localErrorId)); |
2328 | 0 | if (auto localStr = formatVariableModel(localVars, localValues, "\n"); !localStr.empty()) |
2329 | 0 | localState += localStr + "\n"; |
2330 | 0 | } |
2331 | 0 | } |
2332 | 0 | } |
2333 | 0 | else |
2334 | 0 | { |
2335 | 0 | auto modelMsg = formatVariableModel(*stateVars, stateValues, ", "); |
2336 | | /// We report the state after every tx in the trace except for the last, which is reported |
2337 | | /// first in the code above. |
2338 | 0 | if (!modelMsg.empty()) |
2339 | 0 | path.emplace_back("State: " + modelMsg); |
2340 | 0 | } |
2341 | 0 | } |
2342 | 0 | std::string txCex = summaryPredicate->formatSummaryCall(summaryArgs, m_charStreamProvider); |
2343 | |
|
2344 | 0 | std::list<std::string> calls; |
2345 | 0 | auto dfs = [&](unsigned parent, unsigned node, unsigned depth, auto&& _dfs) -> void { |
2346 | 0 | auto pred = nodePred(node); |
2347 | 0 | auto parentPred = nodePred(parent); |
2348 | 0 | solAssert(pred && pred->isSummary(), ""); |
2349 | 0 | solAssert(parentPred && parentPred->isSummary(), ""); |
2350 | 0 | auto callTraceSize = calls.size(); |
2351 | 0 | if (!pred->isConstructorSummary()) |
2352 | 0 | for (unsigned v: callGraph[node]) |
2353 | 0 | _dfs(node, v, depth + 1, _dfs); |
2354 | |
|
2355 | 0 | bool appendTxVars = pred->isConstructorSummary() || pred->isFunctionSummary() || pred->isExternalCallUntrusted(); |
2356 | |
|
2357 | 0 | calls.push_front(std::string(depth * 4, ' ') + pred->formatSummaryCall(nodeArgs(node), m_charStreamProvider, appendTxVars)); |
2358 | 0 | if (pred->isInternalCall()) |
2359 | 0 | calls.front() += " -- internal call"; |
2360 | 0 | else if (pred->isExternalCallTrusted()) |
2361 | 0 | calls.front() += " -- trusted external call"; |
2362 | 0 | else if (pred->isExternalCallUntrusted()) |
2363 | 0 | { |
2364 | 0 | calls.front() += " -- untrusted external call"; |
2365 | 0 | if (calls.size() > callTraceSize + 1) |
2366 | 0 | calls.front() += ", synthesized as:"; |
2367 | 0 | } |
2368 | 0 | else if (pred->programVariable()) |
2369 | 0 | { |
2370 | 0 | calls.front() += "-- action on external contract in state variable \"" + pred->programVariable()->name() + "\""; |
2371 | 0 | if (calls.size() > callTraceSize + 1) |
2372 | 0 | calls.front() += ", synthesized as:"; |
2373 | 0 | } |
2374 | 0 | else if (pred->isFunctionSummary() && parentPred->isExternalCallUntrusted()) |
2375 | 0 | calls.front() += " -- reentrant call"; |
2376 | 0 | }; |
2377 | 0 | dfs(summaryId, summaryId, 0, dfs); |
2378 | 0 | path.emplace_back(boost::algorithm::join(calls, "\n")); |
2379 | 0 | } |
2380 | |
|
2381 | 0 | return localState + "\nTransaction trace:\n" + boost::algorithm::join(path | ranges::views::reverse, "\n"); |
2382 | 0 | } |
2383 | | |
2384 | | std::map<unsigned, std::vector<unsigned>> CHC::summaryCalls(CHCSolverInterface::CexGraph const& _graph, unsigned _root) |
2385 | 0 | { |
2386 | 0 | std::map<unsigned, std::vector<unsigned>> calls; |
2387 | |
|
2388 | 0 | auto compare = [&](unsigned _a, unsigned _b) { |
2389 | 0 | auto extract = [&](std::string const& _s) { |
2390 | | // We want to sort sibling predicates in the counterexample graph by their unique predicate id. |
2391 | | // For most predicates, this actually doesn't matter. |
2392 | | // The cases where this matters are internal and external function calls which have the form: |
2393 | | // summary_<CALLID>_<suffix> |
2394 | | // nondet_call_<CALLID>_<suffix> |
2395 | | // Those have the extra unique <CALLID> numbers based on the traversal order, and are necessary |
2396 | | // to infer the call order so that's shown property in the counterexample trace. |
2397 | | // For other predicates, we do not care. |
2398 | 0 | auto beg = _s.data(); |
2399 | 0 | while (beg != _s.data() + _s.size() && !isDigit(*beg)) ++beg; |
2400 | 0 | int result = -1; |
2401 | 0 | static_cast<void>(std::from_chars(beg, _s.data() + _s.size(), result)); |
2402 | 0 | return result; |
2403 | 0 | }; |
2404 | 0 | auto anum = extract(_graph.nodes.at(_a).name); |
2405 | 0 | auto bnum = extract(_graph.nodes.at(_b).name); |
2406 | | // The second part of the condition is needed to ensure that two different predicates are not considered equal |
2407 | 0 | return (anum > bnum) || (anum == bnum && _graph.nodes.at(_a).name > _graph.nodes.at(_b).name); |
2408 | 0 | }; |
2409 | |
|
2410 | 0 | std::queue<std::pair<unsigned, unsigned>> q; |
2411 | 0 | q.push({_root, _root}); |
2412 | 0 | while (!q.empty()) |
2413 | 0 | { |
2414 | 0 | auto [node, root] = q.front(); |
2415 | 0 | q.pop(); |
2416 | |
|
2417 | 0 | Predicate const* nodePred = Predicate::predicate(_graph.nodes.at(node).name); |
2418 | 0 | Predicate const* rootPred = Predicate::predicate(_graph.nodes.at(root).name); |
2419 | 0 | if (nodePred->isSummary() && ( |
2420 | 0 | _root == root || |
2421 | 0 | nodePred->isInternalCall() || |
2422 | 0 | nodePred->isExternalCallTrusted() || |
2423 | 0 | nodePred->isExternalCallUntrusted() || |
2424 | 0 | rootPred->isExternalCallUntrusted() || |
2425 | 0 | rootPred->programVariable() |
2426 | 0 | )) |
2427 | 0 | { |
2428 | 0 | calls[root].push_back(node); |
2429 | 0 | root = node; |
2430 | 0 | } |
2431 | 0 | auto const& edges = _graph.edges.at(node); |
2432 | 0 | for (unsigned v: std::set<unsigned, decltype(compare)>(begin(edges), end(edges), compare)) |
2433 | 0 | q.push({v, root}); |
2434 | 0 | } |
2435 | |
|
2436 | 0 | return calls; |
2437 | 0 | } |
2438 | | |
2439 | | std::string CHC::cex2dot(CHCSolverInterface::CexGraph const& _cex) |
2440 | 0 | { |
2441 | 0 | std::string dot = "digraph {\n"; |
2442 | |
|
2443 | 0 | auto pred = [&](CHCSolverInterface::CexNode const& _node) { |
2444 | 0 | std::vector<std::string> args = applyMap( |
2445 | 0 | _node.arguments, |
2446 | 0 | [&](auto const& arg) { return arg.name; } |
2447 | 0 | ); |
2448 | 0 | return "\"" + _node.name + "(" + boost::algorithm::join(args, ", ") + ")\""; |
2449 | 0 | }; |
2450 | |
|
2451 | 0 | for (auto const& [u, vs]: _cex.edges) |
2452 | 0 | for (auto v: vs) |
2453 | 0 | dot += pred(_cex.nodes.at(v)) + " -> " + pred(_cex.nodes.at(u)) + "\n"; |
2454 | |
|
2455 | 0 | dot += "}"; |
2456 | 0 | return dot; |
2457 | 0 | } |
2458 | | |
2459 | | std::string CHC::uniquePrefix() |
2460 | 250k | { |
2461 | 250k | return std::to_string(m_blockCounter++); |
2462 | 250k | } |
2463 | | |
2464 | | std::string CHC::contractSuffix(ContractDefinition const& _contract) |
2465 | 87.2k | { |
2466 | 87.2k | return _contract.name() + "_" + std::to_string(_contract.id()); |
2467 | 87.2k | } |
2468 | | |
2469 | | unsigned CHC::newErrorId() |
2470 | 18.5k | { |
2471 | 18.5k | unsigned errorId = m_context.newUniqueId(); |
2472 | | // We need to make sure the error id is not zero, |
2473 | | // because error id zero actually means no error in the CHC encoding. |
2474 | 18.5k | if (errorId == 0) |
2475 | 3.58k | errorId = m_context.newUniqueId(); |
2476 | 18.5k | return errorId; |
2477 | 18.5k | } |
2478 | | |
2479 | | SymbolicIntVariable& CHC::errorFlag() |
2480 | 342k | { |
2481 | 342k | return state().errorFlag(); |
2482 | 342k | } |
2483 | | |
2484 | | void CHC::newTxConstraints(Expression const* _value) |
2485 | 415 | { |
2486 | 415 | auto txOrigin = state().txMember("tx.origin"); |
2487 | 415 | state().newTx(); |
2488 | | // set the transaction sender as this contract |
2489 | 415 | m_context.addAssertion(state().txMember("msg.sender") == state().thisAddress()); |
2490 | | // set the origin to be the current transaction origin |
2491 | 415 | m_context.addAssertion(state().txMember("tx.origin") == txOrigin); |
2492 | | |
2493 | 415 | if (_value) |
2494 | | // set the msg value |
2495 | 7 | m_context.addAssertion(state().txMember("msg.value") == expr(*_value)); |
2496 | 415 | } |
2497 | | |
2498 | | frontend::Expression const* CHC::valueOption(FunctionCallOptions const* _options) |
2499 | 907 | { |
2500 | 907 | if (_options) |
2501 | 70 | for (auto&& [i, name]: _options->names() | ranges::views::enumerate) |
2502 | 74 | if (name && *name == "value") |
2503 | 34 | return _options->options().at(i).get(); |
2504 | 873 | return nullptr; |
2505 | 907 | } |
2506 | | |
2507 | | void CHC::decreaseBalanceFromOptionsValue(Expression const& _value) |
2508 | 34 | { |
2509 | 34 | state().addBalance(state().thisAddress(), 0 - expr(_value)); |
2510 | 34 | } |
2511 | | |
2512 | | std::vector<smtutil::Expression> CHC::commonStateExpressions(smtutil::Expression const& error, smtutil::Expression const& address) |
2513 | 15.9k | { |
2514 | 15.9k | if (state().hasBytesConcatFunction()) |
2515 | 90 | return {error, address, state().abi(), state().bytesConcat(), state().crypto()}; |
2516 | 15.8k | return {error, address, state().abi(), state().crypto()}; |
2517 | 15.9k | } |