Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libsmtutil/CHCSmtLib2Interface.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/CHCSmtLib2Interface.h>
20
21
#include <libsmtutil/SMTLib2Parser.h>
22
23
#include <libsolutil/Keccak256.h>
24
#include <libsolutil/StringUtils.h>
25
#include <libsolutil/Visitor.h>
26
27
#include <boost/algorithm/string/join.hpp>
28
#include <boost/algorithm/string/predicate.hpp>
29
30
#include <range/v3/algorithm/all_of.hpp>
31
#include <range/v3/algorithm/sort.hpp>
32
#include <range/v3/view.hpp>
33
34
#include <array>
35
#include <fstream>
36
#include <iostream>
37
#include <memory>
38
#include <stdexcept>
39
40
using namespace solidity;
41
using namespace solidity::util;
42
using namespace solidity::frontend;
43
using namespace solidity::smtutil;
44
45
CHCSmtLib2Interface::CHCSmtLib2Interface(
46
  std::map<h256, std::string> _queryResponses,
47
  ReadCallback::Callback _smtCallback,
48
  std::optional<unsigned> _queryTimeout
49
):
50
  CHCSolverInterface(_queryTimeout),
51
  m_queryResponses(std::move(_queryResponses)),
52
  m_smtCallback(std::move(_smtCallback))
