Skip to content

Commit e18dec8

Browse files
author
Leonardo
authored
Merge pull request #11705 from ethereum/smt_show_unproved
[SMTChecker] Bundle unproved messages by default
2 parents ae519c1 + 01c01ea commit e18dec8

File tree

99 files changed

+1108
-117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1108
-117
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Compiler Features:
99
* Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables).
1010
* Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default).
1111
* Commandline Interface: option ``--pretty-json`` works also with ``--standard--json``.
12+
* SMTChecker: Unproved targets are hidden by default, and the SMTChecker only states how many unproved targets there are. They can be listed using the command line option ``--model-checker-show-unproved`` or the JSON option ``settings.modelChecker.showUnproved``.
1213

1314

1415
Bugfixes:

docs/smtchecker.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,14 @@ A common subset of targets might be, for example:
474474
There is no precise heuristic on how and when to split verification targets,
475475
but it can be useful especially when dealing with large contracts.
476476

477+
Unproved Targets
478+
================
479+
480+
If there are any unproved targets, the SMTChecker issues one warning stating
481+
how many unproved targets there are. If the user wishes to see all the specific
482+
unproved targets, the CLI option ``--model-checker-show-unproved true`` and
483+
the JSON option ``settings.modelChecker.showUnproved = true`` can be used.
484+
477485
Verified Contracts
478486
==================
479487

docs/using-the-compiler.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ Input Description
402402
},
403403
// Choose which model checker engine to use: all (default), bmc, chc, none.
404404
"engine": "chc",
405+
// Choose whether to output all unproved targets. The default is `false`.
406+
"showUnproved": true,
405407
// Choose which targets should be checked: constantCondition,
406408
// underflow, overflow, divByZero, balance, assert, popEmptyArray, outOfBounds.
407409
// If the option is not given all targets are checked by default.

libsolidity/formal/BMC.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ BMC::BMC(
6060
#endif
6161
}
6262

63-
void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<VerificationTargetType>> _solvedTargets)
63+
void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<VerificationTargetType>, smt::EncodingContext::IdCompare> _solvedTargets)
6464
{
6565
if (m_interface->solvers() == 0)
6666
{
@@ -84,8 +84,21 @@ void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<Verificatio
8484
m_context.setAssertionAccumulation(true);
8585
m_variableUsage.setFunctionInlining(shouldInlineFunctionCall);
8686
createFreeConstants(sourceDependencies(_source));
87+
m_unprovedAmt = 0;
8788

8889
_source.accept(*this);
90+
91+
if (m_unprovedAmt > 0 && !m_settings.showUnproved)
92+
m_errorReporter.warning(
93+
2788_error,
94+
{},
95+
"BMC: " +
96+
to_string(m_unprovedAmt) +
97+
" verification condition(s) could not be proved." +
98+
" Enable the model checker option \"show unproved\" to see all of them." +
99+
" Consider choosing a specific contract to be verified in order to reduce the solving problems." +
100+
" Consider increasing the timeout per query."
101+
);
89102
}
90103

91104
// If this check is true, Z3 and CVC4 are not available
@@ -961,8 +974,12 @@ void BMC::checkCondition(
961974
case smtutil::CheckResult::UNSATISFIABLE:
962975
break;
963976
case smtutil::CheckResult::UNKNOWN:
964-
m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + _description + " might happen here.", secondaryLocation);
977+
{
978+
++m_unprovedAmt;
979+
if (m_settings.showUnproved)
980+
m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + _description + " might happen here.", secondaryLocation);
965981
break;
982+
}
966983
case smtutil::CheckResult::CONFLICTING:
967984
m_errorReporter.warning(1584_error, _location, "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
968985
break;

libsolidity/formal/BMC.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class BMC: public SMTEncoder
6666
langutil::CharStreamProvider const& _charStreamProvider
6767
);
6868

69-
void analyze(SourceUnit const& _sources, std::map<ASTNode const*, std::set<VerificationTargetType>> _solvedTargets);
69+
void analyze(SourceUnit const& _sources, std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> _solvedTargets);
7070

7171
/// This is used if the SMT solver is not directly linked into this binary.
7272
/// @returns a list of inputs to the SMT solver that were not part of the argument to
@@ -192,7 +192,10 @@ class BMC: public SMTEncoder
192192
std::vector<BMCVerificationTarget> m_verificationTargets;
193193

