Skip to content

Commit 8caf354

Browse files
authored
Merge pull request ethereum#15862 from ethereum/smt-fix-loops-in-bmc
SMTChecker: Loop conditions should be analyzed as in loop context in BMC
2 parents 2ab62d7 + d9083dc commit 8caf354

5 files changed

+119
-91
lines changed

Changelog.md

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

99

1010
Bugfixes:
11+
* SMTChecker: Do not consider loop conditions as constant-condition verification target as this could cause incorrect reports and internal compiler errors.
1112
* SMTChecker: Fix incorrect analysis when only a subset of contracts is selected with `--model-checker-contracts`.
1213
* SMTChecker: Fix internal compiler error when string literal is used to initialize user-defined type based on fixed bytes.
1314

libsolidity/formal/BMC.cpp

+75-82
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,8 @@ bool BMC::visit(IfStatement const& _node)
247247
m_context.pushSolver();
248248
_node.condition().accept(*this);
249249

250-
// We ignore called functions here because they have
251-
// specific input values.
252-
if (isRootFunction() && !isInsideLoop())
253-
addVerificationTarget(
254-
VerificationTargetType::ConstantCondition,
255-
expr(_node.condition()),
256-
&_node.condition()
257-
);
250+
checkIfConditionIsConstant(_node.condition());
251+
258252
m_context.popSolver();
259253
resetVariableIndices(std::move(indicesBeforePush));
260254

@@ -283,13 +277,7 @@ bool BMC::visit(Conditional const& _op)
283277
auto indicesBeforePush = copyVariableIndices();
284278
m_context.pushSolver();
285279
_op.condition().accept(*this);
286-
287-
if (isRootFunction() && !isInsideLoop())
288-
addVerificationTarget(
289-
VerificationTargetType::ConstantCondition,
290-
expr(_op.condition()),
291-
&_op.condition()
292-
);
280+
checkIfConditionIsConstant(_op.condition());
293281
m_context.popSolver();
294282
resetVariableIndices(std::move(indicesBeforePush));
295283

@@ -375,7 +363,9 @@ bool BMC::visit(WhileStatement const& _node)
375363
{
376364
//after loop iterations are done, we check the loop condition last final time
377365
auto indices = copyVariableIndices();
366+
m_loopCheckpoints.emplace();
378367
_node.condition().accept(*this);
368+
m_loopCheckpoints.pop();
379369
loopCondition = expr(_node.condition());
380370
// assert that the loop is complete
381371
m_context.addAssertion(!loopCondition || broke || !loopConditionOnPreviousIterations);
@@ -403,13 +393,13 @@ bool BMC::visit(ForStatement const& _node)
403393
for (unsigned int i = 0; i < bmcLoopIterations; ++i)
404394
{
405395
auto indicesBefore = copyVariableIndices();
396+
m_loopCheckpoints.emplace();
406397
if (_node.condition())
407398
{
408399
_node.condition()->accept(*this);
409400
// values in loop condition might change during loop iteration
410401
forCondition = expr(*_node.condition());
411402
}
412-
m_loopCheckpoints.emplace();
413403
auto indicesAfterCondition = copyVariableIndices();
414404

415405
pushPathCondition(forCondition);
@@ -455,8 +445,10 @@ bool BMC::visit(ForStatement const& _node)
455445
auto indices = copyVariableIndices();
456446
if (_node.condition())
457447
{
448+
m_loopCheckpoints.emplace();
458449
_node.condition()->accept(*this);
459450
forCondition = expr(*_node.condition());
451+
m_loopCheckpoints.pop();
460452
}
461453
// assert that the loop is complete
462454
m_context.addAssertion(!forCondition || broke || !forConditionOnPreviousIterations);
@@ -690,12 +682,7 @@ void BMC::visitRequire(FunctionCall const& _funCall)
690682
auto const& args = _funCall.arguments();
691683
solAssert(args.size() >= 1, "");
692684
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
693-
if (isRootFunction() && !isInsideLoop())
694-
addVerificationTarget(
695-
VerificationTargetType::ConstantCondition,
696-
expr(*args.front()),
697-
args.front().get()
698-
);
685+
checkIfConditionIsConstant(*args.front());
699686
}
700687

701688
void BMC::visitAddMulMod(FunctionCall const& _funCall)
@@ -933,9 +920,6 @@ void BMC::checkVerificationTarget(BMCVerificationTarget& _target)
933920

934921
switch (_target.type)
935922
{
936-
case VerificationTargetType::ConstantCondition:
937-
checkConstantCondition(_target);
938-
break;
939923
case VerificationTargetType::Underflow:
940924
checkUnderflow(_target);
941925
break;
@@ -951,19 +935,70 @@ void BMC::checkVerificationTarget(BMCVerificationTarget& _target)
951935
case VerificationTargetType::Assert:
952936
checkAssert(_target);
953937
break;
938+
case VerificationTargetType::ConstantCondition:
939+
smtAssert(false, "Checks for constant condition are handled separately");
954940
default:
955-
solAssert(false, "");
941+
smtAssert(false);
956942
}
957943
}
958944

