-
Notifications
You must be signed in to change notification settings - Fork 6.1k
[SMTChecker] Support to integer and Bool storage vars #3947
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
Conversation
m_interface->reset(); | ||
m_variables.clear(); | ||
m_variables.insert(m_stateVariables.begin(), m_stateVariables.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_variables
is mostly used in a way that works for both local and state vars, that's why I decided to have only one map (instead of this one and another m_stateVariables
) and add the state vars to m_variables
in the beginning of a function visit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can still be refactored a little better, for example having only a single map where the entries know when they have to be reset and how, but we can merge it like that for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can still be refactored a little better, for example having only a single map where the entries know when they have to be reset and how, but we can merge it like that for now.
@@ -607,7 +615,8 @@ void SMTChecker::checkCondition( | |||
message << " for:\n"; | |||
solAssert(values.size() == expressionNames.size(), ""); | |||
for (size_t i = 0; i < values.size(); ++i) | |||
message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n"; | |||
if (expressionsToEvaluate.at(i).name != values.at(i)) | |||
message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if
had to be added so that state variables that are not touched by a function don't appear in a counterexample, since they don't have a model. In that case, the solver returns the name of the SMT variable itself as the model. Right now this happens only for Bool. The reason is that when we set a Bool var to unknown, no constraint is added to the solver, therefore if the function doesn't do anything with this state var, it's just not there. Since integers have constraints for the unknown case, integer state variables will have a model even if they're not used.
So, we should choose one out of the following 3 cases for how counterexamples are shown:
- As it is in this PR: unused state integer vars get a model, Bools don't.
- Unused Bool state vars should have a model too. This case is probably simple to create, we just have to add the trivial constraint
(or var (not var))
in the unknown case. - No unused state vars should have models. This is more complicated and would require changes in the class
VariableUsage
or something similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine this way.
libsolidity/formal/SMTChecker.cpp
Outdated
@@ -62,6 +62,8 @@ void SMTChecker::endVisit(VariableDeclaration const& _varDecl) | |||
{ | |||
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) | |||
assignment(_varDecl, *_varDecl.value(), _varDecl.location()); | |||
else if (_varDecl.isStateVariable() && _varDecl.type()->isValueType()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably fails if variables are declared below functions. I think we have to manually register them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True.. I read somewhere that state vars were visited before functions, but you're right.
@@ -62,6 +62,8 @@ void SMTChecker::endVisit(VariableDeclaration const& _varDecl) | |||
{ | |||
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I think this was always wrong with variables like var (a, b) = f()
.
libsolidity/formal/SMTChecker.cpp
Outdated
m_pathConditions.clear(); | ||
m_conditionalExecutionHappened = false; | ||
initializeLocalVariables(_function); | ||
resetStateVariables(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above, we call m_interface->reset()
, so is this really necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's necessary to re-add the range constraints coming from types
else | ||
{ | ||
solAssert(_varDecl.isStateVariable(), ""); | ||
m_stateVariables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have this distinction in the first place? If we do not add the state variables to m_variables
, they are not taken into account when unifying branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The distinction is because state vars are visited before functions, so we keep a single copy while visiting all the functions, and add them to m_variables
in the beginning of each function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the thing about state vars being visited before functions: has to be done manually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_variables do not include m_stateVariables. Because of that, a test for if(b) { a = 200; } assert(a == 200);
should be incorrect if a
is a state variable and b
is an input.
|
ea3625d
to
60b9083
Compare
@@ -33,7 +33,6 @@ VariableUsage::VariableUsage(ASTNode const& _node) | |||
solAssert(declaration, ""); | |||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) | |||
if ( | |||
varDecl->isLocalVariable() && | |||
identifier->annotation().lValueRequested && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might not work correctly for tuple assignments, I created an issue for that: #4141
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I wanted to check that after a similar comment from you
@@ -58,6 +58,19 @@ void SMTChecker::analyze(SourceUnit const& _source) | |||
_source.accept(*this); | |||
} | |||
|
|||
bool SMTChecker::visit(ContractDefinition const& _contract) | |||
{ | |||
for (auto _var : _contract.stateVariables()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not include inherited state variables, so we have to be careful once we add inheritance.
Rebased and adjusted changelog entry. |
The encoding isn't very different from local variables. In the beginning of every function the state vars are reset, meaning that their SSA index is increased and the new var is set to unknown.
Maybe we need more tests, not sure.
Added Changelog entry under 23 for now, will rebase later on.