194194
/// Targets that were already proven.
195-
std::map<ASTNode const*, std::set<VerificationTargetType>> m_solvedTargets;
195+
std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> m_solvedTargets;
196+
197+
/// Number of verification conditions that could not be proved.
198+
size_t m_unprovedAmt = 0;
196199
};
197200

198201
}

libsolidity/formal/CHC.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ void CHC::resetSourceAnalysis()
933933
{
934934
m_safeTargets.clear();
935935
m_unsafeTargets.clear();
936+
m_unprovedTargets.clear();
936937
m_functionTargetIds.clear();
937938
m_verificationTargets.clear();
938939
m_queryPlaceholders.clear();
@@ -1594,6 +1595,32 @@ void CHC::checkVerificationTargets()
15941595
checkedErrorIds.insert(target.errorId);
15951596
}
15961597

1598+
auto toReport = m_unsafeTargets;
1599+
if (m_settings.showUnproved)
1600+
for (auto const& [node, targets]: m_unprovedTargets)
1601+
for (auto const& [target, info]: targets)
1602+
toReport[node].emplace(target, info);
1603+
1604+
for (auto const& [node, targets]: toReport)
1605+
for (auto const& [target, info]: targets)
1606+
m_errorReporter.warning(
1607+
info.error,
1608+
info.location,
1609+
info.message
1610+
);
1611+
1612+
if (!m_settings.showUnproved && !m_unprovedTargets.empty())
1613+
m_errorReporter.warning(
1614+
5840_error,
1615+
{},
1616+
"CHC: " +
1617+
to_string(m_unprovedTargets.size()) +
1618+
" verification condition(s) could not be proved." +
1619+
" Enable the model checker option \"show unproved\" to see all of them." +
1620+
" Consider choosing a specific contract to be verified in order to reduce the solving problems." +
1621+
" Consider increasing the timeout per query."
1622+
);
1623+
15971624
// There can be targets in internal functions that are not reachable from the external interface.
15981625
// These are safe by definition and are not even checked by the CHC engine, but this information
15991626
// must still be reported safe by the BMC engine.
@@ -1633,27 +1660,26 @@ void CHC::checkAndReportTarget(
16331660
else if (result == CheckResult::SATISFIABLE)
16341661
{
16351662
solAssert(!_satMsg.empty(), "");
1636-
m_unsafeTargets[_target.errorNode].insert(_target.type);
16371663
auto cex = generateCounterexample(model, error().name);
16381664
if (cex)
1639-
m_errorReporter.warning(
1665+
m_unsafeTargets[_target.errorNode][_target.type] = {
16401666
_errorReporterId,
16411667
location,
16421668
"CHC: " + _satMsg + "\nCounterexample:\n" + *cex
1643-
);
1669+
};
16441670
else
1645-
m_errorReporter.warning(
1671+
m_unsafeTargets[_target.errorNode][_target.type] = {
16461672
_errorReporterId,
16471673
location,
16481674
"CHC: " + _satMsg
1649-
);
1675+
};
16501676
}
16511677
else if (!_unknownMsg.empty())
1652-
m_errorReporter.warning(
1678+
m_unprovedTargets[_target.errorNode][_target.type] = {
16531679
_errorReporterId,
16541680
location,
16551681
"CHC: " + _unknownMsg
1656-
);
1682+
};
16571683
}
16581684

