Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsmtutil/SMTLib2Context.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/SMTLib2Context.h>
20
21
#include <boost/functional/hash.hpp>
22
23
#include <range/v3/algorithm/find_if.hpp>
24
25
namespace solidity::smtutil
26
{
27
28
std::size_t SortPairHash::operator()(std::pair<SortId, SortId> const& _pair) const
29
683k
{
30
683k
  std::size_t seed = 0;
31
683k
  boost::hash_combine(seed, _pair.first);
32
683k
  boost::hash_combine(seed, _pair.second);
33
683k
  return seed;
34
683k
}
35
36
SMTLib2Context::SMTLib2Context()
37
30.8k
{
38
30.8k
  clear();
39
30.8k
}
40
41
bool SMTLib2Context::isDeclared(std::string const& _name) const
42
33.9M
{
43
33.9M
  return m_functions.count(_name) > 0;
44
33.9M
}
45
46
void SMTLib2Context::declare(std::string const& _name, SortPointer const& _sort)
47
1.30M
{
48
1.30M
  auto [_, inserted] = m_functions.insert({_name, _sort});
49
1.30M
  smtAssert(inserted, "Trying to redeclare SMT function!");
50
1.30M
}
51
52
SortPointer SMTLib2Context::getDeclaredSort(std::string const& _name) const
53
4.56M
{
54
4.56M
  smtAssert(isDeclared(_name));
55
4.56M
  return m_functions.at(_name);
56
4.56M
}
57
58
78.3k
void SMTLib2Context::clear() {
59
78.3k
  m_functions.clear();
60
78.3k
  m_knownTypes.clear();
61
78.3k
  m_arraySorts.clear();
62
78.3k
  m_tupleSorts.clear();
63
78.3k
  m_bitVectorSorts.clear();
64
78.3k
  m_callback = {};
65
78.3k
  m_knownTypes.emplace_back(std::make_unique<SMTLibSort>(Kind::Bool, std::string("Bool"), std::vector<SortId>{}, SortId{0u}));
66
78.3k
  m_knownTypes.emplace_back(std::make_unique<SMTLibSort>(Kind::Int, std::string("Int"), std::vector<SortId>{}, SortId{1u}));
67
78.3k
  assert(m_boolSort == m_knownTypes[0]->id);
68
78.3k
  assert(m_intSort == m_knownTypes[1]->id);
69
78.3k
}
70
71
SortId SMTLib2Context::resolve(SortPointer const& _sort)
72
10.7M
{
73
10.7M
  switch (_sort->kind)
74
10.7M
  {
75
5.22M
  case Kind::Int:
76
5.22M
    return m_intSort;
77
389k
  case Kind::Bool:
78
389k
    return m_boolSort;
79
0
  case Kind::BitVector:
80
0
    return resolveBitVectorSort(dynamic_cast<BitVectorSort const&>(*_sort));
81
607k
  case Kind::Array:
82
607k
    return resolveArraySort(dynamic_cast<ArraySort const&>(*_sort));
83
4.54M
  case Kind::Tuple:
84
4.54M
    return resolveTupleSort(dynamic_cast<TupleSort const&>(*_sort));
85
0
  default:
86
0
    smtAssert(false, "Invalid SMT sort");
87
10.7M
  }
88
10.7M
}
89
90
SortPointer SMTLib2Context::unresolve(SortId _sortId) const
91
0
{
92
0
  smtAssert(_sortId < m_knownTypes.size());
93
0
  auto const& type = *m_knownTypes[_sortId];
94
0
  switch (type.kind)
95
0
  {
96
0
  case Kind::Int:
97
0
    return SortProvider::sintSort;
98
0
  case Kind::Bool:
99
0
    return SortProvider::boolSort;
100
0
  case Kind::BitVector:
101
0
  {
102
0
    auto it = ranges::find_if(m_bitVectorSorts, [&](auto const& entry) { return entry.second == _sortId; });
103
0
    smtAssert(it != m_bitVectorSorts.end());
104
0
    return std::make_shared<BitVectorSort>(it->first);
105
0
  }
106
0
  case Kind::Array:
107
0
  {
108
0
    auto it = ranges::find_if(m_arraySorts, [&](auto const& entry) { return entry.second == _sortId; });
109
0
    smtAssert(it != m_arraySorts.end());
110
0
    return std::make_shared<ArraySort>(unresolve(it->first.first), unresolve(it->first.second));
111
0
  }
112
0
  case Kind::Tuple:
113
0
  {
114
0
    auto const& tupleType = dynamic_cast<TupleType const&>(type);
115
0
    std::vector<std::string> memberNames;
116
0
    std::vector<SortPointer> memberTypes;
117
0
    for (auto&& [name, sortId] : tupleType.accessors)
118
0
    {
119
0
      memberNames.push_back(name);
120
0
      memberTypes.push_back(unresolve(sortId));
121
0
    }
122
0
    return std::make_shared<TupleSort>(tupleType.name, std::move(memberNames), std::move(memberTypes));
123
0
  }
124
0
  default:
125
0
    smtAssert(false, "Invalid SMT sort");
126
0
  }
127
0
}
128
129
SortId SMTLib2Context::resolveBitVectorSort(BitVectorSort const& _sort)
130
0
{
131
0
  auto size = _sort.size;
132
0
  auto it = m_bitVectorSorts.find(size);
133
0
  if (it == m_bitVectorSorts.end())
134
0
  {
135
0
    auto newId = static_cast<uint32_t>(m_knownTypes.size());
136
0
    m_knownTypes.emplace_back(std::make_unique<SMTLibSort>(Kind::BitVector, "(_ BitVec " + std::to_string(size) + ')', std::vector<SortId>{}, SortId{newId}));
137
0
    auto&& [newIt, inserted] = m_bitVectorSorts.emplace(size, SortId{newId});
138
0
    smtAssert(inserted);
139
0
    return newIt->second;
140
0
  }
141
0
  return it->second;
142
0
}
143
144
SortId SMTLib2Context::resolveArraySort(ArraySort const& _sort)
145
607k
{
146
607k
  smtAssert(_sort.domain && _sort.range);
147
607k
  auto domainSort = resolve(_sort.domain);
148
607k
  auto rangeSort = resolve(_sort.range);
149
607k
  auto pair = std::make_pair(domainSort, rangeSort);
150
607k
  auto it = m_arraySorts.find(pair);
151
607k
  if (it == m_arraySorts.end())
152
106k
  {
153
106k
    auto newId = static_cast<uint32_t>(m_knownTypes.size());
154
106k
    m_knownTypes.emplace_back(std::make_unique<SMTLibSort>(Kind::Array, "Array", std::vector<SortId>{domainSort, rangeSort}, SortId{newId}));
155
106k
    auto&& [newIt, inserted] = m_arraySorts.emplace(pair, SortId{newId});
156
106k
    smtAssert(inserted);
157
106k
    return newIt->second;
158
106k
  }
159
501k
  return it->second;
160
607k
}
161
162
SortId SMTLib2Context::resolveTupleSort(TupleSort const& _sort)
163
4.54M
{
164
4.54M
  auto const& tupleName = _sort.name;
165
4.54M
  auto it = m_tupleSorts.find(tupleName);
166
4.54M
  if (it == m_tupleSorts.end())
167
211k
  {
168
211k
    std::vector<std::pair<std::string, SortId>> accessors;
169
211k
    smtAssert(_sort.members.size() == _sort.components.size());
170
1.11M
    for (std::size_t i = 0u; i < _sort.members.size(); ++i)
171
899k
      accessors.emplace_back(_sort.members[i], resolve(_sort.components[i]));
172
211k
    auto newId = static_cast<uint32_t>(m_knownTypes.size());
173
211k
    m_knownTypes.emplace_back(std::make_unique<TupleType>(tupleName, std::move(accessors), SortId{newId}));
174
211k
    auto&& [newIt, inserted] = m_tupleSorts.emplace(tupleName, SortId{newId});
175
211k
    smtAssert(inserted);
176
211k
    if (m_callback)
177
211k
      m_callback(_sort);
178
211k
    return newIt->second;
179
211k
  }
180
4.33M
  return it->second;
181
4.54M
}
182
183
std::string SMTLib2Context::toString(SortId _id)
184
9.34M
{
185
9.34M
  auto const& sort = m_knownTypes.at(_id);
186
9.34M
  switch (sort->kind)
187
9.34M
  {
188
4.23M
  case Kind::Int:
189
4.23M
    return "Int";
190
388k
  case Kind::Bool:
191
388k
    return "Bool";
192
0
  case Kind::BitVector:
193
0
    return dynamic_cast<SMTLibSort const&>(*sort).name;
194
344k
  case Kind::Array:
195
344k
  {
196
344k
    auto const& arraySort = dynamic_cast<SMTLibSort const&>(*sort);
197
344k
    smtAssert(arraySort.args.size() == 2);
198
344k
    return "(Array " + toString(arraySort.args.at(0)) + ' ' + toString(arraySort.args.at(1)) + ')';
199
0
  }
200
4.37M
  case Kind::Tuple:
201
4.37M
  {
202
4.37M
    auto const& tupleType = dynamic_cast<TupleType const&>(*sort);
203
4.37M
    return '|' + tupleType.name + '|';
204
0
  }
205
0
  default:
206
0
    smtAssert(false, "Invalid SMT sort");
207
9.34M
  }
208
9.34M
}
209
210
std::string SMTLib2Context::toSmtLibSort(solidity::smtutil::SortPointer const& _sort)
211
8.65M
{
212
8.65M
  return toString(resolve(_sort));
213
8.65M
}
214
215
std::string SMTLib2Context::toSExpr(Expression const& _expr)
216
30.6M
{
217
30.6M
  if (_expr.arguments.empty())
218
17.0M
    return _expr.name;
219
220
13.5M
  std::string sexpr = "(";
221
13.5M
  if (_expr.name == "int2bv")
222
11.6k
  {
223
11.6k
    size_t size = std::stoul(_expr.arguments[1].name);
224
11.6k
    auto arg = toSExpr(_expr.arguments.front());
225
11.6k
    auto int2bv = "(_ int2bv " + std::to_string(size) + ")";
226
    // Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed.
227
11.6k
    sexpr += std::string("ite ") +
228
11.6k
      "(>= " + arg + " 0) " +
229
11.6k
      "(" + int2bv + " " + arg + ") " +
230
11.6k
      "(bvneg (" + int2bv + " (- " + arg + ")))";
231
11.6k
  }
232
13.5M
  else if (_expr.name == "bv2int")
233
6.74k
  {
234
6.74k
    auto intSort = std::dynamic_pointer_cast<IntSort>(_expr.sort);
235
6.74k
    smtAssert(intSort, "");
236
237
6.74k
    auto arg = toSExpr(_expr.arguments.front());
238
6.74k
    auto nat = "(bv2nat " + arg + ")";
239
240
6.74k
    if (!intSort->isSigned)
241
6.11k
      return nat;
242
243
624
    auto bvSort = std::dynamic_pointer_cast<BitVectorSort>(_expr.arguments.front().sort);
244
624
    smtAssert(bvSort, "");
245
624
    auto size = std::to_string(bvSort->size);
246
624
    auto pos = std::to_string(bvSort->size - 1);
247
248
    // Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed.
249
624
    sexpr += std::string("ite ") +
250
624
      "(= ((_ extract " + pos + " " + pos + ")" + arg + ") #b0) " +
251
624
      nat + " " +
252
624
      "(- (bv2nat (bvneg " + arg + ")))";
253
624
  }
254
13.5M
  else if (_expr.name == "const_array")
255
77.8k
  {
256
77.8k
    smtAssert(_expr.arguments.size() == 2, "");
257
77.8k
    auto sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments.at(0).sort);
258
77.8k
    smtAssert(sortSort, "");
259
77.8k
    auto arraySort = std::dynamic_pointer_cast<ArraySort>(sortSort->inner);
260
77.8k
    smtAssert(arraySort, "");
261
77.8k
    sexpr += "(as const " + toSmtLibSort(arraySort) + ") ";
262
77.8k
    sexpr += toSExpr(_expr.arguments.at(1));
263
77.8k
  }
264
13.5M
  else if (_expr.name == "tuple_get")
265
2.02M
  {
266
2.02M
    smtAssert(_expr.arguments.size() == 2, "");
267
2.02M
    auto tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.arguments.at(0).sort);
268
2.02M
    size_t index = std::stoul(_expr.arguments.at(1).name);
269
2.02M
    smtAssert(index < tupleSort->members.size(), "");
270
2.02M
    sexpr += "|" + tupleSort->members.at(index) + "| " + toSExpr(_expr.arguments.at(0));
271
2.02M
  }