53
14.6k
{
54
14.6k
  reset();
55
14.6k
}
56
57
void CHCSmtLib2Interface::reset()
58
30.8k
{
59
30.8k
  m_unhandledQueries.clear();
60
30.8k
  m_commands.clear();
61
30.8k
  m_context.clear();
62
30.8k
  createHeader();
63
99.0k
  m_context.setTupleDeclarationCallback([&](TupleSort const& _tupleSort){
64
99.0k
    m_commands.declareTuple(
65
99.0k
      _tupleSort.name,
66
99.0k
      _tupleSort.members,
67
99.0k
      _tupleSort.components
68
410k
        | ranges::views::transform([&](SortPointer const& _sort){ return m_context.toSmtLibSort(_sort); })
69
99.0k
        | ranges::to<std::vector>()
70
99.0k
    );
71
99.0k
  });
72
30.8k
}
73
74
void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
75
283k
{
76
283k
  smtAssert(_expr.sort);
77
283k
  smtAssert(_expr.sort->kind == Kind::Function);
78
283k
  if (m_context.isDeclared(_expr.name))
79
0
    return;
80
283k
  auto const& fSort = std::dynamic_pointer_cast<FunctionSort>(_expr.sort);
81
283k
  smtAssert(fSort->codomain);
82
283k
  auto domain = toSmtLibSort(fSort->domain);
83
283k
  std::string codomain = toSmtLibSort(fSort->codomain);
84
283k
  m_commands.declareFunction(_expr.name, domain, codomain);
85
283k
  m_context.declare(_expr.name, _expr.sort);
86
283k
}
87
88
void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& /*_name*/)
89
355k
{
90
355k
  m_commands.assertion("(forall" + forall(_expr) + '\n' + m_context.toSExpr(_expr) + ")\n");
91
355k
}
92
93
CHCSolverInterface::QueryResult CHCSmtLib2Interface::query(Expression const& _block)
94
22.2k
{
95
22.2k
  std::string query = dumpQuery(_block);
96
22.2k
  try
97
22.2k
  {
98
22.2k
    std::string response = querySolver(query);
99
100
22.2k
    CheckResult result;
101
    // NOTE: Our internal semantics is UNSAT -> SAFE and SAT -> UNSAFE, which corresponds to usual SMT-based model checking
102
    // However, with CHC solvers, the meaning is flipped, UNSAT -> UNSAFE and SAT -> SAFE.
103
    // So we have to flip the answer.
104
22.2k
    if (boost::starts_with(response, "sat"))
105
0
      return {CheckResult::UNSATISFIABLE, invariantsFromSolverResponse(response), {}};
106
22.2k
    if (boost::starts_with(response, "unsat"))
107
0
      result = CheckResult::SATISFIABLE;
108
22.2k
    else if (boost::starts_with(response, "unknown"))
109
22.2k
      result = CheckResult::UNKNOWN;
110
0
    else
111
0
      result = CheckResult::ERROR;
112
22.2k
    return {result, {}, {}};
113
22.2k
  }
114
22.2k
  catch(smtutil::SMTSolverInteractionError const&)
115
22.2k
  {
116
0
    return {CheckResult::ERROR, {}, {}};
117
0
  }
118
119
22.2k
}
120
121
void CHCSmtLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort)
122
9.60M
{
123
9.60M
  smtAssert(_sort);
124
9.60M
  if (!m_context.isDeclared(_name))
125
509k
    m_context.declare(_name, _sort);
126
9.60M
}
127
128
std::string CHCSmtLib2Interface::toSmtLibSort(SortPointer const& _sort)
129
7.18M
{
130
7.18M
  return m_context.toSmtLibSort(_sort);
131
7.18M
}
132
133
std::vector<std::string> CHCSmtLib2Interface::toSmtLibSort(std::vector<SortPointer> const& _sorts)
134
283k
{
135
2.28M
  return applyMap(_sorts, [this](auto const& sort) { return toSmtLibSort(sort); });
136
283k
}
137
138
std::set<std::string> CHCSmtLib2Interface::collectVariableNames(Expression const& _expr) const
139
355k
{
140
355k
  std::set<std::string> names;
141
355k
  auto dfs = [&](Expression const& _current, auto _recurse) -> void
142
29.6M
  {
143
29.6M
    if (_current.arguments.empty())
144
17.5M
    {
145
17.5M
      if (m_context.isDeclared(_current.name))
146
11.2M
        names.insert(_current.name);
147
17.5M
    }
148
12.1M
    else
149
12.1M
      for (auto const& arg: _current.arguments)
150
29.3M
        _recurse(arg, _recurse);
151
29.6M
  };
152
355k
  dfs(_expr, dfs);
153
355k
  return names;
154
355k
}
155
156
std::string CHCSmtLib2Interface::forall(Expression const& _expr)
157
355k
{
158
355k
  auto varNames = collectVariableNames(_expr);
159
355k
  std::string vars("(");
160
355k
  for (auto const& name: varNames)
161
4.64M
  {
162
4.64M
    auto sort = m_context.getDeclaredSort(name);
163
4.64M
    if (sort->kind != Kind::Function)
164
4.62M
      vars += " (" + name + " " + toSmtLibSort(sort) + ")";
165
4.64M
  }
166
355k
  vars += ")";
167
355k
  return vars;
168
355k
}
169
170
std::string CHCSmtLib2Interface::querySolver(std::string const& _input)
171
22.2k
{
172
22.2k
  util::h256 inputHash = util::keccak256(_input);
173
22.2k
  if (m_queryResponses.count(inputHash))
174
0
    return m_queryResponses.at(inputHash);
175
176
22.2k
  if (m_smtCallback)
177
0
  {
178
0
    auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
179
0
    if (result.success)
180
0
      return result.responseOrErrorMessage;
181
0
  }
182
183
22.2k
  m_unhandledQueries.push_back(_input);
184
22.2k
  return "unknown\n";
185
22.2k
}
186
187
std::string CHCSmtLib2Interface::dumpQuery(Expression const& _expr)
188
22.2k
{
189
22.2k
  return m_commands.toString() + createQueryAssertion(_expr.name) + '\n' + "(check-sat)" + '\n';
190
22.2k
}
191
192
void CHCSmtLib2Interface::createHeader()
193
30.8k
{
194
30.8k
  if (m_queryTimeout)
195
30.8k
    m_commands.setOption("timeout", std::to_string(*m_queryTimeout));
196
30.8k
  m_commands.setLogic("HORN");
197
30.8k
}
198
199
22.2k
std::string CHCSmtLib2Interface::createQueryAssertion(std::string _name) {
200
22.2k
  return "(assert\n(forall ((UNUSED Bool))\n(=> " + std::move(_name) + " false)))";
201
22.2k
}
202
203
namespace
204
{
205
bool isNumber(std::string const& _expr)
206
0
{
207
0
  return ranges::all_of(_expr, [](char c) { return isDigit(c) || c == '.'; });
208
0
}
209
210
bool isBitVectorHexConstant(std::string const& _string)
211
0
{
212
0
  if (_string.substr(0, 2) != "#x")
213
0
    return false;
214
0
  if (_string.find_first_not_of("0123456789abcdefABCDEF", 2) != std::string::npos)
215
0
    return false;
216
0
  return true;
217
0
}
218
219
bool isBitVectorConstant(std::string const& _string)
220
0
{
221
0
  if (_string.substr(0, 2) != "#b")
222
0
    return false;
223
0
  if (_string.find_first_not_of("01", 2) != std::string::npos)
224
0
    return false;
225
0
  return true;
226
0
}
227
}
228
229
void CHCSmtLib2Interface::ScopedParser::addVariableDeclaration(std::string _name, solidity::smtutil::SortPointer _sort)
230
0
{
231
0
  m_localVariables.emplace(std::move(_name), std::move(_sort));
232
0
}
233
234
std::optional<SortPointer> CHCSmtLib2Interface::ScopedParser::lookupKnownTupleSort(std::string const& _name) const
235
0
{
236
0
  return m_context.getTupleType(_name);
237
0
}
238
239
SortPointer CHCSmtLib2Interface::ScopedParser::toSort(SMTLib2Expression const& _expr)
240
0
{
241
0
  if (isAtom(_expr))
242
0
  {
243
0
    auto const& name = asAtom(_expr);
244
0
    if (name == "Int")
245
0
      return SortProvider::sintSort;
246
0
    if (name == "Bool")
247
0
      return SortProvider::boolSort;
248
0
    auto tupleSort = lookupKnownTupleSort(name);
249
0
    if (tupleSort)
250
0
      return tupleSort.value();
251
0
  }
252
0
  else
253
0
  {
254
0
    auto const& args = asSubExpressions(_expr);
255
0
    if (asAtom(args[0]) == "Array")
256
0
    {
257
0
      smtSolverInteractionRequire(args.size() == 3, "Wrong format of Array sort in solver's response");
258
0
      auto domainSort = toSort(args[1]);
259
0
      auto codomainSort = toSort(args[2]);
260
0
      return std::make_shared<ArraySort>(std::move(domainSort), std::move(codomainSort));
261
0
    }
262
0
    if (args.size() == 3 && isAtom(args[0]) && asAtom(args[0]) == "_" && isAtom(args[1])
263
0
      && asAtom(args[1]) == "int2bv")
264
0
      return std::make_shared<BitVectorSort>(std::stoul(asAtom(args[2])));
265
0
  }
266
0
  smtSolverInteractionRequire(false, "Unknown sort encountered");
267
0
}
268
269
smtutil::Expression CHCSmtLib2Interface::ScopedParser::parseQuantifier(
270
  std::string const& _quantifierName,
271
  std::vector<SMTLib2Expression> const& _varList,
272
  SMTLib2Expression const& _coreExpression)