16591685
/**

libsolidity/formal/CHC.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939

4040
#include <libsmtutil/CHCSolverInterface.h>
4141

42+
#include <liblangutil/SourceLocation.h>
43+
4244
#include <boost/algorithm/string/join.hpp>
4345

4446
#include <map>
@@ -62,8 +64,14 @@ class CHC: public SMTEncoder
6264

6365
void analyze(SourceUnit const& _sources);
6466

65-
std::map<ASTNode const*, std::set<VerificationTargetType>> const& safeTargets() const { return m_safeTargets; }
66-
std::map<ASTNode const*, std::set<VerificationTargetType>> const& unsafeTargets() const { return m_unsafeTargets; }
67+
struct ReportTargetInfo
68+
{
69+
langutil::ErrorId error;
70+
langutil::SourceLocation location;
71+
std::string message;
72+
};
73+
std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> const& safeTargets() const { return m_safeTargets; }
74+
std::map<ASTNode const*, std::map<VerificationTargetType, ReportTargetInfo>, smt::EncodingContext::IdCompare> const& unsafeTargets() const { return m_unsafeTargets; }
6775

6876
/// This is used if the Horn solver is not directly linked into this binary.
6977
/// @returns a list of inputs to the Horn solver that were not part of the argument to
@@ -347,10 +355,12 @@ class CHC: public SMTEncoder
347355
/// Helper mapping unique IDs to actual verification targets.
348356
std::map<unsigned, CHCVerificationTarget> m_verificationTargets;
349357

350-
/// Targets proven safe.
351-
std::map<ASTNode const*, std::set<VerificationTargetType>> m_safeTargets;
352-
/// Targets proven unsafe.
353-
std::map<ASTNode const*, std::set<VerificationTargetType>> m_unsafeTargets;
358+
/// Targets proved safe.
359+
std::map<ASTNode const*, std::set<VerificationTargetType>, smt::EncodingContext::IdCompare> m_safeTargets;
360+
/// Targets proved unsafe.
361+
std::map<ASTNode const*, std::map<VerificationTargetType, ReportTargetInfo>, smt::EncodingContext::IdCompare> m_unsafeTargets;
362+
/// Targets not proved.
363+
std::map<ASTNode const*, std::map<VerificationTargetType, ReportTargetInfo>, smt::EncodingContext::IdCompare> m_unprovedTargets;
354364
//@}
355365

356366
/// Control-flow.

libsolidity/formal/ModelChecker.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ void ModelChecker::analyze(SourceUnit const& _source)
120120
m_chc.analyze(_source);
121121

122122
auto solvedTargets = m_chc.safeTargets();
123-
for (auto const& target: m_chc.unsafeTargets())
124-
solvedTargets[target.first] += target.second;
123+
for (auto const& [node, targets]: m_chc.unsafeTargets())
124+
solvedTargets[node] += targets | ranges::views::keys;
125125

126126
if (m_settings.engine.bmc)
127127
m_bmc.analyze(_source, solvedTargets);

libsolidity/formal/ModelCheckerSettings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ struct ModelCheckerSettings
113113
{
114114
ModelCheckerContracts contracts = ModelCheckerContracts::Default();
115115
ModelCheckerEngine engine = ModelCheckerEngine::None();
116+
bool showUnproved = false;
116117
smtutil::SMTSolverChoice solvers = smtutil::SMTSolverChoice::All();
117118
ModelCheckerTargets targets = ModelCheckerTargets::Default();
118119
std::optional<unsigned> timeout;
@@ -123,6 +124,7 @@ struct ModelCheckerSettings
123124
return
124125
contracts == _other.contracts &&
125126
engine == _other.engine &&
127+
showUnproved == _other.showUnproved &&
126128
solvers == _other.solvers &&
127129
targets == _other.targets &&
128130
timeout == _other.timeout;

libsolidity/interface/StandardCompiler.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
442442

443443
std::optional<Json::Value> checkModelCheckerSettingsKeys(Json::Value const& _input)
444444
{
445-
static set<string> keys{"contracts", "engine", "solvers", "targets", "timeout"};
445+
static set<string> keys{"contracts", "engine", "showUnproved", "solvers", "targets", "timeout"};
446446
return checkKeys(_input, keys, "modelChecker");
447447
}
448448

@@ -951,6 +951,14 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
951951
ret.modelCheckerSettings.engine = *engine;
952952
}
953953

954+
if (modelCheckerSettings.isMember("showUnproved"))
955+
{
956+
auto const& showUnproved = modelCheckerSettings["showUnproved"];
957+
if (!showUnproved.isBool())
958+
return formatFatalError("JSONError", "settings.modelChecker.showUnproved must be a Boolean value.");
959+
ret.modelCheckerSettings.showUnproved = showUnproved.asBool();
960+
}
961+
954962
if (modelCheckerSettings.isMember("solvers"))
955963
{
956964
auto const& solversArray = modelCheckerSettings["solvers"];

0 commit comments

Comments
 (0)