959-
void BMC::checkConstantCondition(BMCVerificationTarget& _target)
945+
void BMC::checkIfConditionIsConstant(Expression const& _condition)
960946
{
961-
checkBooleanNotConstant(
962-
*_target.expression,
963-
_target.constraints,
964-
_target.value,
965-
_target.callStack
966-
);
947+
if (
948+
!m_settings.targets.has(VerificationTargetType::ConstantCondition) ||
949+
(m_currentContract && !shouldEncode(*m_currentContract))
950+
)
951+
return;
952+
953+
// We ignore called functions here because they have specific input values.
954+
// Also, expressions inside loop can have different values in different iterations.
955+
if (!isRootFunction() || isInsideLoop())
956+
return;
957+
958+
// Do not check for const-ness if this is a literal.
959+
if (dynamic_cast<Literal const*>(&_condition))
960+
return;
961+
962+
auto [canBeTrue, canBeFalse] = checkBooleanNotConstant(currentPathConditions() && m_context.assertions(), expr(_condition));
963+
964+
// Report based on the result of the checks
965+
if (canBeTrue == CheckResult::ERROR || canBeFalse == CheckResult::ERROR)
966+
m_errorReporter.warning(8592_error, _condition.location(), "BMC: Error trying to invoke SMT solver.");
967+
else if (canBeTrue == CheckResult::CONFLICTING || canBeFalse == CheckResult::CONFLICTING)
968+
m_errorReporter.warning(3356_error, _condition.location(), "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
969+
else if (canBeTrue == CheckResult::UNKNOWN || canBeFalse == CheckResult::UNKNOWN)
970+
{
971+
// Not enough information to make definite claims.
972+
}
973+
else if (canBeTrue == CheckResult::SATISFIABLE && canBeFalse == CheckResult::SATISFIABLE)
974+
{
975+
// Condition can be both true and false for some program runs.
976+
}
977+
978+
else if (canBeTrue == CheckResult::UNSATISFIABLE && canBeFalse == CheckResult::UNSATISFIABLE)
979+
m_errorReporter.warning(2512_error, _condition.location(), "BMC: Condition unreachable.", SMTEncoder::callStackMessage(m_callStack));
980+
else
981+
{
982+
std::string description;
983+
if (canBeFalse == smtutil::CheckResult::UNSATISFIABLE)
984+
{
985+
smtAssert(canBeTrue == smtutil::CheckResult::SATISFIABLE);
986+
description = "BMC: Condition is always true.";
987+
}
988+
else
989+
{
990+
smtAssert(canBeTrue == smtutil::CheckResult::UNSATISFIABLE);
991+
smtAssert(canBeFalse == smtutil::CheckResult::SATISFIABLE);
992+
description = "BMC: Condition is always false.";
993+
}
994+
m_errorReporter.warning(
995+
6838_error,
996+
_condition.location(),
997+
description,
998+
SMTEncoder::callStackMessage(m_callStack)
999+
);
1000+
}
1001+
9671002
}
9681003

9691004
void BMC::checkUnderflow(BMCVerificationTarget& _target)
@@ -1068,6 +1103,7 @@ void BMC::addVerificationTarget(
10681103
Expression const* _expression
10691104
)
10701105
{
1106+
smtAssert(_type != VerificationTargetType::ConstantCondition, "Checks for constant condition are handled separately");
10711107
if (!m_settings.targets.has(_type) || (m_currentContract && !shouldAnalyzeVerificationTargetsFor(*m_currentContract)))
10721108
return;
10731109

@@ -1081,10 +1117,7 @@ void BMC::addVerificationTarget(
10811117
m_callStack,
10821118
modelExpressions()
10831119
};
1084-
if (_type == VerificationTargetType::ConstantCondition)
1085-
checkVerificationTarget(target);
1086-
else
1087-
m_verificationTargets.emplace_back(std::move(target));
1120+
m_verificationTargets.emplace_back(std::move(target));
10881121
}
10891122

10901123
/// Solving.
@@ -1188,62 +1221,22 @@ void BMC::checkCondition(
11881221
m_interface->pop();
11891222
}
11901223

1191-
void BMC::checkBooleanNotConstant(
1192-
Expression const& _condition,
1224+
BMC::ConstantExpressionCheckResult BMC::checkBooleanNotConstant(
11931225
smtutil::Expression const& _constraints,
1194-
smtutil::Expression const& _value,
1195-
std::vector<SMTEncoder::CallStackEntry> const& _callStack
1226+
smtutil::Expression const& _condition
11961227
)
11971228
{
1198-
// Do not check for const-ness if this is a constant.
1199-
if (dynamic_cast<Literal const*>(&_condition))
1200-
return;
1201-
12021229
m_interface->push();
1203-
m_interface->addAssertion(_constraints && _value);
1230+
m_interface->addAssertion(_constraints && _condition);
12041231
auto positiveResult = checkSatisfiable();
12051232
m_interface->pop();
12061233

12071234
m_interface->push();
1208-
m_interface->addAssertion(_constraints && !_value);
1235+
m_interface->addAssertion(_constraints && !_condition);
12091236
auto negatedResult = checkSatisfiable();
12101237
m_interface->pop();
12111238

1212-
if (positiveResult == smtutil::CheckResult::ERROR || negatedResult == smtutil::CheckResult::ERROR)
1213-
m_errorReporter.warning(8592_error, _condition.location(), "BMC: Error trying to invoke SMT solver.");
1214-
else if (positiveResult == smtutil::CheckResult::CONFLICTING || negatedResult == smtutil::CheckResult::CONFLICTING)
1215-
m_errorReporter.warning(3356_error, _condition.location(), "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
1216-
else if (positiveResult == smtutil::CheckResult::SATISFIABLE && negatedResult == smtutil::CheckResult::SATISFIABLE)
1217-
{
1218-
// everything fine.
1219-
}
1220-
else if (positiveResult == smtutil::CheckResult::UNKNOWN || negatedResult == smtutil::CheckResult::UNKNOWN)
1221-
{
1222-
// can't do anything.
1223-
}
1224-
else if (positiveResult == smtutil::CheckResult::UNSATISFIABLE && negatedResult == smtutil::CheckResult::UNSATISFIABLE)
1225-
m_errorReporter.warning(2512_error, _condition.location(), "BMC: Condition unreachable.", SMTEncoder::callStackMessage(_callStack));
1226-
else
1227-
{
1228-
std::string description;
1229-
if (positiveResult == smtutil::CheckResult::SATISFIABLE)
1230-
{
1231-
solAssert(negatedResult == smtutil::CheckResult::UNSATISFIABLE, "");
1232-
description = "BMC: Condition is always true.";
1233-
}
1234-
else
1235-
{
1236-
solAssert(positiveResult == smtutil::CheckResult::UNSATISFIABLE, "");
1237-
solAssert(negatedResult == smtutil::CheckResult::SATISFIABLE, "");
1238-
description = "BMC: Condition is always false.";
1239-
}
1240-
m_errorReporter.warning(
1241-
6838_error,
1242-
_condition.location(),
1243-
description,
1244-
SMTEncoder::callStackMessage(_callStack)
1245-
);
1246-
}
1239+
return {.canBeTrue = positiveResult, .canBeFalse = negatedResult};
12471240
}
12481241

12491242
std::pair<smtutil::CheckResult, std::vector<std::string>>

libsolidity/formal/BMC.h

+21-9
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ class BMC: public SMTEncoder
161161

162162
void checkVerificationTargets();
163163
void checkVerificationTarget(BMCVerificationTarget& _target);
164-
void checkConstantCondition(BMCVerificationTarget& _target);
165164
void checkUnderflow(BMCVerificationTarget& _target);
166165
void checkOverflow(BMCVerificationTarget& _target);
167166
void checkDivByZero(BMCVerificationTarget& _target);
@@ -172,8 +171,13 @@ class BMC: public SMTEncoder
172171
smtutil::Expression const& _value,
173172
Expression const* _expression
174173
);
174+
/// Special handling of ConstantCondition verification target.
175+
/// The target is checked immediately, unlike the other targets that are queued for checking at the end of analysis.
176+
void checkIfConditionIsConstant(Expression const& _condition);
175177
//@}
176178

179+
180+
177181
/// Solver related.
178182
//@{
179183
/// Check that a condition can be satisfied.
@@ -188,13 +192,18 @@ class BMC: public SMTEncoder
188192
std::string const& _additionalValueName = "",
189193
smtutil::Expression const* _additionalValue = nullptr
190194
);
191-
/// Checks that a boolean condition is not constant. Do not warn if the expression
192-
/// is a literal constant.
193-
void checkBooleanNotConstant(
194-
Expression const& _condition,
195+
196+
struct ConstantExpressionCheckResult
197+
{
198+
smtutil::CheckResult canBeTrue;
199+
smtutil::CheckResult canBeFalse;
200+
};
201+
202+
/// Checks whether the given boolean condition is either true or always false under given constraints.
203+
/// Returns the results from the solver for these two checks.
204+
ConstantExpressionCheckResult checkBooleanNotConstant(
195205
smtutil::Expression const& _constraints,
196-
smtutil::Expression const& _value,
197-
std::vector<CallStackEntry> const& _callStack
206+
smtutil::Expression const& _condition
198207
);
199208
std::pair<smtutil::CheckResult, std::vector<std::string>>
200209
checkSatisfiableAndGenerateModel(std::vector<smtutil::Expression> const& _expressionsToEvaluate);
@@ -222,20 +231,23 @@ class BMC: public SMTEncoder
222231
/// Number of verification conditions that could not be proved.
223232
size_t m_unprovedAmt = 0;
224233

234+
/// Loop analysis
235+
//@{
225236
enum class LoopControlKind
226237
{
227238
Continue,
228239
Break
229240
};
230241

231-
// Current path conditions and SSA indices for break or continue statement
242+
/// Current path conditions and SSA indices for break or continue statement
232243
struct LoopControl {
233244
LoopControlKind kind;
234245
smtutil::Expression pathConditions;
235246
VariableIndices variableIndices;
236247
};
237248

238-
// Loop control statements for every loop
249+
/// Loop control statements for every loop
239250
std::stack<std::vector<LoopControl>> m_loopCheckpoints;
251+
//@}
240252
};
241253
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
contract Test {
2+
function loop() public pure {
3+
for (uint k = 0; (k == 0 ? true : false); k++) {
4+
}
5+
}
6+
}
7+
// ====
8+
// SMTEngine: bmc
9+
// SMTTargets: constantCondition
10+
// ----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
contract Test {
2+
function loop() public pure {
3+
uint k = 0;
4+
while (k == 0 ? true : false) {
5+
++k;
6+
}
7+
}
8+
}
9+
// ====
10+
// SMTEngine: bmc
11+
// SMTTargets: constantCondition
12+
// ----

0 commit comments

Comments
 (0)