-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
Support user-defined operators in SMTChecker #14534
Conversation
3a5a4df
to
bb37a3e
Compare
bb37a3e
to
23a346e
Compare
} | ||
|
||
contract C { | ||
function f(U x, U y) public pure returns (U) { | ||
return x / y; // FIXME: should detect div by zero | ||
return x / y; // FIXME: are we ok that div by zero not reported here? |
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.
There is a potential problem that errors are not reported where the operator is used, but in the implementation.
In this example division by zero is reported in line 5 but not here.
I could live with that, but maybe we want the error to be reported here as well?
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.
Oh, it makes perfect sense to report it in the implementation. I guess that's consistent with what would happen for a div(x, y)
function that does division. So I think this is fine and probably even expected.
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 agree. The implementation of the user-defined operator can be arbitrary code, so it makes sense to pinpoint the exact problem in the operator implementation.
} | ||
|
||
function testArithmetic(I16 x, I16 y) public pure { | ||
assert(x + y == add(x, y)); // FIXME: should hold | ||
assert(x + y == add(x, y)); // should hold |
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.
CHC reports Assertion violation *might* happen here.
I don't know why. Maybe it's because there are possible overlows/underflows/division by zero in implementation?
It affects only integer arithmetic operators. Bitwise and boolean are ok.
When I don't use operators and just apply appropriate function calls on both sides of the relation I also get those warnings.
edit:
On current develop branch, if I turn the assert
statement into for example:
assert(eq(add(x, y), add(x, y)))
it also yields Assertion violation *might* happen here.
warnings.
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.
@blishko Do you know what this^ might happen?
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.
*might* happen
just means that the solver was not able to solve the query within the given timeout/resource limit.
At least I think it is only in this case. This does not mean that there is necessarily something wrong here, just that the solver is not able to deal with the query efficiently. We can try to solve it with increased timeout, see if that helps.
23a346e
to
27969b4
Compare
439c4e1
to
3a8b65c
Compare
772ecc0
to
b68f8f8
Compare
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.
Looks pretty good! I agree that treating the user-defined operators as function calls is the right approach. I just have a few questions.
51d0dc6
to
6a9a0a4
Compare
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.
Left some more comments based on my recent reflections.
libsolidity/formal/BMC.cpp
Outdated
void BMC::inlineFunctionCall( | ||
FunctionDefinition const* _funDef, | ||
Expression const& _callStackExpr, | ||
Expression const* _calledExpr, |
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 looks to me like we are not consistent about what _calledExpr
represents.
In case of Operations
, you pass the pointer to the whole operation expression, e.g. a + b
.
But in case of a function call, e.g., f(a,b)
, this is only the pointer to the expression representing the name of the function, i.e, f
.
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.
Yea there is a mix up here:
pushCallStack
anddefineExpr
insidecreateReturnedExpression
should be called on theFunctionCall
object for the normal function call case, and indeed for the expression in case if user-defined operator call (e.g.a + b
)._funType->hasBoundFirstArgument()
can only be true for normal function calls (I assume, we should check in solidity-dev), so insymbolicArguments
we do need theFunctionCall::expression()
object (of typeExpression
) for the function call case, but not for the user-defined case
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.
Please correct me if I'm wrong, but I think now the arguments provided to BMC::inlineFuncitonCall
are correct.
libsolidity/formal/BMC.cpp
Outdated
Expression const* calledExpr = &_funCall.expression(); | ||
auto funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type); | ||
std::vector<ASTPointer<Expression const>> arguments = _funCall.sortedArguments(); | ||
|
||
inlineFunctionCall(funDef, _funCall, calledExpr, funType, arguments); |
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.
Here we extract only the name of the function to calledExpr
from the whole function call expression, see my comment above about difference in what calledExpr
represents.
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.
yes I agree, the callstack object needs to be the one of type FunctionCall*
in case of an actual function call
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.
@leonardoalt, to make sure, the current code is ok in that regard?
Maybe I should leave a clarifying comment in the code?
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.
@leonardoalt , I am still confused about "callstack object". This should be the expression that represents the function call in the caller?
For me the confusing bit here is the calledExpr
. In the case of an actual function call, this will be the expression representing the name of the function (because it is FunctionCall::expression
).
Why would we refer to the expressions of the function name as calledExpr
? It is not the expression that represents the function call, only the name of the function.
libsolidity/formal/SMTEncoder.cpp
Outdated
if (_funType->hasBoundFirstArgument()) | ||
{ | ||
calledExpr = innermostTuple(*calledExpr); | ||
auto const& attachedFunction = dynamic_cast<MemberAccess const*>(calledExpr); | ||
_calledExpr = innermostTuple(*_calledExpr); | ||
auto const& attachedFunction = dynamic_cast<MemberAccess const*>(_calledExpr); | ||
solAssert(attachedFunction, ""); | ||
args.push_back(expr(attachedFunction->expression(), functionParams.front()->type())); | ||
firstParam = 1; | ||
} |
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 more I am looking at this, the more I would like to refactor this method somehow to split the treatment of the extra argument in the special case of MemberAccess
and the conversion of actual arguments to SMT expressions.
One way that comes to my mind is to check for this optional extra argument beforehand and pass here just std::optional<smtutil::Expression>
. Then instead of _funDef, _calledExpr, _funType
, this method would only need the formal parameters (what we now get from _funDef->parameters()
).
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.
Done
...solidity/smtCheckerTests/operators/userDefined/user_defined_operations_on_constants_fail.sol
Show resolved
Hide resolved
libsolidity/formal/BMC.cpp
Outdated
Expression const* calledExpr = &_funCall.expression(); | ||
auto funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type); | ||
std::vector<ASTPointer<Expression const>> arguments = _funCall.sortedArguments(); | ||
|
||
inlineFunctionCall(funDef, _funCall, calledExpr, funType, arguments); |
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.
yes I agree, the callstack object needs to be the one of type FunctionCall*
in case of an actual function call
libsolidity/formal/BMC.cpp
Outdated
void BMC::inlineFunctionCall( | ||
FunctionDefinition const* _funDef, | ||
Expression const& _callStackExpr, | ||
Expression const* _calledExpr, |
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.
Yea there is a mix up here:
pushCallStack
anddefineExpr
insidecreateReturnedExpression
should be called on theFunctionCall
object for the normal function call case, and indeed for the expression in case if user-defined operator call (e.g.a + b
)._funType->hasBoundFirstArgument()
can only be true for normal function calls (I assume, we should check in solidity-dev), so insymbolicArguments
we do need theFunctionCall::expression()
object (of typeExpression
) for the function call case, but not for the user-defined case
b7e11f2
to
71eb81f
Compare
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 looks good from my side!
71eb81f
to
00bf98e
Compare
libsolidity/formal/SMTEncoder.cpp
Outdated
unsigned firstParam = 0; | ||
if (funType->hasBoundFirstArgument()) | ||
if (_calledExpr) |
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 looks a bit misleading. It must run only for bound calls, but it's called _calledExpr
. Are we sure it only runs in such case? Looking into the caller code I think this is the case, but we should confirm. In any case, I think this variable should be renamed to show more explicitly that it's about bound function calls.
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.
Right. I corrected that by renaming it to boundArgumentCall
.
I've checked it in the code, that this is an optional with value only for functions in which funType->hasBoundFirstArgument()
is true.
m_callGraph[m_currentContract].insert(funDef); | ||
} | ||
|
||
Expression const* calledExpr = &_funCall.expression(); |
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 calledExpr
here has the meaning that I imagined at first during the previous review, but in BMC it means bound functions. We should clarify this
libsolidity/formal/CHC.cpp
Outdated
if (!usesStaticCall(_funCall)) | ||
|
||
std::optional<Expression const*> calledExpr = | ||
_funType->hasBoundFirstArgument() ? std::make_optional(_calledExpr) : std::nullopt; |
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.
here it has the same behavior as BMC
5e9c27a
to
ba88e25
Compare
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.
lgtm, please squash
67956f8
to
5fe9d5d
Compare
5fe9d5d
to
da7c00e
Compare
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.
LGTM!
Closes #13893.
User-defined operators are handled like function calls.