Skip to content

Commit 25463c5

Browse files
committed
SMTChecker: Fix analysis for selected contracts
Previously, when a contract was selected for analysis, the analysis was incorrect. There were two issues. First, the contracts in the same file were considered as entry points even though they were not selected for analysis. Second, the contracts in a different file were mostly ignored, resulting in unsoundness when an external call was made to such a contract in trusted mode. The solution to the above problems is to always create representation of all contracts (in case they are called from the selected contract), but create verification targets only for the selected contracts.
1 parent 9620f4f commit 25463c5

File tree

18 files changed

+94
-128
lines changed

18 files changed

+94
-128
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
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)

libsolidity/formal/BMC.cpp

Lines changed: 1 addition & 1 deletion
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{

libsolidity/formal/CHC.cpp

Lines changed: 13 additions & 13 deletions
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;

libsolidity/formal/ModelCheckerSettings.cpp

Lines changed: 1 addition & 1 deletion
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

libsolidity/formal/SMTEncoder.cpp

Lines changed: 9 additions & 4 deletions
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)

libsolidity/formal/SMTEncoder.h

Lines changed: 6 additions & 5 deletions
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,
Lines changed: 0 additions & 26 deletions
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-
| ^^^^^^^^^^^^^

test/cmdlineTests/model_checker_contracts_only_one/err

Lines changed: 1 addition & 1 deletion
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
|

test/cmdlineTests/standard_model_checker_contracts_inexistent_contract/output.json

Lines changed: 0 additions & 66 deletions
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": {

test/cmdlineTests/standard_model_checker_contracts_multi_source/output.json

Lines changed: 2 additions & 2 deletions
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": {

0 commit comments

Comments
 (0)