Coverage Report

Created: 2026-06-30 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsmtutil/SMTLib2Interface.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 <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
12.5k
  BMCSolverInterface(_queryTimeout),
50
12.5k
  m_queryResponses(std::move(_queryResponses)),
51
12.5k
  m_smtCallback(std::move(_smtCallback))
52
12.5k
{
53
12.5k
  reset();
54
12.5k
}
55
56
void SMTLib2Interface::reset()
57
12.5k
{
58
12.5k
  m_commands.clear();
59
12.5k
  m_context.clear();
60
12.5k
  m_commands.setOption("produce-models", "true");
61
12.5k
  if (m_queryTimeout)
62
11.9k
    m_commands.setOption("timeout", std::to_string(*m_queryTimeout));
63
12.5k
  m_commands.setLogic("ALL");
64
83.2k
  m_context.setTupleDeclarationCallback([&](TupleSort const& tupleSort){
65
83.2k
    m_commands.declareTuple(
66
83.2k
      tupleSort.name,
67
83.2k
      tupleSort.members,
68
83.2k
      tupleSort.components
69
345k
        | ranges::views::transform([&](SortPointer const& sort){ return m_context.toSmtLibSort(sort); })
70
83.2k
        | ranges::to<std::vector>()
71
83.2k
    );
72
83.2k
  });
73
12.5k
}
74
75
void SMTLib2Interface::push()
76
5.58k
{
77
5.58k
  m_commands.push();
78
5.58k
}
79
80
void SMTLib2Interface::pop()
81
5.58k
{
82
5.58k
  m_commands.pop();
83
5.58k
}
84
85
void SMTLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort)
86
1.56M
{
87
1.56M
  smtAssert(_sort);
88
1.56M
  if (_sort->kind == Kind::Function)
89
9.10k
    declareFunction(_name, _sort);
90
1.55M
  else if (!m_context.isDeclared(_name))
91
390k
  {
92
390k
    m_context.declare(_name, _sort);
93
390k
    m_commands.declareVariable(_name, toSmtLibSort(_sort));
94
390k
  }
95
1.56M
}
96
97
void SMTLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort)
98
9.10k
{
99
9.10k
  smtAssert(_sort);
100
9.10k
  smtAssert(_sort->kind == Kind::Function);
101
9.10k
  if (!m_context.isDeclared(_name))
102
8.54k
  {
103
8.54k
    auto const& fSort = std::dynamic_pointer_cast<FunctionSort>(_sort);
104
8.54k
    auto domain = toSmtLibSort(fSort->domain);
105
8.54k
    std::string codomain = toSmtLibSort(fSort->codomain);
106
8.54k
    m_context.declare(_name, _sort);
107
8.54k
    m_commands.declareFunction(_name, domain, codomain);
108
8.54k
  }
109
9.10k
}
110
111
void SMTLib2Interface::addAssertion(Expression const& _expr)
112
5.58k
{
113
5.58k
  m_commands.assertion(toSExpr(_expr));
114
5.58k
}
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
5.58k
{
153
5.58k
  std::string response = querySolver(dumpQuery(_expressionsToEvaluate));
154
155
5.58k
  CheckResult result;
156
  // TODO proper parsing
157
5.58k
  if (boost::starts_with(response, "sat"))
158
0
    result = CheckResult::SATISFIABLE;
159
5.58k
  else if (boost::starts_with(response, "unsat"))
160
0
    result = CheckResult::UNSATISFIABLE;
161
5.58k
  else if (boost::starts_with(response, "unknown"))
162
5.58k
    result = CheckResult::UNKNOWN;
163
0
  else
164
0
    result = CheckResult::ERROR;
165
166
5.58k
  std::vector<std::string> values;
167
5.58k
  if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
168
0
    values = parseValuesFromResponse(response);
169
5.58k
  return std::make_pair(result, values);
170
5.58k
}
171
172
std::string SMTLib2Interface::toSmtLibSort(SortPointer _sort)
173
402k
{
174
402k
  return m_context.toSmtLibSort(std::move(_sort));
175
402k
}
176
177
std::vector<std::string> SMTLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts)
178
8.54k
{
179
8.54k
  std::vector<std::string> ssorts;
180
8.54k
  ssorts.reserve(_sorts.size());
181
8.54k
  for (auto const& sort: _sorts)
182
3.66k
    ssorts.push_back(toSmtLibSort(sort));
183
8.54k
  return ssorts;
184
8.54k
}
185
186
std::string SMTLib2Interface::toSExpr(solidity::smtutil::Expression const& _expr)
187
16.3k
{
188
16.3k
  return m_context.toSExpr(_expr);
189
16.3k
}
190
191
std::string SMTLib2Interface::checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate)
192
5.58k
{
193
5.58k
  std::string command;
194
5.58k
  if (_expressionsToEvaluate.empty())
195
2.61k
    command = "(check-sat)\n";
196
2.96k
  else
197
2.96k
  {
198
    // TODO make sure these are unique
199
13.7k
    for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
200
10.7k
    {
201
10.7k
      auto const& e = _expressionsToEvaluate.at(i);
202
10.7k
      smtAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate.");
203
10.7k
      command += "(declare-const |EVALEXPR_" + std::to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n";
204
10.7k
      command += "(assert (= |EVALEXPR_" + std::to_string(i) + "| " + toSExpr(e) + "))\n";
205
10.7k
    }
206
2.96k
    command += "(check-sat)\n";
207
2.96k
    command += "(get-value (";
208
13.7k
    for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
209
10.7k
      command += "|EVALEXPR_" + std::to_string(i) + "| ";
210
2.96k
    command += "))\n";
211
2.96k
  }
212
213
5.58k
  return command;
214
5.58k
}
215
216
std::string SMTLib2Interface::querySolver(std::string const& _input)
217
5.58k
{
218
5.58k
  h256 inputHash = keccak256(_input);
219
5.58k
  if (m_queryResponses.count(inputHash))
220
0
    return m_queryResponses.at(inputHash);
221
5.58k
  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
5.58k
  m_unhandledQueries.push_back(_input);
229
5.58k
  return "unknown\n";
230
5.58k
}
231
232
std::string SMTLib2Interface::dumpQuery(std::vector<Expression> const& _expressionsToEvaluate)
233
5.58k
{
234
5.58k
  return m_commands.toString() + '\n' + checkSatAndGetValuesCommand(_expressionsToEvaluate);
235
5.58k
}
236
237
238
5.58k
void SMTLib2Commands::push() {
239
5.58k
  m_frameLimits.push_back(m_commands.size());
240
5.58k
}
241
242
5.58k
void SMTLib2Commands::pop() {
243
5.58k
  smtAssert(!m_commands.empty());
244
5.58k
  auto limit = m_frameLimits.back();
245
5.58k
  m_frameLimits.pop_back();
246
11.1k
  while (m_commands.size() > limit)
247
5.58k
    m_commands.pop_back();
248
5.58k
}
249
250
21.2k
std::string SMTLib2Commands::toString() const {
251
21.2k
  return boost::algorithm::join(m_commands, "\n");
252
21.2k
}
253
254
37.9k
void SMTLib2Commands::clear() {
255
37.9k
  m_commands.clear();
256
37.9k
  m_frameLimits.clear();
257
37.9k
}
258
259
299k
void SMTLib2Commands::assertion(std::string _expr) {
260
299k
  m_commands.push_back("(assert " + std::move(_expr) + ')');
261
299k
}
262
263
void SMTLib2Commands::setOption(std::string _name, std::string _value)
264
49.8k
{
265
49.8k
  m_commands.push_back("(set-option :" + std::move(_name) + ' ' + std::move(_value) + ')');
266
49.8k
}
267
268
void SMTLib2Commands::setLogic(std::string _logic)
269
37.9k
{
270
37.9k
  m_commands.push_back("(set-logic " + std::move(_logic) + ')');
271
37.9k
}
272
273
void SMTLib2Commands::declareVariable(std::string _name, std::string _sort)
274
390k
{
275
390k
  m_commands.push_back("(declare-fun |" + std::move(_name) + "| () " + std::move(_sort) + ')');
276
390k
}
277
278
void SMTLib2Commands::declareFunction(std::string const& _name, std::vector<std::string> const& _domain, std::string const& _codomain)
279
243k
{
280
243k
  std::stringstream ss;
281
243k
  ss << "(declare-fun |" << _name << "| " << '(' << boost::join(_domain, " ")
282
243k
    << ')' << ' ' << _codomain << ')';
283
243k
  m_commands.push_back(ss.str());
284
243k
}
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
163k
{
292
163k
  auto quotedName = '|' + _name + '|';
293
163k
  std::stringstream ss;
294
163k
  ss << "(declare-datatypes ((" << quotedName << " 0)) (((" << quotedName;
295
163k
  for (auto && [memberName, memberSort]: ranges::views::zip(_memberNames, _memberSorts))
296
679k
    ss << " (|" << memberName << "| " << memberSort << ")";
297
163k
  ss << "))))";
298
163k
  auto declaration = ss.str();
299
163k
  m_commands.push_back(ss.str());
300
163k
}