Coverage Report

Created: 2025-06-24 07:59

/src/solidity/libsmtutil/SMTLib2Interface.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
  This file is part of solidity.
3
4
  solidity is free software: you can redistribute it and/or modify
5
  it under the terms of the GNU General Public License as published by
6
  the Free Software Foundation, either version 3 of the License, or
7
  (at your option) any later version.
8
9
  solidity is distributed in the hope that it will be useful,
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
  GNU General Public License for more details.
13
14
  You should have received a copy of the GNU General Public License
15
  along with solidity.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
// SPDX-License-Identifier: GPL-3.0
18
19
#include <libsmtutil/SMTLib2Interface.h>
20
21
#include <libsmtutil/SMTLib2Parser.h>
22
23
#include <libsolutil/Keccak256.h>
24
25
#include <boost/algorithm/string/join.hpp>
26
#include <boost/algorithm/string/predicate.hpp>
27
28
#include <range/v3/view/transform.hpp>
29
#include <range/v3/view/zip.hpp>
30
31
#include <array>
32
#include <fstream>
33
#include <iostream>
34
#include <memory>
35
#include <stdexcept>
36
#include <string>
37
#include <utility>
38
39
using namespace solidity;
40
using namespace solidity::util;
41
using namespace solidity::frontend;
42
using namespace solidity::smtutil;
43
44
SMTLib2Interface::SMTLib2Interface(
45
  std::map<h256, std::string> _queryResponses,
46
  ReadCallback::Callback _smtCallback,
47
  std::optional<unsigned> _queryTimeout
48
):
49
  BMCSolverInterface(_queryTimeout),
50
  m_queryResponses(std::move(_queryResponses)),
51
  m_smtCallback(std::move(_smtCallback))
