Coverage Report

Created: 2025-09-08 08:10

/src/solidity/libsolidity/formal/ExpressionFormatter.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/ExpressionFormatter.h>
20
21
#include <libsolidity/formal/SymbolicTypes.h>
22
23
#include <libsolidity/ast/AST.h>
24
25
#include <libsolutil/Algorithms.h>
26
#include <libsolutil/CommonData.h>
27
28
#include <boost/algorithm/string.hpp>
29
30
#include <map>
31
#include <vector>
32
#include <string>
33
34
using boost::algorithm::starts_with;
35
using namespace solidity;
36
using namespace solidity::util;
37
using namespace solidity::smtutil;
38
using namespace solidity::frontend::smt;
39
40
namespace solidity::frontend::smt
41
{
42
43
namespace
44
{
45
46
std::string formatDatatypeAccessor(smtutil::Expression const& _expr, std::vector<std::string> const& _args)
47
0
{
48
0
  auto const& op = _expr.name;
49
50
  // This is the most complicated part of the translation.
51
  // Datatype accessor means access to a field of a datatype.
52
  // In our encoding, datatypes are used to encode:
53
  // - arrays/mappings as the tuple (array, length)
54
  // - structs as the tuple (<member1>, ..., <memberK>)
55
  // - hash and signature functions as the tuple (keccak256, sha256, ripemd160, ecrecover),
56
  //   where each element is an array emulating an UF
57
  // - abi.* functions as the tuple (<abiCall1>, ..., <abiCallK>).
58
0
  if (op == "dt_accessor_keccak256")
59
0
    return "keccak256";
60
0
  if (op == "dt_accessor_sha256")
61
0
    return "sha256";
62
0
  if (op == "dt_accessor_ripemd160")
63
0
    return "ripemd160";
64
0
  if (op == "dt_accessor_ecrecover")
65
0
    return "ecrecover";
66
67
0
  std::string accessorStr = "accessor_";
68
  // Struct members have suffix "accessor_<memberName>".
69
0
  std::string type = op.substr(op.rfind(accessorStr) + accessorStr.size());
70
0
  solAssert(_expr.arguments.size() == 1, "");
71
72
0
  if (type == "length")
73
0
    return _args.at(0) + ".length";
74
0
  if (type == "array")
75
0
    return _args.at(0);
76
77
0
  if (
78
0
    starts_with(type, "block") ||
79
0
    starts_with(type, "msg") ||
80
0
    starts_with(type, "tx") ||
81
0
    starts_with(type, "abi")
82
0
  )
83
0
    return type;
84
85
0
  if (starts_with(type, "t_function_abi"))
86
0
    return type;
87
88
0
  return _args.at(0) + "." + type;
89
0
}
90
91
std::string formatGenericOp(smtutil::Expression const& _expr, std::vector<std::string> const& _args)
92
0
{
93
0
  return _expr.name + "(" + boost::algorithm::join(_args, ", ") + ")";
94
0
}
95
96
std::string formatInfixOp(std::string const& _op, std::vector<std::string> const& _args)
97
0
{
98
0
  return "(" + boost::algorithm::join(_args, " " + _op + " ") + ")";
99
0
}
100
101
std::string formatArrayOp(smtutil::Expression const& _expr, std::vector<std::string> const& _args)
102
0
{
103
0
  if (_expr.name == "select")
104
0
  {
105
0
    auto const& a0 = _args.at(0);
106
0
    static std::set<std::string> const ufs{"keccak256", "sha256", "ripemd160", "ecrecover"};
107
0
    if (ufs.count(a0) || starts_with(a0, "t_function_abi"))
108
0
      return _args.at(0) + "(" + _args.at(1) + ")";
109
0
    return _args.at(0) + "[" + _args.at(1) + "]";
110
0
  }
111
0
  if (_expr.name == "store")
112
0
    return "(" + _args.at(0) + "[" + _args.at(1) + "] := " + _args.at(2) + ")";
113
0
  return formatGenericOp(_expr, _args);
114
0
}
115
116
std::string formatUnaryOp(smtutil::Expression const& _expr, std::vector<std::string> const& _args)
117
0
{
118
0
  if (_expr.name == "not")
119
0
    return "!" + _args.at(0);
120
0
  if (_expr.name == "-")
121
0
    return "-" + _args.at(0);
122
  // Other operators such as exists may end up here.
123
0
  return formatGenericOp(_expr, _args);
124
0
}
125
126
}
127
128
smtutil::Expression substitute(smtutil::Expression _from, std::map<std::string, std::string> const& _subst)
129
0
{
130
  // TODO For now we ignore nested quantifier expressions,
131
  // but we should support them in the future.
132
0
  if (_from.name == "forall" || _from.name == "exists")
133
0
    return smtutil::Expression(true);
134
0
  if (_subst.count(_from.name))
135
0
    _from.name = _subst.at(_from.name);
136
0
  for (auto& arg: _from.arguments)
137
0
    arg = substitute(arg, _subst);
138
0
  return _from;
139
0
}
140
141
std::string toSolidityStr(smtutil::Expression const& _expr)
142
0
{
143
0
  auto const& op = _expr.name;
144
145
0
  auto const& args = _expr.arguments;
146
0
  auto strArgs = util::applyMap(args, [](auto const& _arg) { return toSolidityStr(_arg); });
147
148
  // Constant or variable.
149
0
  if (args.empty())
150
0
    return op;
151
152
0
  if (starts_with(op, "dt_accessor"))
153
0
    return formatDatatypeAccessor(_expr, strArgs);
154
155
  // Infix operators with format replacements.
156
0
  static std::map<std::string, std::string> const infixOps{
157
0
    {"and", "&&"},
158
0
    {"or", "||"},
159
0
    {"implies", "=>"},
160
0
    {"=", "="},
161
0
    {">", ">"},
162
0
    {">=", ">="},
163
0
    {"<", "<"},
164
0
    {"<=", "<="},
165
0
    {"+", "+"},
166
0
    {"-", "-"},
167
0
    {"*", "*"},
168
0
    {"/", "/"},
169
0
    {"div", "/"},
170
0
    {"mod", "%"}
171
0
  };
172
  // Some of these (and, or, +, *) may have >= 2 arguments from z3.
173
0
  if (infixOps.count(op) && args.size() >= 2)
174
0
    return formatInfixOp(infixOps.at(op), strArgs);
175
176
0
  static std::set<std::string> const arrayOps{"select", "store", "const_array"};
177
0
  if (arrayOps.count(op))
178
0
    return formatArrayOp(_expr, strArgs);
179
180
0
  if (args.size() == 1)
181
0
    return formatUnaryOp(_expr, strArgs);
182
183
  // Other operators such as bv2int, int2bv may end up here.
184
0
  return op + "(" + boost::algorithm::join(strArgs, ", ") + ")";
185
0
}
186
187
namespace
188
{
189
bool fillArray(smtutil::Expression const& _expr, std::vector<std::string>& _array, ArrayType const& _type)
190
0
{
191
  // Base case
192
0
  if (_expr.name == "const_array")
193
0
  {
194
0
    auto length = _array.size();
195
0
    std::optional<std::string> elemStr = expressionToString(_expr.arguments.at(1), _type.baseType());
196
0
    if (!elemStr)
197
0
      return false;
198
0
    _array.clear();
199
0
    _array.resize(length, *elemStr);
200
0
    return true;
201
0
  }
202
203
  // Recursive case.
204
0
  if (_expr.name == "store")
205
0
  {
206
0
    if (!fillArray(_expr.arguments.at(0), _array, _type))
207
0
      return false;
208
0
    std::optional<std::string> indexStr = expressionToString(_expr.arguments.at(1), TypeProvider::uint256());
209
0
    if (!indexStr)
210
0
      return false;
211
    // Sometimes the solver assigns huge lengths that are not related,
212
    // we should catch and ignore those.
213
0
    unsigned long index;
214
0
    try
215
0
    {
216
0
      index = stoul(*indexStr);
217
0
    }
218
0
    catch (std::out_of_range const&)
219
0
    {
220
0
      return true;
221
0
    }
222
0
    catch (std::invalid_argument const&)
223
0
    {
224
0
      return true;
225
0
    }
226
0
    std::optional<std::string> elemStr = expressionToString(_expr.arguments.at(2), _type.baseType());
227
0
    if (!elemStr)
228
0
      return false;
229
0
    if (index < _array.size())
230
0
      _array.at(index) = *elemStr;
231
0
    return true;
232
0
  }
233
234
  // Special base case, not supported yet.
235
0
  if (_expr.name.rfind("(_ as-array") == 0)
236
0
  {
237
    // Z3 expression representing reinterpretation of a different term as an array
238
0
    return false;
239
0
  }
240
241
0
  solAssert(false);
242
0
}
243
}
244
245
std::optional<std::string> expressionToString(smtutil::Expression const& _expr, frontend::Type const* _type)
246
0
{
247
0
  if (smt::isNumber(*_type))
248
0
  {
249
0
    solAssert(_expr.sort->kind == Kind::Int);
250
0
    solAssert(_expr.arguments.empty() || _expr.name == "-");
251
0
    if (_expr.name == "-")
252
0
    {
253
0
      solAssert(_expr.arguments.size() == 1);
254
0
      smtutil::Expression const& val = _expr.arguments[0];
255
0
      solAssert(val.sort->kind == Kind::Int && val.arguments.empty());
256
0
      return "(- " + val.name + ")";
257
0
    }
258
259
0
    if (
260
0
      _type->category() == frontend::Type::Category::Address ||
261
0
      _type->category() == frontend::Type::Category::FixedBytes
262
0
    )
263
0
    {
264
0
      try
265
0
      {
266
0
        if (_expr.name == "0")
267
0
          return "0x0";
268
        // For some reason the code below returns "0x" for "0".
269
0
        return util::toHex(toCompactBigEndian(bigint(_expr.name)), util::HexPrefix::Add, util::HexCase::Lower);
270
0
      }
271
0
      catch (std::out_of_range const&)
272
0
      {
273
0
      }
274
0
      catch (std::invalid_argument const&)
275
0
      {
276
0
      }
277
0
    }
278
279
0
    return _expr.name;
280
0
  }
281
0
  if (smt::isBool(*_type))
282
0
  {
283
0
    solAssert(_expr.sort->kind == Kind::Bool);
284
0
    solAssert(_expr.arguments.empty());
285
0
    solAssert(_expr.name == "true" || _expr.name == "false");
286
0
    return _expr.name;
287
0
  }
288
0
  if (smt::isFunction(*_type))
289
0
  {
290
0
    solAssert(_expr.arguments.empty());
291
0
    return _expr.name;
292
0
  }
293
0
  if (smt::isArray(*_type))
294
0
  {
295
0
    auto const& arrayType = dynamic_cast<ArrayType const&>(*_type);
296
0
    if (_expr.name != "tuple_constructor")
297
0
      return {};
298
299
0
    auto const& tupleSort = dynamic_cast<TupleSort const&>(*_expr.sort);
300
0
    solAssert(tupleSort.components.size() == 2);
301
302
0
    unsigned long length;
303
0
    try
304
0
    {
305
0
      length = stoul(_expr.arguments.at(1).name);
306
0
    }
307
0
    catch(std::out_of_range const&)
308
0
    {
309
0
      return {};
310
0
    }
311
0
    catch(std::invalid_argument const&)
312
0
    {
313
0
      return {};
314
0
    }
315
316
    // Limit this counterexample size to 1k.
317
    // Some OSs give you "unlimited" memory through swap and other virtual memory,
318
    // so purely relying on bad_alloc being thrown is not a good idea.
319
    // In that case, the array allocation might cause OOM and the program is killed.
320
0
    if (length >= 1024)
321
0
      return {};
322
0
    try
323
0
    {
324
0
      std::vector<std::string> array(length);
325
0
      if (!fillArray(_expr.arguments.at(0), array, arrayType))
326
0
        return {};
327
0
      return "[" + boost::algorithm::join(array, ", ") + "]";
328
0
    }
329
0
    catch (std::bad_alloc const&)
330
0
    {
331
      // Solver gave a concrete array but length is too large.
332
0
    }
333
0
  }
334
0
  if (smt::isNonRecursiveStruct(*_type))
335
0
  {
336
0
    auto const& structType = dynamic_cast<StructType const&>(*_type);
337
0
    solAssert(_expr.name == "tuple_constructor");
338
0
    auto const& tupleSort = dynamic_cast<TupleSort const&>(*_expr.sort);
339
0
    auto members = structType.structDefinition().members();
340
0
    solAssert(tupleSort.components.size() == members.size());
341
0
    solAssert(_expr.arguments.size() == members.size());
342
0
    std::vector<std::string> elements;
343
0
    for (unsigned i = 0; i < members.size(); ++i)
344
0
    {
345
0
      std::optional<std::string> elementStr = expressionToString(_expr.arguments.at(i), members[i]->type());
346
0
      elements.push_back(members[i]->name() + (elementStr.has_value() ?  ": " + elementStr.value() : ""));
347
0
    }
348
0
    return "{" + boost::algorithm::join(elements, ", ") + "}";
349
0
  }
350
351
0
  return {};
352
0
}
353
354
std::vector<std::optional<std::string>> formatExpressions(
355
  std::vector<smtutil::Expression> const& _exprs,
356
  std::vector<frontend::Type const*> const& _types
357
)
358
0
{
359
0
  solAssert(_exprs.size() == _types.size());
360
0
  std::vector<std::optional<std::string>> strExprs;
361
0
  for (unsigned i = 0; i < _exprs.size(); ++i)
362
0
    strExprs.push_back(expressionToString(_exprs.at(i), _types.at(i)));
363
0
  return strExprs;
364
0
}
365
366
}