Skip to content

Commit b53eba3

Browse files
authored
Merge pull request #15880 from ethereum/smt-fix-contract-selection
SMTChecker: Fix analysis for selected contracts
2 parents 9620f4f + 25463c5 commit b53eba3

18 files changed

+94
-128
lines changed

Diff for: Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Compiler Features:
77

88

99
Bugfixes:
10+
* SMTChecker: Fix incorrect analysis when only a subset of contracts is selected with `--model-checker-contracts`.
1011

1112

1213
### 0.8.29 (2025-03-12)

Diff for: libsolidity/formal/BMC.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ void BMC::addVerificationTarget(
10681068
Expression const* _expression
10691069
)
10701070
{
1071-
if (!m_settings.targets.has(_type) || (m_currentContract && !shouldAnalyze(*m_currentContract)))
1071+
if (!m_settings.targets.has(_type) || (m_currentContract && !shouldAnalyzeVerificationTargetsFor(*m_currentContract)))
10721072
return;
10731073

10741074
BMCVerificationTarget target{

Diff for: libsolidity/formal/CHC.cpp

+13-13
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ void CHC::analyze(SourceUnit const& _source)
8989
" If you wish to use Eldarica, please enable Eldarica only."
9090
);
9191

92-
if (!shouldAnalyze(_source))
92+
if (!shouldAnalyzeVerificationTargetsFor(_source))
9393
return;
9494

9595
resetSourceAnalysis();
@@ -131,7 +131,7 @@ std::vector<std::string> CHC::unhandledQueries() const
131131

132132
bool CHC::visit(ContractDefinition const& _contract)
133133
{
134-
if (!shouldAnalyze(_contract))
134+
if (!shouldEncode(_contract))
135135
return false;
136136

137137
// Raises UnimplementedFeatureError in the presence of transient storage variables
@@ -152,7 +152,7 @@ bool CHC::visit(ContractDefinition const& _contract)
152152

153153
void CHC::endVisit(ContractDefinition const& _contract)
154154
{
155-
if (!shouldAnalyze(_contract))
155+
if (!shouldEncode(_contract))
156156
return;
157157

158158
for (auto base: _contract.annotation().linearizedBaseContracts)
@@ -239,15 +239,14 @@ void CHC::endVisit(ContractDefinition const& _contract)
239239
setCurrentBlock(*m_constructorSummaries.at(&_contract));
240240

241241
solAssert(&_contract == m_currentContract, "");
242-
if (shouldAnalyze(_contract))
243-
{
244-
auto constructor = _contract.constructor();
245-
auto txConstraints = state().txTypeConstraints();
246-
if (!constructor || !constructor->isPayable())
247-
txConstraints = txConstraints && state().txNonPayableConstraint();
242+
smtAssert(shouldEncode(_contract));
243+
auto constructor = _contract.constructor();
244+
auto txConstraints = state().txTypeConstraints();
245+
if (!constructor || !constructor->isPayable())
246+
txConstraints = txConstraints && state().txNonPayableConstraint();
247+
connectBlocks(m_currentBlock, interface(), txConstraints && errorFlag().currentValue() == 0);
248+
if (shouldAnalyzeVerificationTargetsFor(_contract))
248249
m_queryPlaceholders[&_contract].push_back({txConstraints, errorFlag().currentValue(), m_currentBlock});
249-
connectBlocks(m_currentBlock, interface(), txConstraints && errorFlag().currentValue() == 0);
250-
}
251250

252251
solAssert(m_scopes.back() == &_contract, "");
253252
m_scopes.pop_back();
@@ -338,7 +337,7 @@ void CHC::endVisit(FunctionDefinition const& _function)
338337
!_function.isConstructor() &&
339338
_function.isPublic() &&
340339
contractFunctions(*m_currentContract).count(&_function) &&
341-
shouldAnalyze(*m_currentContract)
340+
shouldEncode(*m_currentContract)
342341
)
343342
{
344343
defineExternalFunctionInterface(_function, *m_currentContract);
@@ -349,8 +348,9 @@ void CHC::endVisit(FunctionDefinition const& _function)
349348
auto ifacePre = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context);
350349
auto sum = externalSummary(_function);
351350

352-
m_queryPlaceholders[&_function].push_back({sum, errorFlag().currentValue(), ifacePre});
353351
connectBlocks(ifacePre, interface(), sum && errorFlag().currentValue() == 0);
352+
if (shouldAnalyzeVerificationTargetsFor(*m_currentContract))
353+
m_queryPlaceholders[&_function].push_back({sum, errorFlag().currentValue(), ifacePre});
354354
}
355355

356356
m_currentFunction = nullptr;

Diff for: libsolidity/formal/ModelCheckerSettings.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ std::optional<ModelCheckerContracts> ModelCheckerContracts::fromString(std::stri
123123
{
124124
auto&& names = sourceContract | ranges::views::split(':') | ranges::to<std::vector<std::string>>();
125125
if (names.size() != 2 || names.at(0).empty() || names.at(1).empty())
126-
return {};
126+
return std::nullopt;
127127
chosen[names.at(0)].insert(names.at(1));
128128
}
129129

Diff for: libsolidity/formal/SMTEncoder.cpp

+9-4
Original file line numberDiff line numberDiff line change
@@ -1092,19 +1092,24 @@ void SMTEncoder::visitPublicGetter(FunctionCall const& _funCall)
10921092
}
10931093
}
10941094

