Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SMTChecker: Loop conditions should be analyzed as in loop context in BMC #15862

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

blishko
Copy link
Contributor

@blishko blishko commented Feb 13, 2025

Value of loop condition can change between iterations of the loop.
Therefore, BMC should be in the "inside loop" state when analyzing loop conditions.

Previously, when visiting loop condition of a top-level loop, BMC engine did not consider itself being "inside loop", and therefore was checking if the condition is constant (always true or always false).
When the condition is always true in the first iteration and always false in the second iteration, BMC would try to report these two contradicting statements for the same source code expression, leading to an assertion violation in the UniqueErrorReporter.
Because the value of the condition can change between iteration, BMC should not try to check for constant condition in these cases.

As part of this PR the handling of the ConstantCondition verification target has been refactored (first commit).

Fixes #15847.

@blishko blishko added smt 🟡 PR review label labels Feb 13, 2025
@blishko blishko force-pushed the smt-fix-loops-in-bmc branch 3 times, most recently from 7a9f978 to ff5ef45 Compare February 17, 2025 17:31
@github-actions github-actions bot added the stale The issue/PR was marked as stale because it has been open for too long. label Mar 4, 2025
@blishko blishko removed the stale The issue/PR was marked as stale because it has been open for too long. label Mar 4, 2025
@blishko blishko force-pushed the smt-fix-loops-in-bmc branch from ff5ef45 to b92ef6b Compare March 5, 2025 13:22
@ethereum ethereum deleted a comment from github-actions bot Mar 7, 2025

if (
!m_settings.targets.has(VerificationTargetType::ConstantCondition) ||
(m_currentContract && !shouldAnalyze(*m_currentContract))
Copy link
Member

@cameel cameel Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the change from #15880, I guess we don't want this check to be bypassed simply because it's in an unselected contract, right?

Suggested change
(m_currentContract && !shouldAnalyze(*m_currentContract))
(m_currentContract && !m_currentContract->canBeDeployed())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct! The code has been updated.

Comment on lines 958 to 959
// Do not check for const-ness if this is a constant.
if (dynamic_cast<Literal const*>(&_condition))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like this will not exclude constants (bool constant C = true;), just literals (true), right?

Suggested change
// Do not check for const-ness if this is a constant.
if (dynamic_cast<Literal const*>(&_condition))
// Do not check for const-ness if this is a literal.
if (dynamic_cast<Literal const*>(&_condition))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still confused about constants in Solidity. But I believe you are right and I've updated to comment accordingly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test would be better off named top_level_for_loop_trivial_non_constant_condition.sol. It would better explain why we're checking this, not just what we're testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't think that captures the purpose of the test. I tried a different name.

Changelog.md Outdated
@@ -22,6 +22,7 @@ Bugfixes:
* Metadata: Fix custom cleanup sequence missing from metadata when other optimizer settings have default values.
* SMTChecker: Fix internal compiler error when analyzing overflowing expressions or bitwise negation of unsigned types involving constants.
* SMTChecker: Fix reporting on targets that are safe in the context of one contract but unsafe in the context of another contract.
* SMTChecker: Fix SMT logic error in BMC when loop condition is always true in one iteration and always false in the next one.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the ICE isn't the only thing this PR fixes. 0.8.28 reports k == 0 as always false in this example:

contract Test {
    function loop() public pure {
        uint k = 0;
        while (k == 0 ? true : false) {
            ++k;
        }
    }
}
Warning: BMC: Condition is always false.
 --> test.sol:4:16:
  |
4 |         while (k == 0 ? true : false) {
  |                ^^^^^^
Note: Callstack:

The version from your branch does not.

I'm comparing with 0.8.28 binary though so there's also a chance it's the result of another, already merged fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this is the result of the current changes. I've updated the changelog entry, let me know what you think.

Comment on lines -1198 to -1200
// Do not check for const-ness if this is a constant.
if (dynamic_cast<Literal const*>(&_condition))
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkBooleanNotConstant()'s docstring needs to be updated. It still mentions that the function checks this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I update the docstring. Actually, coming back to this function, I refactored it further to only perform the satisfiability checks and moved reporting of the result to the only caller.

Comment on lines +448 to +451
m_loopCheckpoints.emplace();
_node.condition()->accept(*this);
forCondition = expr(*_node.condition());
m_loopCheckpoints.pop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're adding/moving these in 3 different places, but you're only adding one new test case and not updating any existing ones. I suspect that we don't have coverage for all 3.

The example I posted under the changelog would probably cover one of them. What's the other one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added you example with while loop, which covers the change in visit(WhileStatement).
The one test with for loop that I added originally actually exercises both changes in visit(ForStatement), so no additional test is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, there are two places in visit(ForStatement) where the loop condition is visited:
Before each unrolling, and a final check when the loop no longer enters another iteration.
We needed to patch both places.

@blishko blishko force-pushed the smt-fix-loops-in-bmc branch 3 times, most recently from 47bb2ac to 343184a Compare March 21, 2025 17:49
blishko added 2 commits March 27, 2025 21:53
Value of loop condition can change between iterations of the loop.
Therefore, BMC should be in the "inside loop" state when analyzing loop
conditions.
@blishko blishko force-pushed the smt-fix-loops-in-bmc branch from 343184a to 2b4d77d Compare March 27, 2025 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
smt 🟡 PR review label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SMTChecker: Internal compiler error in BMC analysis of loop condition
2 participants