/src/solidity/libsolidity/formal/ModelChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | |
19 | | #include <libsolidity/formal/ModelChecker.h> |
20 | | |
21 | | #include <boost/version.hpp> |
22 | | #if (BOOST_VERSION < 108800) |
23 | | #include <boost/process.hpp> |
24 | | #else |
25 | | #define BOOST_PROCESS_VERSION 1 |
26 | | #include <boost/process/v1/search_path.hpp> |
27 | | #endif |
28 | | |
29 | | #include <range/v3/algorithm/any_of.hpp> |
30 | | #include <range/v3/view.hpp> |
31 | | |
32 | | using namespace solidity; |
33 | | using namespace solidity::util; |
34 | | using namespace solidity::langutil; |
35 | | using namespace solidity::frontend; |
36 | | using namespace solidity::smtutil; |
37 | | |
38 | | ModelChecker::ModelChecker( |
39 | | ErrorReporter& _errorReporter, |
40 | | langutil::CharStreamProvider const& _charStreamProvider, |
41 | | std::map<h256, std::string> const& _smtlib2Responses, |
42 | | ModelCheckerSettings _settings, |
43 | | ReadCallback::Callback const& _smtCallback |
44 | | ): |
45 | | m_errorReporter(_errorReporter), |
46 | | m_provedSafeReporter(m_provedSafeLogs), |
47 | | m_settings(std::move(_settings)), |
48 | | m_context(), |
49 | | m_bmc(m_context, m_uniqueErrorReporter, m_unsupportedErrorReporter, m_provedSafeReporter, _smtlib2Responses, _smtCallback, m_settings, _charStreamProvider), |
50 | | m_chc(m_context, m_uniqueErrorReporter, m_unsupportedErrorReporter, m_provedSafeReporter, _smtlib2Responses, _smtCallback, m_settings, _charStreamProvider) |
51 | 15.2k | { |
52 | 15.2k | } |
53 | | |
54 | | // TODO This should be removed for 0.9.0. |
55 | | bool ModelChecker::isPragmaPresent(std::vector<std::shared_ptr<SourceUnit>> const& _sources) |
56 | 15.2k | { |
57 | 15.2k | return ranges::any_of(_sources, [](auto _source) { |
58 | 15.2k | return _source && _source->annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker); |
59 | 15.2k | }); |
60 | 15.2k | } |
61 | | |
62 | | void ModelChecker::checkRequestedSourcesAndContracts(std::vector<std::shared_ptr<SourceUnit>> const& _sources) |
63 | 15.2k | { |
64 | 15.2k | std::map<std::string, std::set<std::string>> exist; |
65 | 15.2k | for (auto const& source: _sources) |
66 | 16.7k | for (auto node: source->nodes()) |
67 | 40.6k | if (auto contract = std::dynamic_pointer_cast<ContractDefinition>(node)) |
68 | 17.7k | exist[contract->sourceUnitName()].insert(contract->name()); |
69 | | |
70 | | // Requested sources |
71 | 15.2k | for (auto const& sourceName: m_settings.contracts.contracts | ranges::views::keys) |
72 | 0 | { |
73 | 0 | if (!exist.count(sourceName)) |
74 | 0 | { |
75 | 0 | m_uniqueErrorReporter.warning( |
76 | 0 | 9134_error, |
77 | 0 | SourceLocation(), |
78 | 0 | "Requested source \"" + sourceName + "\" does not exist." |
79 | 0 | ); |
80 | 0 | continue; |
81 | 0 | } |
82 | 0 | auto const& source = exist.at(sourceName); |
83 | | // Requested contracts in source `s`. |
84 | 0 | for (auto const& contract: m_settings.contracts.contracts.at(sourceName)) |
85 | 0 | if (!source.count(contract)) |
86 | 0 | m_uniqueErrorReporter.warning( |
87 | 0 | 7400_error, |
88 | 0 | SourceLocation(), |
89 | 0 | "Requested contract \"" + contract + "\" does not exist in source \"" + sourceName + "\"." |
90 | 0 | ); |
91 | 0 | } |
92 | 15.2k | } |
93 | | |
94 | | void ModelChecker::analyze(SourceUnit const& _source) |
95 | 16.7k | { |
96 | | // TODO This should be removed for 0.9.0. |
97 | 16.7k | if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) |
98 | 16.1k | { |
99 | 16.1k | PragmaDirective const* smtPragma = nullptr; |
100 | 16.1k | for (auto node: _source.nodes()) |
101 | 39.0k | if (auto pragma = std::dynamic_pointer_cast<PragmaDirective>(node)) |
102 | 19.2k | if ( |
103 | 19.2k | pragma->literals().size() >= 2 && |
104 | 19.2k | pragma->literals().at(1) == "SMTChecker" |
105 | 19.2k | ) |
106 | 16.1k | { |
107 | 16.1k | smtPragma = pragma.get(); |
108 | 16.1k | break; |
109 | 16.1k | } |
110 | 16.1k | solAssert(smtPragma, ""); |
111 | 16.1k | m_uniqueErrorReporter.warning( |
112 | 16.1k | 5523_error, |
113 | 16.1k | smtPragma->location(), |
114 | 16.1k | "The SMTChecker pragma has been deprecated and will be removed in the future. " |
115 | 16.1k | "Please use the \"model checker engine\" compiler setting to activate the SMTChecker instead. " |
116 | 16.1k | "If the pragma is enabled, all engines will be used." |
117 | 16.1k | ); |
118 | 16.1k | } |
119 | | |
120 | 16.7k | if (m_settings.engine.none()) |
121 | 590 | return; |
122 | | |
123 | 16.2k | if (m_settings.engine.chc) |
124 | 16.2k | m_chc.analyze(_source); |
125 | | |
126 | 16.2k | std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> solvedTargets; |
127 | | |
128 | 16.2k | for (auto const& [node, targets]: m_chc.safeTargets()) |
129 | 49 | for (auto const& target: targets) |
130 | 49 | solvedTargets[node].insert(target.type); |
131 | | |
132 | 16.2k | for (auto const& [node, targets]: m_chc.unsafeTargets()) |
133 | 0 | solvedTargets[node] += targets | ranges::views::keys; |
134 | | |
135 | 16.2k | if (m_settings.engine.bmc) |
136 | 16.2k | m_bmc.analyze(_source, solvedTargets); |
137 | | |
138 | 16.2k | if (m_settings.showUnsupported) |
139 | 0 | { |
140 | 0 | m_errorReporter.append(m_unsupportedErrorReporter.errors()); |
141 | 0 | m_unsupportedErrorReporter.clear(); |
142 | 0 | } |
143 | 16.2k | else if (!m_unsupportedErrorReporter.errors().empty()) |
144 | 3.18k | m_errorReporter.warning( |
145 | 3.18k | 5724_error, |
146 | 3.18k | {}, |
147 | 3.18k | "SMTChecker: " + |
148 | 3.18k | std::to_string(m_unsupportedErrorReporter.errors().size()) + |
149 | 3.18k | " unsupported language feature(s)." |
150 | 3.18k | " Enable the model checker option \"show unsupported\" to see all of them." |
151 | 3.18k | ); |
152 | | |
153 | 16.2k | m_errorReporter.append(m_uniqueErrorReporter.errors()); |
154 | 16.2k | m_uniqueErrorReporter.clear(); |
155 | | |
156 | 16.2k | if (m_settings.showProvedSafe) |
157 | 0 | { |
158 | 0 | m_errorReporter.append(m_provedSafeReporter.errors()); |
159 | 0 | m_provedSafeReporter.clear(); |
160 | 0 | } |
161 | 16.2k | } |
162 | | |
163 | | std::vector<std::string> ModelChecker::unhandledQueries() |
164 | 15.2k | { |
165 | 15.2k | return m_bmc.unhandledQueries() + m_chc.unhandledQueries(); |
166 | 15.2k | } |
167 | | |
168 | | SMTSolverChoice ModelChecker::availableSolvers() |
169 | 14.6k | { |
170 | 14.6k | smtutil::SMTSolverChoice available = smtutil::SMTSolverChoice::SMTLIB2(); |
171 | 14.6k | available.eld = !boost::process::search_path("eld").empty(); |
172 | 14.6k | available.cvc5 = !boost::process::search_path("cvc5").empty(); |
173 | | #ifdef EMSCRIPTEN_BUILD |
174 | | available.z3 = true; |
175 | | #else |
176 | 14.6k | available.z3 = !boost::process::search_path("z3").empty(); |
177 | 14.6k | #endif |
178 | 14.6k | return available; |
179 | 14.6k | } |
180 | | |
181 | | SMTSolverChoice ModelChecker::checkRequestedSolvers(SMTSolverChoice _enabled, ErrorReporter& _errorReporter) |
182 | 14.6k | { |
183 | 14.6k | SMTSolverChoice availableSolvers{ModelChecker::availableSolvers()}; |
184 | | |
185 | 14.6k | if (_enabled.cvc5 && !availableSolvers.cvc5) |
186 | 14.6k | { |
187 | 14.6k | _enabled.cvc5 = false; |
188 | 14.6k | _errorReporter.warning( |
189 | 14.6k | 4902_error, |
190 | 14.6k | SourceLocation(), |
191 | 14.6k | "Solver cvc5 was selected for SMTChecker but it is not available." |
192 | 14.6k | ); |
193 | 14.6k | } |
194 | | |
195 | 14.6k | if (_enabled.eld && !availableSolvers.eld) |
196 | 14.6k | { |
197 | 14.6k | _enabled.eld = false; |
198 | 14.6k | _errorReporter.warning( |
199 | 14.6k | 4458_error, |
200 | 14.6k | SourceLocation(), |
201 | 14.6k | #if defined(__linux) || defined(__APPLE__) |
202 | 14.6k | "Solver Eldarica was selected for SMTChecker but it was not found in the system." |
203 | | #else |
204 | | "Solver Eldarica was selected for SMTChecker but it is only supported on Linux and MacOS." |
205 | | #endif |
206 | 14.6k | ); |
207 | 14.6k | } |
208 | | |
209 | 14.6k | if (_enabled.z3 && !availableSolvers.z3) |
210 | 14.6k | { |
211 | 14.6k | _enabled.z3 = false; |
212 | 14.6k | _errorReporter.warning( |
213 | 14.6k | 8158_error, |
214 | 14.6k | SourceLocation(), |
215 | 14.6k | "Solver z3 was selected for SMTChecker but it is not available." |
216 | 14.6k | ); |
217 | 14.6k | } |
218 | | |
219 | 14.6k | return _enabled; |
220 | 14.6k | } |