52
14.6k
{
53
14.6k
  reset();
54
14.6k
}
55
56
void SMTLib2Interface::reset()
57
14.6k
{
58
14.6k
  m_commands.clear();
59
14.6k
  m_context.clear();
60
14.6k
  m_commands.setOption("produce-models", "true");
61
14.6k
  if (m_queryTimeout)
62
14.0k
    m_commands.setOption("timeout", std::to_string(*m_queryTimeout));
63
14.6k
  m_commands.setLogic("ALL");
64
98.5k
  m_context.setTupleDeclarationCallback([&](TupleSort const& tupleSort){
65
98.5k
    m_commands.declareTuple(
66
98.5k
      tupleSort.name,
67
98.5k
      tupleSort.members,
68
98.5k
      tupleSort.components
69
407k
        | ranges::views::transform([&](SortPointer const& sort){ return m_context.toSmtLibSort(sort); })
70
98.5k
        | ranges::to<std::vector>()
71
98.5k
    );
72
98.5k
  });
73
14.6k
}
74
75
void SMTLib2Interface::push()
76
6.28k
{
77
6.28k
  m_commands.push();
78
6.28k
}
79
80
void SMTLib2Interface::pop()
81
6.28k
{
82
6.28k
  m_commands.pop();
83
6.28k
}
84
85
void SMTLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort)
86
1.81M
{
87
1.81M
  smtAssert(_sort);
88
1.81M
  if (_sort->kind == Kind::Function)
89
10.1k
    declareFunction(_name, _sort);
90
1.80M
  else if (!m_context.isDeclared(_name))
91
454k
  {
92
454k
    m_context.declare(_name, _sort);
93
454k
    m_commands.declareVariable(_name, toSmtLibSort(_sort));
94
454k
  }
95
1.81M
}
96
97
void SMTLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort)
98
10.1k
{
99
10.1k
  smtAssert(_sort);
100
10.1k
  smtAssert(_sort->kind == Kind::Function);
101
10.1k
  if (!m_context.isDeclared(_name))
102
9.51k
  {
103
9.51k
    auto const& fSort = std::dynamic_pointer_cast<FunctionSort>(_sort);
104
9.51k
    auto domain = toSmtLibSort(fSort->domain);
105
9.51k
    std::string codomain = toSmtLibSort(fSort->codomain);
106
9.51k
    m_context.declare(_name, _sort);
107
9.51k
    m_commands.declareFunction(_name, domain, codomain);
108
9.51k
  }
109
10.1k
}
110
111
void SMTLib2Interface::addAssertion(Expression const& _expr)
112
6.28k
{
113
6.28k
  m_commands.assertion(toSExpr(_expr));
114
6.28k
}
115
116
namespace
117
{
118
std::vector<std::string> parseValuesFromResponse(std::string const& _response)
119
0
{
120
0
  std::stringstream ss(_response);
121
0
  std::string answer;
122
0
  ss >> answer;
123
0
  smtSolverInteractionRequire(answer == "sat", "SMT: Parsing model values only possible after sat answer");
124
125
0
  std::vector<SMTLib2Expression> parsedOutput;
126
0
  SMTLib2Parser parser(ss);
127
0
  try
128
0
  {
129
0
    while (!parser.isEOF())
130
0
      parsedOutput.push_back(parser.parseExpression());
131
0
  }
132
0
  catch(SMTLib2Parser::ParsingException&)
133
0
  {
134
0
    smtSolverInteractionRequire(false, "Error during parsing SMT answer");
135
0
  }
136
0
  smtSolverInteractionRequire(parsedOutput.size() == 1, "SMT: Expected model values as a single s-expression");
137
0
  auto const& values = parsedOutput[0];
138
0
  smtSolverInteractionRequire(!isAtom(values), "Invalid format of values in SMT answer");
139
0
  std::vector<std::string> parsedValues;
140
0
  for (auto const& nameValuePair: asSubExpressions(values))
141
0
  {
142
0
    smtSolverInteractionRequire(!isAtom(nameValuePair), "Invalid format of values in SMT answer");
143
0
    smtSolverInteractionRequire(asSubExpressions(nameValuePair).size() == 2, "Invalid format of values in SMT answer");
144
0
    auto const& value = asSubExpressions(nameValuePair)[1];
145
0
    parsedValues.push_back(value.toString());
146
0
  }
147
0
  return parsedValues;
148
0
}
149
} // namespace
150
151
std::pair<CheckResult, std::vector<std::string>> SMTLib2Interface::check(std::vector<Expression> const& _expressionsToEvaluate)
152
6.28k
{
153
6.28k
  std::string response = querySolver(dumpQuery(_expressionsToEvaluate));
154
155
6.28k
  CheckResult result;
156
  // TODO proper parsing
157
6.28k
  if (boost::starts_with(response, "sat"))
158
0
    result = CheckResult::SATISFIABLE;
159
6.28k
  else if (boost::starts_with(response, "unsat"))
160
0
    result = CheckResult::UNSATISFIABLE;
161
6.28k
  else if (boost::starts_with(response, "unknown"))
162
6.28k
    result = CheckResult::UNKNOWN;
163
0
  else
164
0
    result = CheckResult::ERROR;
165
166
6.28k
  std::vector<std::string> values;
167
6.28k
  if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
168
0
    values = parseValuesFromResponse(response);
169
6.28k
  return std::make_pair(result, values);
170
6.28k
}
171
172
std::string SMTLib2Interface::toSmtLibSort(SortPointer _sort)
173
468k
{
174
468k
  return m_context.toSmtLibSort(std::move(_sort));
175
468k
}
176
177
std::vector<std::string> SMTLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts)
178
9.51k
{
179
9.51k
  std::vector<std::string> ssorts;
180
9.51k
  ssorts.reserve(_sorts.size());
181
9.51k
  for (auto const& sort: _sorts)
182
4.08k
    ssorts.push_back(toSmtLibSort(sort));
183
9.51k
  return ssorts;
184
9.51k
}
185
186
std::string SMTLib2Interface::toSExpr(solidity::smtutil::Expression const& _expr)
187
17.7k
{
188
17.7k
  return m_context.toSExpr(_expr);
189
17.7k
}
190
191
std::string SMTLib2Interface::checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate)
192
6.28k
{
193
6.28k
  std::string command;
194
6.28k
  if (_expressionsToEvaluate.empty())
195
2.91k
    command = "(check-sat)\n";
196
3.36k
  else
197
3.36k
  {
198
    // TODO make sure these are unique
199
14.8k
    for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
200
11.4k
    {
201
11.4k
      auto const& e = _expressionsToEvaluate.at(i);
202
11.4k
      smtAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate.");
203
11.4k
      command += "(declare-const |EVALEXPR_" + std::to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n";
204
11.4k
      command += "(assert (= |EVALEXPR_" + std::to_string(i) + "| " + toSExpr(e) + "))\n";
205
11.4k
    }
206
3.36k
    command += "(check-sat)\n";
207
3.36k
    command += "(get-value (";
208
14.8k
    for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
209
11.4k
      command += "|EVALEXPR_" + std::to_string(i) + "| ";
210
3.36k
    command += "))\n";
211
3.36k
  }
212
213
6.28k
  return command;
214
6.28k
}
215
216
std::string SMTLib2Interface::querySolver(std::string const& _input)
217
6.28k
{
218
6.28k
  h256 inputHash = keccak256(_input);
219
6.28k
  if (m_queryResponses.count(inputHash))
220
0
    return m_queryResponses.at(inputHash);
221
6.28k
  if (m_smtCallback)
222
0
  {
223
0
    setupSmtCallback();
224
0
    auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
225
0
    if (result.success)
226
0
      return result.responseOrErrorMessage;
227
0
  }
228
6.28k
  m_unhandledQueries.push_back(_input);
229
6.28k
  return "unknown\n";
230
6.28k
}
231
232
std::string SMTLib2Interface::dumpQuery(std::vector<Expression> const& _expressionsToEvaluate)
233
6.28k
{
234
6.28k
  return m_commands.toString() + '\n' + checkSatAndGetValuesCommand(_expressionsToEvaluate);
235
6.28k
}
236
237
238
6.28k
void SMTLib2Commands::push() {
239
6.28k
  m_frameLimits.push_back(m_commands.size());
240
6.28k
}
241
242
6.28k
void SMTLib2Commands::pop() {
243
6.28k
  smtAssert(!m_commands.empty());
244
6.28k
  auto limit = m_frameLimits.back();
245
6.28k
  m_frameLimits.pop_back();
246
12.5k
  while (m_commands.size() > limit)
247
6.28k
    m_commands.pop_back();
248
6.28k
}
249
250
27.4k
std::string SMTLib2Commands::toString() const {
251
27.4k
  return boost::algorithm::join(m_commands, "\n");
252
27.4k
}
253
254
44.1k
void SMTLib2Commands::clear() {
255
44.1k
  m_commands.clear();
256
44.1k
  m_frameLimits.clear();
257
44.1k
}
258
259
349k
void SMTLib2Commands::assertion(std::string _expr) {
260
349k
  m_commands.push_back("(assert " + std::move(_expr) + ')');
261
349k
}
262
263
void SMTLib2Commands::setOption(std::string _name, std::string _value)
264
58.1k
{
265
58.1k
  m_commands.push_back("(set-option :" + std::move(_name) + ' ' + std::move(_value) + ')');
266
58.1k
}
267
268
void SMTLib2Commands::setLogic(std::string _logic)
269
44.1k
{
270
44.1k
  m_commands.push_back("(set-logic " + std::move(_logic) + ')');
271
44.1k
}
272
273
void SMTLib2Commands::declareVariable(std::string _name, std::string _sort)
274
454k
{
275
454k
  m_commands.push_back("(declare-fun |" + std::move(_name) + "| () " + std::move(_sort) + ')');
276
454k
}
277
278
void SMTLib2Commands::declareFunction(std::string const& _name, std::vector<std::string> const& _domain, std::string const& _codomain)
279
282k
{
280
282k
  std::stringstream ss;
281
282k
  ss << "(declare-fun |" << _name << "| " << '(' << boost::join(_domain, " ")
282
282k
    << ')' << ' ' << _codomain << ')';
283
282k
  m_commands.push_back(ss.str());
284
282k
}
285
286
void SMTLib2Commands::declareTuple(
287
  std::string const& _name,
288
  std::vector<std::string> const& _memberNames,
289
  std::vector<std::string> const& _memberSorts
290
)
291
193k
{
292
193k
  auto quotedName = '|' + _name + '|';
293
193k
  std::stringstream ss;
294
193k
  ss << "(declare-datatypes ((" << quotedName << " 0)) (((" << quotedName;
295
193k
  for (auto && [memberName, memberSort]: ranges::views::zip(_memberNames, _memberSorts))
296
801k
    ss << " (|" << memberName << "| " << memberSort << ")";
297
193k
  ss << "))))";
298
193k
  auto declaration = ss.str();
299
193k
  m_commands.push_back(ss.str());
300
193k
}