272
11.4M
  else if (_expr.name == "tuple_constructor")
273
116k
  {
274
116k
    auto tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.sort);
275
116k
    smtAssert(tupleSort, "");
276
116k
    sexpr += "|" + tupleSort->name + "|";
277
116k
    for (auto const& arg: _expr.arguments)
278
218k
      sexpr += " " + toSExpr(arg);
279
116k
  }
280
11.3M
  else
281
11.3M
  {
282
11.3M
    sexpr += _expr.name;
283
11.3M
    for (auto const& arg: _expr.arguments)
284
27.8M
      sexpr += " " + toSExpr(arg);
285
11.3M
  }
286
13.5M
  sexpr += ")";
287
13.5M
  return sexpr;
288
13.5M
}
289
290
std::optional<SortPointer> SMTLib2Context::getTupleType(std::string const& _name) const
291
0
{
292
0
  auto it = m_tupleSorts.find(_name);
293
0
  return it == m_tupleSorts.end() ? std::nullopt : std::optional<SortPointer>(unresolve(it->second));
294
0
}
295
296
std::optional<std::pair<std::string, SortPointer>> SMTLib2Context::getTupleAccessor(std::string const& _name) const
297
0
{
298
0
  for (auto&& [_, sortId] : m_tupleSorts)
299
0
  {
300
0
    auto const& type = m_knownTypes.at(sortId);
301
0
    smtAssert(type->kind == Kind::Tuple);
302
0
    auto const& tupleType = dynamic_cast<TupleType const&>(*type);
303
0
    for (auto&& [memberName, memberSort] : tupleType.accessors)
304
0
      if (memberName == _name)
305
0
        return std::make_pair(memberName, unresolve(memberSort));
306
0
  }
307
0
  return std::nullopt;
308
0
}
309
310
void SMTLib2Context::setTupleDeclarationCallback(TupleDeclarationCallback _callback)
311
47.4k
{
312
47.4k
  m_callback = std::move(_callback);
313
47.4k
}
314
} // namespace solidity::smtutil