273
0
{
274
0
  std::vector<std::pair<std::string, SortPointer>> boundVariables;
275
0
  for (auto const& sortedVar: _varList)
276
0
  {
277
0
    smtSolverInteractionRequire(!isAtom(sortedVar), "Wrong format of quantified expression in solver's response");
278
0
    auto varSortPair = asSubExpressions(sortedVar);
279
0
    smtSolverInteractionRequire(varSortPair.size() == 2, "Wrong format of quantified expression in solver's response");
280
0
    boundVariables.emplace_back(asAtom(varSortPair[0]), toSort(varSortPair[1]));
281
0
  }
282
0
  for (auto const& [var, sort]: boundVariables)
283
0
  {
284
0
    smtSolverInteractionRequire(m_localVariables.count(var) == 0, "Quantifying over previously encountered variable"); // TODO: deal with shadowing?
285
0
    m_localVariables.emplace(var, sort);
286
0
  }
287
0
  auto core = toSMTUtilExpression(_coreExpression);
288
0
  for (auto const& [var, sort]: boundVariables)
289
0
  {
290
0
    smtSolverInteractionRequire(m_localVariables.count(var) != 0, "Error in processing quantified expression");
291
0
    m_localVariables.erase(var);
292
0
  }
293
0
  return Expression(_quantifierName, {core}, SortProvider::boolSort); // TODO: what about the bound variables?
294
0
}
295
296
smtutil::Expression CHCSmtLib2Interface::ScopedParser::toSMTUtilExpression(SMTLib2Expression const& _expr)
297
0
{
298
0
  return std::visit(
299
0
    GenericVisitor{
300
0
      [&](std::string const& _atom)
301
0
      {
302
0
        if (_atom == "true" || _atom == "false")
303
0
          return smtutil::Expression(_atom == "true");
304
0
        else if (isNumber(_atom))
305
0
          return smtutil::Expression(_atom, {}, SortProvider::sintSort);
306
0
        else if (isBitVectorHexConstant(_atom))
307
0
          return smtutil::Expression(_atom, {}, std::make_shared<BitVectorSort>((_atom.size() - 2) * 4));
308
0
        else if (isBitVectorConstant(_atom))
309
0
          return smtutil::Expression(_atom, {}, std::make_shared<BitVectorSort>(_atom.size() - 2));
310
0
        else if (auto it = m_localVariables.find(_atom); it != m_localVariables.end())
311
0
          return smtutil::Expression(_atom, {}, it->second);
312
0
        else if (m_context.isDeclared(_atom))
313
0
          return smtutil::Expression(_atom, {}, m_context.getDeclaredSort(_atom));
314
0
        else if (auto maybeTupleType = m_context.getTupleType(_atom); maybeTupleType.has_value())
315
0
        {
316
          // 0-ary tuple type, can happen
317
0
          return smtutil::Expression(_atom, {}, std::make_shared<TupleSort>(_atom, std::vector<std::string>{}, std::vector<SortPointer>{}));
318
0
        }
319
0
        else
320
0
          smtSolverInteractionRequire(false, "Unhandled atomic SMT expression");
321
0
      },
322
0
      [&](std::vector<SMTLib2Expression> const& _subExpr)
323
0
      {
324
0
        SortPointer sort;
325
0
        std::vector<smtutil::Expression> arguments;
326
0
        if (isAtom(_subExpr.front()))
327
0
        {
328
0
          std::string const& op = asAtom(_subExpr.front());
329
0
          if (op == "!")
330
0
          {
331
            // named term, we ignore the name
332
0
            smtSolverInteractionRequire(_subExpr.size() > 2, "Wrong format of named SMT-LIB term");
333
0
            return toSMTUtilExpression(_subExpr[1]);
334
0
          }
335
0
          if (op == "exists" || op == "forall")
336
0
          {
337
0
            smtSolverInteractionRequire(_subExpr.size() == 3, "Wrong format of quantified expression");
338
0
            smtSolverInteractionRequire(!isAtom(_subExpr[1]), "Wrong format of quantified expression");
339
0
            return parseQuantifier(op, asSubExpressions(_subExpr[1]), _subExpr[2]);
340
0
          }
341
0
          for (size_t i = 1; i < _subExpr.size(); i++)
342
0
            arguments.emplace_back(toSMTUtilExpression(_subExpr[i]));
343
0
          if (auto tupleSort = lookupKnownTupleSort(op); tupleSort)
344
0
          {
345
0
            auto sortSort = std::make_shared<SortSort>(tupleSort.value());
346
0
            return Expression::tuple_constructor(Expression(sortSort), arguments);
347
0
          }
348
0
          if (m_context.isDeclared(op))
349
0
            return smtutil::Expression(op, std::move(arguments), m_context.getDeclaredSort(op));
350
0
          if (auto maybeTupleAccessor = m_context.getTupleAccessor(op); maybeTupleAccessor.has_value())
351
0
          {
352
0
            auto accessor = maybeTupleAccessor.value();
353
0
            return smtutil::Expression("dt_accessor_" + accessor.first, std::move(arguments), accessor.second);
354
0
          }
355
0
          if (op == "select")
356
0
          {
357
0
            smtSolverInteractionRequire(arguments.size() == 2, "Select has two arguments: array and index");
358
0
            return smtutil::Expression::select(arguments[0], arguments[1]);
359
0
          }
360
0
          if (op == "store")
361
0
          {
362
0
            smtSolverInteractionRequire(arguments.size() == 3, "Store has three arguments: array, index and element");
363
0
            return smtutil::Expression::store(arguments[0], arguments[1], arguments[2]);
364
0
          }
365
0
          if (op == "bv2int")
366
0
          {
367
0
            smtSolverInteractionRequire(arguments.size() == 1, "bv2int has one argument");
368
0
            return smtutil::Expression::bv2int(arguments[0]);
369
0
          }
370
0
          else
371
0
          {
372
0
            std::set<std::string> boolOperators{"and", "or", "not", "=", "<", ">", "<=", ">=", "=>"};
373
0
            sort = contains(boolOperators, op) ? SortProvider::boolSort : arguments.back().sort;
374
0
            return smtutil::Expression(op, std::move(arguments), std::move(sort));
375
0
          }
376
0
          smtSolverInteractionRequire(false, "Unhandled case in expression conversion");
377
0
        }
378
0
        else
379
0
        {
380
          // check for const array
381
0
          if (_subExpr.size() == 2 and !isAtom(_subExpr[0]))
382
0
          {
383
0
            auto const& typeArgs = asSubExpressions(_subExpr.front());
384
0
            if (typeArgs.size() == 3 && typeArgs[0].toString() == "as"
385
0
              && typeArgs[1].toString() == "const")
386
0
            {
387
0
              auto arraySort = toSort(typeArgs[2]);
388
0
              auto sortSort = std::make_shared<SortSort>(arraySort);
389
0
              return smtutil::Expression::
390
0
                const_array(Expression(sortSort), toSMTUtilExpression(_subExpr[1]));
391
0
            }
392
0
            if (typeArgs.size() == 3 && typeArgs[0].toString() == "_"
393
0
              && typeArgs[1].toString() == "int2bv")
394
0
            {
395
0
              auto bvSort = std::dynamic_pointer_cast<BitVectorSort>(toSort(_subExpr[0]));
396
0
              smtSolverInteractionRequire(bvSort, "Invalid format of bitvector sort");
397
0
              return smtutil::Expression::int2bv(toSMTUtilExpression(_subExpr[1]), bvSort->size);
398
0
            }
399
0
            if (typeArgs.size() == 4 && typeArgs[0].toString() == "_"
400
0
              && typeArgs[1].toString() == "extract")
401
0
              return smtutil::Expression(
402
0
                "extract",
403
0
                {toSMTUtilExpression(typeArgs[2]), toSMTUtilExpression(typeArgs[3])},
404
0
                SortProvider::bitVectorSort // TODO: Compute bit size properly?
405
0
              );
406
0
          }
407
0
          smtSolverInteractionRequire(false, "Unhandled case in expression conversion");
408
0
        }
409
0
      }
410
0
    },
411
0
    _expr.data
412
0
  );
413
0
}
414
415
416
CHCSmtLib2Interface::Invariants CHCSmtLib2Interface::invariantsFromSolverResponse(std::string const& _response) const
417
0
{
418
0
  std::stringstream ss(_response);
419
0
  std::string answer;
420
0
  ss >> answer;
421
0
  smtSolverInteractionRequire(answer == "sat", "CHC model can only be extracted from sat answer");
422
0
  SMTLib2Parser parser(ss);
423
0
  if (parser.isEOF())
424
0
    return {};
425
0
  std::vector<SMTLib2Expression> parsedOutput;
426
0
  try
427
0
  {
428
0
    while (!parser.isEOF())
429
0
      parsedOutput.push_back(parser.parseExpression());
430
0
  }
431
0
  catch(SMTLib2Parser::ParsingException&)
432
0
  {
433
0
    smtSolverInteractionRequire(false, "Error during parsing CHC model");
434
0
  }
435
0
  smtSolverInteractionRequire(parser.isEOF(), "Error during parsing CHC model");
436
0
  smtSolverInteractionRequire(!parsedOutput.empty(), "Error during parsing CHC model");
437
0
  auto& commands = parsedOutput.size() == 1 ? asSubExpressions(parsedOutput[0]) : parsedOutput;
438
0
  Invariants definitions;
439
0
  for (auto& command: commands)
440
0
  {
441
0
    auto& args = asSubExpressions(command);
442
0
    smtSolverInteractionRequire(args.size() == 5, "Invalid format of CHC model");
443
    // args[0] = "define-fun"
444
    // args[1] = predicate name
445
    // args[2] = formal arguments of the predicate
446
    // args[3] = return sort
447
    // args[4] = body of the predicate's interpretation
448
0
    smtSolverInteractionRequire(isAtom(args[0]) && asAtom(args[0]) == "define-fun", "Invalid format of CHC model");
449
0
    smtSolverInteractionRequire(isAtom(args[1]), "Invalid format of CHC model");
450
0
    smtSolverInteractionRequire(!isAtom(args[2]), "Invalid format of CHC model");
451
0
    smtSolverInteractionRequire(isAtom(args[3]) && asAtom(args[3]) == "Bool", "Invalid format of CHC model");
452
0
    auto& interpretation = args[4];
453
0
    inlineLetExpressions(interpretation);
454
0
    ScopedParser scopedParser(m_context);
455
0
    auto const& formalArguments = asSubExpressions(args[2]);
456
0
    std::vector<std::string> predicateArguments;
457
0
    for (auto const& formalArgument: formalArguments)
458
0
    {
459
0
      smtSolverInteractionRequire(!isAtom(formalArgument), "Invalid format of CHC model");
460
0
      auto const& nameSortPair = asSubExpressions(formalArgument);
461
0
      smtSolverInteractionRequire(nameSortPair.size() == 2, "Invalid format of CHC model");
462
0
      smtSolverInteractionRequire(isAtom(nameSortPair[0]), "Invalid format of CHC model");
463
0
      SortPointer varSort = scopedParser.toSort(nameSortPair[1]);
464
0
      scopedParser.addVariableDeclaration(asAtom(nameSortPair[0]), varSort);
465
0
      predicateArguments.push_back(asAtom(nameSortPair[0]));
466
0
    }
467
468
0
    auto parsedInterpretation = scopedParser.toSMTUtilExpression(interpretation);
469
470
    // Hack to make invariants more stable across operating systems
471
0
    if (parsedInterpretation.name == "and" || parsedInterpretation.name == "or")
472
0
      ranges::sort(parsedInterpretation.arguments, [](Expression const& first, Expression const& second) {
473
0
        return first.name < second.name;
474
0
      });
475
476
0
    std::string const& predicateName = asAtom(args[1]);
477
0
    smtSolverInteractionRequire(!definitions.contains(predicateName), "Predicates must be unique");
478
0
    definitions.emplace(predicateName, InvariantInfo{parsedInterpretation, predicateArguments});
479
0
  }
480
0
  return definitions;
481
0
}
482
483
namespace
484
{
485
486
struct LetBindings
487
{
488
  using BindingRecord = std::vector<SMTLib2Expression>;
489
  std::unordered_map<std::string, BindingRecord> bindings;
490
  std::vector<std::string> varNames;
491
  std::vector<std::size_t> scopeBounds;
492
493
0
  bool has(std::string const& varName) { return bindings.find(varName) != bindings.end(); }
494
495
  SMTLib2Expression& operator[](std::string const& varName)
496
0
  {
497
0
    auto it = bindings.find(varName);
498
0
    smtSolverInteractionRequire(it != bindings.end(), "Error in processing let bindings");
499
0
    smtSolverInteractionRequire(!it->second.empty(), "Error in processing let bindings");
500
0
    return it->second.back();
501
0
  }
502
503
0
  void pushScope() { scopeBounds.push_back(varNames.size()); }
504
505
  void popScope()
506
0
  {
507
0
    smtSolverInteractionRequire(!scopeBounds.empty(), "Error in processing let bindings");
508
0
    auto bound = scopeBounds.back();
509
0
    while (varNames.size() > bound)
510
0
    {
511
0
      auto const& varName = varNames.back();
512
0
      auto it = bindings.find(varName);
513
0
      smtSolverInteractionRequire(it != bindings.end(), "Error in processing let bindings");
514
0
      auto& record = it->second;
515
0
      record.pop_back();
516
0
      if (record.empty())
517
0
        bindings.erase(it);
518
0
      varNames.pop_back();
519
0
    }
520
0
    scopeBounds.pop_back();
521
0
  }
522
523
  void addBinding(std::string name, SMTLib2Expression expression)
524
0
  {
525
0
    auto it = bindings.find(name);
526
0
    if (it == bindings.end())
527
0
      bindings.insert({name, {std::move(expression)}});
528
0
    else
529
0
      it->second.push_back(std::move(expression));
530
0
    varNames.push_back(std::move(name));
531
0
  }
532
};
533
534
void inlineLetExpressions(SMTLib2Expression& _expr, LetBindings& _bindings)
535
0
{
536
0
  if (isAtom(_expr))
537
0
  {
538
0
    auto const& atom = asAtom(_expr);
539
0
    if (_bindings.has(atom))
540
0
      _expr = _bindings[atom];
541
0
    return;
542
0
  }
543
0
  auto& subexprs = asSubExpressions(_expr);
544
0
  smtSolverInteractionRequire(!subexprs.empty(), "Invalid let expression");
545
0
  auto const& first = subexprs.at(0);
546
0
  if (isAtom(first) && asAtom(first) == "let")
547
0
  {
548
0
    smtSolverInteractionRequire(subexprs.size() == 3, "Invalid let expression");
549
0
    smtSolverInteractionRequire(!isAtom(subexprs[1]), "Invalid let expression");
550
0
    auto& bindingExpressions = asSubExpressions(subexprs[1]);
551
    // process new bindings
552
0
    std::vector<std::pair<std::string, SMTLib2Expression>> newBindings;
553
0
    for (auto& binding: bindingExpressions)
554
0
    {
555
0
      smtSolverInteractionRequire(!isAtom(binding), "Invalid let expression");
556
0
      auto& bindingPair = asSubExpressions(binding);
557
0
      smtSolverInteractionRequire(bindingPair.size() == 2, "Invalid let expression");
558
0
      smtSolverInteractionRequire(isAtom(bindingPair.at(0)), "Invalid let expression");
559
0
      inlineLetExpressions(bindingPair.at(1), _bindings);
560
0
      newBindings.emplace_back(asAtom(bindingPair.at(0)), bindingPair.at(1));
561
0
    }
562
0
    _bindings.pushScope();
563
0
    for (auto&& [name, expr]: newBindings)
564
0
      _bindings.addBinding(std::move(name), std::move(expr));
565
566
0
    newBindings.clear();
567
568
    // get new subexpression
569
0
    inlineLetExpressions(subexprs.at(2), _bindings);
570
    // remove the new bindings
571
0
    _bindings.popScope();
572
573
    // update the expression
574
0
    auto tmp = std::move(subexprs.at(2));
575
0
    _expr = std::move(tmp);
576
0
    return;
577
0
  }
578
0
  else if (isAtom(first) && (asAtom(first) == "forall" || asAtom(first) == "exists"))
579
0
  {
580
    // A little hack to ensure quantified variables are not substituted because of some outer let definition:
581
    // We define the current binding of the variable to itself, before we recurse in to subterm
582
0
    smtSolverInteractionRequire(subexprs.size() == 3, "Invalid let expression");
583
0
    _bindings.pushScope();
584
0
    for (auto const& sortedVar: asSubExpressions(subexprs.at(1)))
585
0
    {
586
0
      auto const& varNameExpr = asSubExpressions(sortedVar).at(0);
587
0
      _bindings.addBinding(asAtom(varNameExpr), varNameExpr);
588
0
    }
589
0
    inlineLetExpressions(subexprs.at(2), _bindings);
590
0
    _bindings.popScope();
591
0
    return;
592
0
  }
593
594
  // not a let expression, just process all arguments recursively
595
0
  for (auto& subexpr: subexprs)
596
0
    inlineLetExpressions(subexpr, _bindings);
597
0
}
598
}
599
600
void CHCSmtLib2Interface::inlineLetExpressions(SMTLib2Expression& expr)
601
0
{
602
0
  LetBindings bindings;
603
0
  ::inlineLetExpressions(expr, bindings);
604
0
}