1095-
bool SMTEncoder::shouldAnalyze(SourceUnit const& _source) const
1095+
bool SMTEncoder::shouldEncode(ContractDefinition const& _contract) const
1096+
{
1097+
return _contract.canBeDeployed();
1098+
}
1099+
1100+
bool SMTEncoder::shouldAnalyzeVerificationTargetsFor(SourceUnit const& _source) const
10961101
{
10971102
return m_settings.contracts.isDefault() ||
10981103
m_settings.contracts.has(*_source.annotation().path);
10991104
}
11001105

1101-
bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const
1106+
bool SMTEncoder::shouldAnalyzeVerificationTargetsFor(ContractDefinition const& _contract) const
11021107
{
1103-
if (!_contract.canBeDeployed())
1108+
if (!shouldEncode(_contract))
11041109
return false;
11051110

11061111
return m_settings.contracts.isDefault() ||
1107-
m_settings.contracts.has(_contract.sourceUnitName());
1112+
m_settings.contracts.has(_contract.sourceUnitName(), _contract.name());
11081113
}
11091114

11101115
void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)

Diff for: libsolidity/formal/SMTEncoder.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,12 @@ class SMTEncoder: public ASTConstVisitor
238238
void visitFunctionIdentifier(Identifier const& _identifier);
239239
virtual void visitPublicGetter(FunctionCall const& _funCall);
240240

241-
/// @returns true if @param _contract is set for analysis in the settings
242-
/// and it is not abstract.
243-
bool shouldAnalyze(ContractDefinition const& _contract) const;
244-
/// @returns true if @param _source is set for analysis in the settings.
245-
bool shouldAnalyze(SourceUnit const& _source) const;
241+
/// @returns true if symbolic representation of @param _contract is required for verification
242+
bool shouldEncode(ContractDefinition const& _contract) const;
243+
/// @returns true if the verification targets of @param _contract are actually selected for verification
244+
bool shouldAnalyzeVerificationTargetsFor(ContractDefinition const& _contract) const;
245+
/// @returns true if we should descend into @param _source to look for contracts that should be verified
246+
bool shouldAnalyzeVerificationTargetsFor(SourceUnit const& _source) const;
246247

