Coverage Report

Created: 2026-09-14 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsmtutil/SMTPortfolio.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/SMTPortfolio.h>
20
21
#include <libsmtutil/SMTLib2Interface.h>
22
23
using namespace solidity;
24
using namespace solidity::util;
25
using namespace solidity::frontend;
26
using namespace solidity::smtutil;
27
28
SMTPortfolio::SMTPortfolio(
29
  std::vector<std::unique_ptr<BMCSolverInterface>> _solvers,
30
  std::optional<unsigned> _queryTimeout
31
):
32
15.6k
  BMCSolverInterface(_queryTimeout), m_solvers(std::move(_solvers))
33
15.6k
{}
34
35
36
void SMTPortfolio::reset()
37
0
{
38
0
  for (auto const& s: m_solvers)
39
0
    s->reset();
40
0
}
41
42
void SMTPortfolio::push()
43
7.57k
{
44
7.57k
  for (auto const& s: m_solvers)
45
7.57k
    s->push();
46
7.57k
}
47
48
void SMTPortfolio::pop()
49
7.57k
{
50
7.57k
  for (auto const& s: m_solvers)
51
7.57k
    s->pop();
52
7.57k
}
53
54
void SMTPortfolio::declareVariable(std::string const& _name, SortPointer const& _sort)
55
1.94M
{
56
1.94M
  smtAssert(_sort, "");
57
1.94M
  for (auto const& s: m_solvers)
58
1.94M
    s->declareVariable(_name, _sort);
59
1.94M
}
60
61
void SMTPortfolio::addAssertion(Expression const& _expr)
62
7.57k
{
63
7.57k
  for (auto const& s: m_solvers)
64
7.57k
    s->addAssertion(_expr);
65
7.57k
}
66
67
/*
68
 * Broadcasts the SMT query to all solvers and returns a single result.
69
 * This comment explains how this result is decided.
70
 *
71
 * When a solver is queried, there are four possible answers:
72
 *   SATISFIABLE (SAT), UNSATISFIABLE (UNSAT), UNKNOWN, CONFLICTING, ERROR
73
 * We say that a solver _answered_ the query if it returns either:
74
 *   SAT or UNSAT
75
 * A solver did not answer the query if it returns either:
76
 *   UNKNOWN (it tried but couldn't solve it) or ERROR (crash, internal error, API error, etc).
77
 *
78
 * Ideally all solvers answer the query and agree on what the answer is
79
 * (all say SAT or all say UNSAT).
80
 *
81
 * The actual logic is as follows:
82
 * 1) If at least one solver answers the query, all the non-answer results are ignored.
83
 *   Here SAT/UNSAT is preferred over UNKNOWN since it's an actual answer, and over ERROR
84
 *   because one buggy solver/integration shouldn't break the portfolio.
85
 *
86
 * 2) If at least one solver answers SAT and at least one answers UNSAT, at least one of them is buggy
87
 * and the result is CONFLICTING.
88
 *   In the future if we have more than 2 solvers enabled we could go with the majority.
89
 *
90
 * 3) If NO solver answers the query:
91
 *   If at least one solver returned UNKNOWN (where the rest returned ERROR), the result is UNKNOWN.
92
 *   This is preferred over ERROR since the SMTChecker might decide to abstract the query
93
 *   when it is told that this is a hard query to solve.
94
 *
95
 *   If all solvers return ERROR, the result is ERROR.
96
*/
97
std::pair<CheckResult, std::vector<std::string>> SMTPortfolio::check(std::vector<Expression> const& _expressionsToEvaluate)
98
7.57k
{
99
7.57k
  CheckResult lastResult = CheckResult::ERROR;
100
7.57k
  std::vector<std::string> finalValues;
101
7.57k
  for (auto const& s: m_solvers)
102
7.57k
  {
103
7.57k
    CheckResult result;
104
7.57k
    std::vector<std::string> values;
105
7.57k
    tie(result, values) = s->check(_expressionsToEvaluate);
106
7.57k
    if (solverAnswered(result))
107
0
    {
108
0
      if (!solverAnswered(lastResult))
109
0
      {
110
0
        lastResult = result;
111
0
        finalValues = std::move(values);
112
0
      }
113
0
      else if (lastResult != result)
114
0
      {
115
0
        lastResult = CheckResult::CONFLICTING;
116
0
        break;
117
0
      }
118
0
    }
119
7.57k
    else if (result == CheckResult::UNKNOWN && lastResult == CheckResult::ERROR)
120
7.57k
      lastResult = result;
121
7.57k
  }
122
7.57k
  return std::make_pair(lastResult, finalValues);
123
7.57k
}
124
125
std::vector<std::string> SMTPortfolio::unhandledQueries()
126
32.1k
{
127
  // This code assumes that the constructor guarantees that
128
  // SmtLib2Interface is in position 0, if enabled.
129
32.1k
  if (!m_solvers.empty())
130
32.1k
    if (auto smtlib2 = dynamic_cast<SMTLib2Interface*>(m_solvers.front().get()))
131
32.1k
      return smtlib2->unhandledQueries();
132
0
  return {};
133
32.1k
}
134
135
bool SMTPortfolio::solverAnswered(CheckResult result)
136
7.57k
{
137
7.57k
  return result == CheckResult::SATISFIABLE || result == CheckResult::UNSATISFIABLE;
138
7.57k
}
139
140
std::string SMTPortfolio::dumpQuery(std::vector<Expression> const& _expressionsToEvaluate)
141
0
{
142
  // This code assumes that the constructor guarantees that
143
  // SmtLib2Interface is in position 0, if enabled.
144
0
  auto smtlib2 = dynamic_cast<SMTLib2Interface*>(m_solvers.front().get());
145
  solAssert(smtlib2, "Must use SMTLib2 solver to dump queries");
146
0
  return smtlib2->dumpQuery(_expressionsToEvaluate);
147
0
}