-
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
Checked arithmetic by default. #9465
Conversation
There was an error when running
Please check that your changes are working as intended. |
There was an error when running
Please check that your changes are working as intended. |
There was an error when running
Please check that your changes are working as intended. |
Rebase problems? |
This still needs to be rebased and perhaps need to merge develop into breaking too. At the same time this seems to depend on #9479, so I'd suggest turning this to draft until that is merged. |
There was an error when running
Please check that your changes are working as intended. |
Rebased and changed target to |
There was an error when running
Please check that your changes are working as intended. |
38be2ad
to
e7e6212
Compare
8450cc3
to
2b3900d
Compare
709edb0
to
a991b2f
Compare
block: | ||
LBrace ( statement | uncheckedBlock )* RBrace; | ||
|
||
uncheckedBlock: Unchecked block; |
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 allows for nested unchecked blocks.
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, it is not a parser but a syntax checker restriction.
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.
Is if (<sth>) unchecked { x; }
invalid? (I'll probably know when I'm done reviewing...)
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.
Yeah, apparently it isn't - not sure if it couldn't be valid, but fine!
docs/types/value-types.rst
Outdated
There are two modes in which arithmetic is performed on these types: The "wrapping" or "unchecked" mode and the "checked" mode. | ||
By default, arithmetic is always "checked", which mean that if the result of an operation falls outside the value range | ||
of the type, the call is reverted through a :ref:`failing assertion<assert-and-require>`. You can switch to "unchecked" mode | ||
using ``unchecked { ... }``. More details can be found in the section about <unchecked>. |
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.
Is <unchecked>
at the end an actual link to the section?
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.
nope, it's not. Will fix.
docs/types/value-types.rst
Outdated
modes in regard to over- and underflow: | ||
|
||
By default, all arithmetic is checked for under- or overflow, but this can be disabled | ||
using the <unchecked> block, resulting in wrapping arithmetic. More details |
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.
using the <unchecked> block, resulting in wrapping arithmetic. More details | |
using the <unchecked> block, resulting in modular arithmetic. More details |
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.
Is <unchecked>
a link?
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.
Fixed the link. Rust also uses the term "wrapping": https://doc.rust-lang.org/std/primitive.u32.html#method.wrapping_add
Maybe "wrapping" is also less intimidating than "modular"?
@@ -380,6 +384,8 @@ class CompilerContext | |||
std::map<Declaration const*, std::vector<unsigned>> m_localVariables; | |||
/// The contract currently being compiled. Virtual function lookup starts from this contarct. | |||
ContractDefinition const* m_mostDerivedContract = nullptr; | |||
/// Whether to use checked arithmetic. | |||
std::stack<Arithmetic> m_arithmetic; |
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 needs to be a stack because you can call a function inside an unchecked block and then open another unchecked block inside that function, but the rest of that function is checked. Correct?
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 actually does not need to be a stack anymore, I think.
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 haven't seen anything that really bothered me in the first pass, but I wasn't extremely careful about it, so I'm a bit hesitant approving just yet...
docs/control-structures.rst
Outdated
An overflow or underflow is the situation where the resulting value of an arithmetic operation, | ||
when executed on an unrestricted integer, falls outside the range of the result type. |
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 seems a bit out of place to me here... this should probably be the second paragraph of the section at the very top?
block: | ||
LBrace ( statement | uncheckedBlock )* RBrace; | ||
|
||
uncheckedBlock: Unchecked block; |
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.
Is if (<sth>) unchecked { x; }
invalid? (I'll probably know when I'm done reviewing...)
block: | ||
LBrace ( statement | uncheckedBlock )* RBrace; | ||
|
||
uncheckedBlock: Unchecked block; |
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.
Yeah, apparently it isn't - not sure if it couldn't be valid, but fine!
@@ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) | |||
m_context << commonType->literalValue(nullptr); | |||
else | |||
{ | |||
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); | |||
bool cleanupNeeded = m_context.arithmetic() == Arithmetic::Checked || cleanupNeededForOp(commonType->category(), c_op); |
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.
Do we actually need cleanup here or aren't we doing it twice then? The checked arithmetic yul functions do the cleanup themselves, don't they?
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!
|
It's fine, I was just wondering when I saw the grammar - disallowing it now is definitely good - we can still allow it later anyways, while not the other way! |
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.
A few small comments.
It would be nice to also have a few more corner case tests:
- unchecked function called by checked function called by unchecked function
- checked function called by unchecked function
- is it possible to have
contract C { uint x = unchecked { ... }; }
?
@@ -275,7 +275,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) | |||
solAssert(*_assignment.annotation().type == leftType, ""); | |||
bool cleanupNeeded = false; | |||
if (op != Token::Assign) | |||
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); | |||
cleanupNeeded = m_context.arithmetic() == Arithmetic::Checked || cleanupNeededForOp(leftType.category(), binOp); |
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.
Is this just extra safety?
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.
Same as #9465 (comment) - since the arithmetic functions in yul already do the cleanup, we should probably not clean up before 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.
Extra blank
break; | ||
case Token::Mod: | ||
fun = m_utils.checkedIntModFunction(*type); | ||
fun = m_utils.intModFunction(*type); |
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.
What about exp
?
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 branches out earlier in visit(BinaryOperation const& _binOp)
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Var)); | ||
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Reference)); |
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.
Where did Reference
come from?
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.
IIUC the test verifies that isReservedKeyword
is correctly true for the first and the last reserved keyword - the last one used to be unchecked
, but then we made var
reserved again and added it as new last one here, but just kept unchecked
, so since then the idea is "check first and last reserved keyword and a random one in between" - the random one in between can't be unchecked anymore, so now it's reference...
Actually some commits need to be squashed. |
Looks good. What about |
Will be done in #10013 |
Depends on #9479.
TODO:
-x
for unsignedx
outside of "unchecked" - or do we want to disallow it in general?0-x
is still OK, I would say.