247248
/// @returns the state variable returned by a public getter if
248249
/// @a _expr is a call to a public getter,
Original file line numberDiff line numberDiff line change
@@ -1,27 +1 @@
11
Warning: Requested contract "C" does not exist in source "model_checker_contracts_inexistent_contract/input.sol".
2-
3-
Warning: CHC: Assertion violation happens here.
4-
Counterexample:
5-
6-
x = 0
7-
8-
Transaction trace:
9-
B.constructor()
10-
B.f(0)
11-
--> model_checker_contracts_inexistent_contract/input.sol:5:3:
12-
|
13-
5 | assert(x > 0);
14-
| ^^^^^^^^^^^^^
15-
16-
Warning: CHC: Assertion violation happens here.
17-
Counterexample:
18-
19-
y = 0
20-
21-
Transaction trace:
22-
A.constructor()
23-
A.g(0)
24-
--> model_checker_contracts_inexistent_contract/input.sol:10:3:
25-
|
26-
10 | assert(y > 0);
27-
| ^^^^^^^^^^^^^

Diff for: test/cmdlineTests/model_checker_contracts_only_one/err

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Counterexample:
44
x = 0
55

66
Transaction trace:
7-
B.constructor()
7+
A.constructor()
88
B.f(0)
99
--> model_checker_contracts_only_one/input.sol:5:3:
1010
|

Diff for: test/cmdlineTests/standard_model_checker_contracts_inexistent_contract/output.json

-66
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,6 @@
99
"message": "Requested contract \"C\" does not exist in source \"Source\".",
1010
"severity": "warning",
1111
"type": "Warning"
12-
},
13-
{
14-
"component": "general",
15-
"errorCode": "6328",
16-
"formattedMessage": "Warning: CHC: Assertion violation happens here.
17-
Counterexample:
18-
19-
y = 0
20-
21-
Transaction trace:
22-
B.constructor()
23-
B.g(0)
24-
--> Source:5:7:
25-
|
26-
5 | \t\t\t\t\t\tassert(y > 0);
27-
| \t\t\t\t\t\t^^^^^^^^^^^^^
28-
29-
",
30-
"message": "CHC: Assertion violation happens here.
31-
Counterexample:
32-
33-
y = 0
34-
35-
Transaction trace:
36-
B.constructor()
37-
B.g(0)",
38-
"severity": "warning",
39-
"sourceLocation": {
40-
"end": 137,
41-
"file": "Source",
42-
"start": 124
43-
},
44-
"type": "Warning"
45-
},
46-
{
47-
"component": "general",
48-
"errorCode": "6328",
49-
"formattedMessage": "Warning: CHC: Assertion violation happens here.
50-
Counterexample:
51-
52-
x = 0
53-
54-
Transaction trace:
55-
A.constructor()
56-
A.f(0)
57-
--> Source:10:7:
58-
|
59-
10 | \t\t\t\t\t\tassert(x > 0);
60-
| \t\t\t\t\t\t^^^^^^^^^^^^^
61-
62-
",
63-
"message": "CHC: Assertion violation happens here.
64-
Counterexample:
65-
66-
x = 0
67-
68-
Transaction trace:
69-
A.constructor()
70-
A.f(0)",
71-
"severity": "warning",
72-
"sourceLocation": {
73-
"end": 231,
74-
"file": "Source",
75-
"start": 218
76-
},
77-
"type": "Warning"
7812
}
7913
],
8014
"sources": {

Diff for: test/cmdlineTests/standard_model_checker_contracts_multi_source/output.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Counterexample:
99
y = 0
1010

1111
Transaction trace:
12-
B.constructor()
12+
A.constructor()
1313
B.g(0)
1414
--> Source:5:7:
1515
|
@@ -23,7 +23,7 @@ Counterexample:
2323
y = 0
2424

2525
Transaction trace:
26-
B.constructor()
26+
A.constructor()
2727
B.g(0)",
2828
"severity": "warning",
2929
"sourceLocation": {

Diff for: test/cmdlineTests/standard_model_checker_contracts_only_one/output.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Counterexample:
99
y = 0
1010

1111
Transaction trace:
12-
B.constructor()
12+
A.constructor()
1313
B.g(0)
1414
--> Source:5:7:
1515
|
@@ -23,7 +23,7 @@ Counterexample:
2323
y = 0
2424

2525
Transaction trace:
26-
B.constructor()
26+
A.constructor()
2727
B.g(0)",
2828
"severity": "warning",
2929
"sourceLocation": {

Diff for: test/libsolidity/SMTCheckerTest.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,17 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename):
3232
universalCallback(nullptr, smtCommand)
3333
{
3434
auto contract = m_reader.stringSetting("SMTContract", "");
35-
if (!contract.empty())
35+
auto maybeContracts = ModelCheckerContracts::fromString(contract);
36+
auto isValidContractName = [](std::string const& _name)
37+
{
38+
return !_name.empty() && _name.find_first_of(" \t\n\r:,") == std::string::npos;
39+
};
40+
if (maybeContracts)
41+
m_modelCheckerSettings.contracts = *maybeContracts;
42+
else if (isValidContractName(contract))
3643
m_modelCheckerSettings.contracts.contracts[""] = {contract};
44+
else
45+
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid contract specified in SMTContract setting."));
3746

3847
auto extCallsMode = ModelCheckerExtCalls::fromString(m_reader.stringSetting("SMTExtCalls", "untrusted"));
3948
if (extCallsMode)

Diff for: test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_1_trusted.sol

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
contract State {
22
function f(uint _x) public pure returns (uint) {
3-
assert(_x < 100); // should fail
3+
assert(_x < 100); // should hold when analyzing only contract C (can fail only when analyzing State as standalone contract)
44
return _x;
55
}
66
}
@@ -18,5 +18,4 @@ contract C {
1818
// SMTExtCalls: trusted
1919
// SMTIgnoreInv: yes
2020
// ----
21-
// Warning 6328: (69-85): CHC: Assertion violation happens here.
22-
// Info 1391: CHC: 1 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them.
21+
// Info 1391: CHC: 2 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them.

Diff for: test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3_trusted.sol

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
contract State {
22
function f(uint _x) public pure returns (uint) {
3-
assert(_x < 100); // should fail
3+
assert(_x < 100); // should hold when analyzing only contract C (can fail only when analyzing State as standalone contract)
44
return _x;
55
}
66
}
@@ -21,5 +21,4 @@ contract C {
2121
// SMTEngine: all
2222
// SMTExtCalls: trusted
2323
// ----
24-
// Warning 6328: (69-85): CHC: Assertion violation happens here.
25-
// Info 1391: CHC: 1 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them.
24+
// Info 1391: CHC: 2 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
==== Source: A.sol ====
2+
contract A { function a() pure public { assert(false); } }
3+
==== Source: B.sol ====
4+
import "A.sol";
5+
contract B { function b(A a) pure public { a.a(); } }
6+
// ====
7+
// SMTContract: B.sol:B
8+
// SMTEngine: chc
9+
// SMTExtCalls: trusted
10+
// ----
11+
// Warning 6328: (A.sol:40-53): CHC: Assertion violation happens here.\nCounterexample:\n\na = 0\n\nTransaction trace:\nB.constructor()\nB.b(0)\n A.a() -- trusted external call
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
==== Source: A.sol ====
2+
contract A { function a() pure public { assert(false); } }
3+
==== Source: B.sol ====
4+
import "A.sol";
5+
contract B { function b() pure public { } }
6+
// ====
7+
// SMTContract: B.sol:B
8+
// SMTEngine: chc
9+
// ----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
contract B {
2+
constructor() { fail(); }
3+
4+
function fail() pure internal { assert(false); }
5+
}
6+
7+
contract A {
8+
function safe() pure public { }
9+
}
10+
// ====
11+
// SMTContract: A
12+
// SMTEngine: chc
13+
// ----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
contract B {
2+
function fail() pure public { assert(false); }
3+
}
4+
5+
contract A {
6+
function safe() pure public { }
7+
}
8+
// ====
9+
// SMTContract: A
10+
// SMTEngine: chc
11+
// ----

0 commit comments

Comments
 (0)