Merge pull request #9465 from ethereum/unchecked

Checked arithmetic by default.
This commit is contained in:
chriseth 2020-10-19 17:58:15 +02:00 committed by GitHub
commit 33a5fbf7a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 847 additions and 244 deletions

View File

@ -2,6 +2,7 @@
Breaking Changes: Breaking Changes:
* Assembler: The artificial ASSIGNIMMUTABLE opcode and the corresponding builtin in the "EVM with object access" dialect of Yul take the base offset of the code to modify as additional argument. * Assembler: The artificial ASSIGNIMMUTABLE opcode and the corresponding builtin in the "EVM with object access" dialect of Yul take the base offset of the code to modify as additional argument.
* Code Generator: All arithmetic is checked by default. These checks can be disabled using ``unchecked { ... }``.
* Type System: Unary negation can only be used on signed integers, not on unsigned integers. * Type System: Unary negation can only be used on signed integers, not on unsigned integers.
* Type System: Disallow explicit conversions from negative literals and literals larger than ``type(uint160).max`` to ``address`` type. * Type System: Disallow explicit conversions from negative literals and literals larger than ``type(uint160).max`` to ``address`` type.
* Parser: Exponentiation is right associative. ``a**b**c`` is parsed as ``a**(b**c)``. * Parser: Exponentiation is right associative. ``a**b**c`` is parsed as ``a**(b**c)``.
@ -15,6 +16,9 @@ Language Features:
* New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName`` * New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName``
AST Changes:
* New node type: unchecked block - used for ``unchecked { ... }``.
### 0.7.4 (unreleased) ### 0.7.4 (unreleased)
Important Bugfixes: Important Bugfixes:

View File

@ -475,6 +475,69 @@ In any case, you will get a warning about the outer variable being shadowed.
} }
} }
.. _unchecked:
Checked or Unchecked Arithmetic
===============================
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.
Prior to Solidity 0.8.0, arithmetic operations would always wrap in case of
under- or overflow leading to widespread use of libraries that introduce
additional checks.
Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default,
thus making the use of these libraries unnecessary.
To obtain the previous behaviour, an ``unchecked`` block can be used:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.7.99;
contract C {
function f(uint a, uint b) pure public returns (uint) {
// This addition will wrap on underflow.
unchecked { return a - b; }
}
function g(uint a, uint b) pure public returns (uint) {
// This addition will revert on underflow.
return a - b;
}
}
The call to ``f(2, 3)`` will return ``2**256-1``, while ``g(2, 3)`` will cause
a failing assertion.
The ``unchecked`` block can be used everywhere inside a block, but not as a replacement
for a block. It also cannot be nested.
The setting only affects the statements that are syntactically inside the block.
Functions called from within an ``unchecked`` block do not inherit the property.
.. note::
To avoid ambiguity, you cannot use ``_;`` inside an ``unchecked`` block.
The following operators will cause a failing assertion on overflow or underflow
and will wrap without an error if used inside an unchecked block:
``++``, ``--``, ``+``, binary ``-``, unary ``-``, ``*``, ``/``, ``%``, ``**``
``+=``, ``-=``, ``*=``, ``/=``, ``%=``
.. warning::
It is not possible to disable the check for division by zero
or modulo by zero using the ``unchecked`` block.
.. note::
The second statement in ``int x = type(int).min; -x;`` will result in an overflow
because the negative range can hold one more value than the positive range.
Explicit type conversions will always truncate and never cause a failing assertion
with the exception of a conversion from an integer to an enum type.
.. index:: ! exception, ! throw, ! assert, ! require, ! revert, ! errors .. index:: ! exception, ! throw, ! assert, ! require, ! revert, ! errors
.. _assert-and-require: .. _assert-and-require:
@ -519,6 +582,7 @@ An ``assert``-style exception is generated in the following situations:
#. If you access an array or an array slice at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). #. If you access an array or an array slice at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
#. If you access a fixed-length ``bytesN`` at a too large or negative index. #. If you access a fixed-length ``bytesN`` at a too large or negative index.
#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). #. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
#. If an arithmetic operation results in under- or overflow outside of an ``unchecked { ... }`` block.
#. If you convert a value too big or negative into an enum type. #. If you convert a value too big or negative into an enum type.
#. If you call a zero-initialized variable of internal function type. #. If you call a zero-initialized variable of internal function type.
#. If you call ``assert`` with an argument that evaluates to false. #. If you call ``assert`` with an argument that evaluates to false.

View File

@ -368,7 +368,10 @@ numberLiteral: (DecimalNumber | HexNumber) NumberUnit?;
/** /**
* A curly-braced block of statements. Opens its own scope. * A curly-braced block of statements. Opens its own scope.
*/ */
block: LBrace statement* RBrace; block:
LBrace ( statement | uncheckedBlock )* RBrace;
uncheckedBlock: Unchecked block;
statement: statement:
block block

View File

@ -7,7 +7,7 @@ ReservedKeywords:
'after' | 'alias' | 'apply' | 'auto' | 'case' | 'copyof' | 'default' | 'define' | 'final' 'after' | 'alias' | 'apply' | 'auto' | 'case' | 'copyof' | 'default' | 'define' | 'final'
| 'implements' | 'in' | 'inline' | 'let' | 'macro' | 'match' | 'mutable' | 'null' | 'of' | 'implements' | 'in' | 'inline' | 'let' | 'macro' | 'match' | 'mutable' | 'null' | 'of'
| 'partial' | 'promise' | 'reference' | 'relocatable' | 'sealed' | 'sizeof' | 'static' | 'partial' | 'promise' | 'reference' | 'relocatable' | 'sealed' | 'sizeof' | 'static'
| 'supports' | 'switch' | 'typedef' | 'typeof' | 'unchecked' | 'var'; | 'supports' | 'switch' | 'typedef' | 'typeof' | 'var';
Pragma: 'pragma' -> pushMode(PragmaMode); Pragma: 'pragma' -> pushMode(PragmaMode);
Abstract: 'abstract'; Abstract: 'abstract';
@ -87,6 +87,7 @@ True: 'true';
Try: 'try'; Try: 'try';
Type: 'type'; Type: 'type';
Ufixed: 'ufixed' | ('ufixed' [0-9]+ 'x' [0-9]+); Ufixed: 'ufixed' | ('ufixed' [0-9]+ 'x' [0-9]+);
Unchecked: 'unchecked';
/** /**
* Sized unsigned integer types. * Sized unsigned integer types.
* uint is an alias of uint256. * uint is an alias of uint256.

View File

@ -246,21 +246,32 @@ Two's Complement / Underflows / Overflows
========================================= =========================================
As in many programming languages, Solidity's integer types are not actually integers. As in many programming languages, Solidity's integer types are not actually integers.
They resemble integers when the values are small, but behave differently if the numbers are larger. They resemble integers when the values are small, but cannot represent arbitrarily large numbers.
For example, the following is true: ``uint8(255) + uint8(1) == 0``. This situation is called
an *overflow*. It occurs when an operation is performed that requires a fixed size variable The following code causes an overflow because the result of the addition is too large
to store a number (or piece of data) that is outside the range of the variable's data type. to be stored in the type ``uint8``:
An *underflow* is the converse situation: ``uint8(0) - uint8(1) == 255``.
::
uint8 x = 255;
uint8 y = 1;
return x + y;
Solidity has two modes in which it deals with these overflows: Checked and Unchecked or "wrapping" mode.
The default checked mode will detect overflows and cause a failing assertion. You can disable this check
using ``unchecked { ... }``, causing the overflow to be silently ignored. The above code would return
``0`` if wrapped in ``unchecked { ... }``.
Even in checked mode, do not assume you are protected from overflow bugs.
In this mode, overflows will always revert. If it is not possible to avoid the
overflow, this can lead to a smart contract being stuck in a certain state.
In general, read about the limits of two's complement representation, which even has some In general, read about the limits of two's complement representation, which even has some
more special edge cases for signed numbers. more special edge cases for signed numbers.
Try to use ``require`` to limit the size of inputs to a reasonable range and use the Try to use ``require`` to limit the size of inputs to a reasonable range and use the
:ref:`SMT checker<smt_checker>` to find potential overflows, or use a library like :ref:`SMT checker<smt_checker>` to find potential overflows.
`SafeMath <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol>`_
if you want all overflows to cause a revert.
Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also help you check if values are what you expect.
.. _clearing-mappings: .. _clearing-mappings:

View File

@ -46,8 +46,10 @@ access the minimum and maximum value representable by the type.
.. warning:: .. warning::
Integers in Solidity are restricted to a certain range. For example, with ``uint32``, this is ``0`` up to ``2**32 - 1``. Integers in Solidity are restricted to a certain range. For example, with ``uint32``, this is ``0`` up to ``2**32 - 1``.
If the result of some operation on those numbers does not fit inside this range, it is truncated. These truncations can have There are two modes in which arithmetic is performed on these types: The "wrapping" or "unchecked" mode and the "checked" mode.
serious consequences that you should :ref:`be aware of and mitigate against<underflow-overflow>`. 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 :ref:`unchecked <unchecked>`.
Comparisons Comparisons
^^^^^^^^^^^ ^^^^^^^^^^^
@ -77,23 +79,22 @@ Right operand must be unsigned type. Trying to shift by signed type will produce
Addition, Subtraction and Multiplication Addition, Subtraction and Multiplication
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Addition, subtraction and multiplication have the usual semantics. Addition, subtraction and multiplication have the usual semantics, with two different
They wrap in two's complement representation, meaning that modes in regard to over- and underflow:
for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows
into account when designing safe smart contracts. By default, all arithmetic is checked for under- or overflow, but this can be disabled
using the :ref:`unchecked block<unchecked>`, resulting in wrapping arithmetic. More details
can be found in that section.
The expression ``-x`` is equivalent to ``(T(0) - x)`` where The expression ``-x`` is equivalent to ``(T(0) - x)`` where
``T`` is the type of ``x``. It can only be applied to signed types. ``T`` is the type of ``x``. It can only be applied to signed types.
The value of ``-x`` can be The value of ``-x`` can be
positive if ``x`` is negative. There is another caveat also resulting positive if ``x`` is negative. There is another caveat also resulting
from two's complement representation:: from two's complement representation:
int x = -2**255;
assert(-x == x);
This means that even if a number is negative, you cannot assume that
its negation will be positive.
If you have ``int x = type(int).min;``, then ``-x`` does not fit the positive range.
This means that ``unchecked { assert(-x == x); }`` works, and the expression ``-x``
when used in checked mode will result in a failing assertion.
Division Division
^^^^^^^^ ^^^^^^^^
@ -106,7 +107,12 @@ Note that in contrast, division on :ref:`literals<rational_literals>` results in
of arbitrary precision. of arbitrary precision.
.. note:: .. note::
Division by zero causes a failing assert. Division by zero causes a failing assert. This check can **not** be disabled through ``unchecked { ... }``.
.. note::
The expression ``type(int).min / (-1)`` is the only case where division causes an overflow.
In checked arithmetic mode, this will cause a failing assertion, while in wrapping
mode, the value will be ``type(int).min``.
Modulo Modulo
^^^^^^ ^^^^^^
@ -121,14 +127,19 @@ results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`
* ``int256(-5) % int256(-2) == int256(-1)`` * ``int256(-5) % int256(-2) == int256(-1)``
.. note:: .. note::
Modulo with zero causes a failing assert. Modulo with zero causes a failing assert. This check can **not** be disabled through ``unchecked { ... }``.
Exponentiation Exponentiation
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
Exponentiation is only available for unsigned types in the exponent. The resulting type Exponentiation is only available for unsigned types in the exponent. The resulting type
of an exponentiation is always equal to the type of the base. Please take care that it is of an exponentiation is always equal to the type of the base. Please take care that it is
large enough to hold the result and prepare for potential wrapping behaviour. large enough to hold the result and prepare for potential assertion failures or wrapping behaviour.
.. note::
In checked mode, exponentiation only uses the comparatively cheap ``exp`` opcode for small bases.
For the cases of ``x**3``, the expression ``x*x*x`` might be cheaper.
In any case, gas cost tests and the use of the optimizer are advisable.
.. note:: .. note::
Note that ``0**0`` is defined by the EVM as ``1``. Note that ``0**0`` is defined by the EVM as ``1``.

View File

@ -191,6 +191,7 @@ namespace solidity::langutil
K(Throw, "throw", 0) \ K(Throw, "throw", 0) \
K(Try, "try", 0) \ K(Try, "try", 0) \
K(Type, "type", 0) \ K(Type, "type", 0) \
K(Unchecked, "unchecked", 0) \
K(Unicode, "unicode", 0) \ K(Unicode, "unicode", 0) \
K(Using, "using", 0) \ K(Using, "using", 0) \
K(View, "view", 0) \ K(View, "view", 0) \
@ -266,7 +267,6 @@ namespace solidity::langutil
K(Switch, "switch", 0) \ K(Switch, "switch", 0) \
K(Typedef, "typedef", 0) \ K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \ K(TypeOf, "typeof", 0) \
K(Unchecked, "unchecked", 0) \
K(Var, "var", 0) \ K(Var, "var", 0) \
\ \
/* Yul-specific tokens, but not keywords. */ \ /* Yul-specific tokens, but not keywords. */ \

View File

@ -190,6 +190,28 @@ void SyntaxChecker::endVisit(ForStatement const&)
m_inLoopDepth--; m_inLoopDepth--;
} }
bool SyntaxChecker::visit(Block const& _block)
{
if (_block.unchecked())
{
if (m_uncheckedArithmetic)
m_errorReporter.syntaxError(
1941_error,
_block.location(),
"\"unchecked\" blocks cannot be nested."
);
m_uncheckedArithmetic = true;
}
return true;
}
void SyntaxChecker::endVisit(Block const& _block)
{
if (_block.unchecked())
m_uncheckedArithmetic = false;
}
bool SyntaxChecker::visit(Continue const& _continueStatement) bool SyntaxChecker::visit(Continue const& _continueStatement)
{ {
if (m_inLoopDepth <= 0) if (m_inLoopDepth <= 0)
@ -288,8 +310,15 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
return false; return false;
} }
bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(PlaceholderStatement const& _placeholder)
{ {
if (m_uncheckedArithmetic)
m_errorReporter.syntaxError(
2573_error,
_placeholder.location(),
"The placeholder statement \"_\" cannot be used inside an \"unchecked\" block."
);
m_placeholderFound = true; m_placeholderFound = true;
return true; return true;
} }

View File

@ -71,6 +71,9 @@ private:
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
void endVisit(ForStatement const& _forStatement) override; void endVisit(ForStatement const& _forStatement) override;
bool visit(Block const& _block) override;
void endVisit(Block const& _block) override;
bool visit(Continue const& _continueStatement) override; bool visit(Continue const& _continueStatement) override;
bool visit(Break const& _breakStatement) override; bool visit(Break const& _breakStatement) override;
@ -100,6 +103,9 @@ private:
/// Flag that indicates whether some version pragma was present. /// Flag that indicates whether some version pragma was present.
bool m_versionPragmaFound = false; bool m_versionPragmaFound = false;
/// Flag that indicates whether we are inside an unchecked block.
bool m_uncheckedArithmetic = false;
int m_inLoopDepth = 0; int m_inLoopDepth = 0;
std::optional<ContractKind> m_currentContractKind; std::optional<ContractKind> m_currentContractKind;

View File

@ -1384,18 +1384,24 @@ public:
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _docString, ASTPointer<ASTString> const& _docString,
bool _unchecked,
std::vector<ASTPointer<Statement>> _statements std::vector<ASTPointer<Statement>> _statements
): ):
Statement(_id, _location, _docString), m_statements(std::move(_statements)) {} Statement(_id, _location, _docString),
m_statements(std::move(_statements)),
m_unchecked(_unchecked)
{}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; } std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; }
bool unchecked() const { return m_unchecked; }
BlockAnnotation& annotation() const override; BlockAnnotation& annotation() const override;
private: private:
std::vector<ASTPointer<Statement>> m_statements; std::vector<ASTPointer<Statement>> m_statements;
bool m_unchecked;
}; };
/** /**

View File

@ -39,6 +39,8 @@ enum class StateMutability { Pure, View, NonPayable, Payable };
/// Visibility ordered from restricted to unrestricted. /// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Internal, Public, External }; enum class Visibility { Default, Private, Internal, Public, External };
enum class Arithmetic { Checked, Wrapping };
inline std::string stateMutabilityToString(StateMutability const& _stateMutability) inline std::string stateMutabilityToString(StateMutability const& _stateMutability)
{ {
switch (_stateMutability) switch (_stateMutability)

View File

@ -596,7 +596,7 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
bool ASTJsonConverter::visit(Block const& _node) bool ASTJsonConverter::visit(Block const& _node)
{ {
setJsonNode(_node, "Block", { setJsonNode(_node, _node.unchecked() ? "UncheckedBlock" : "Block", {
make_pair("statements", toJson(_node.statements())) make_pair("statements", toJson(_node.statements()))
}); });
return false; return false;

View File

@ -154,7 +154,9 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
if (nodeType == "InlineAssembly") if (nodeType == "InlineAssembly")
return createInlineAssembly(_json); return createInlineAssembly(_json);
if (nodeType == "Block") if (nodeType == "Block")
return createBlock(_json); return createBlock(_json, false);
if (nodeType == "UncheckedBlock")
return createBlock(_json, true);
if (nodeType == "PlaceholderStatement") if (nodeType == "PlaceholderStatement")
return createPlaceholderStatement(_json); return createPlaceholderStatement(_json);
if (nodeType == "IfStatement") if (nodeType == "IfStatement")
@ -439,7 +441,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
modifiers, modifiers,
createParameterList(member(_node, "returnParameters")), createParameterList(member(_node, "returnParameters")),
memberAsBool(_node, "implemented") ? createBlock(member(_node, "body")) : nullptr memberAsBool(_node, "implemented") ? createBlock(member(_node, "body"), false) : nullptr
); );
} }
@ -489,7 +491,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
memberAsBool(_node, "virtual"), memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body")) _node["body"].isNull() ? nullptr: createBlock(member(_node, "body"), false)
); );
} }
@ -589,7 +591,7 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
); );
} }
ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node) ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node, bool _unchecked)
{ {
std::vector<ASTPointer<Statement>> statements; std::vector<ASTPointer<Statement>> statements;
for (auto& stat: member(_node, "statements")) for (auto& stat: member(_node, "statements"))
@ -597,6 +599,7 @@ ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node)
return createASTNode<Block>( return createASTNode<Block>(
_node, _node,
nullOrASTString(_node, "documentation"), nullOrASTString(_node, "documentation"),
_unchecked,
statements statements
); );
} }

View File

@ -93,7 +93,7 @@ private:
ASTPointer<Mapping> createMapping(Json::Value const& _node); ASTPointer<Mapping> createMapping(Json::Value const& _node);
ASTPointer<ArrayTypeName> createArrayTypeName(Json::Value const& _node); ASTPointer<ArrayTypeName> createArrayTypeName(Json::Value const& _node);
ASTPointer<InlineAssembly> createInlineAssembly(Json::Value const& _node); ASTPointer<InlineAssembly> createInlineAssembly(Json::Value const& _node);
ASTPointer<Block> createBlock(Json::Value const& _node); ASTPointer<Block> createBlock(Json::Value const& _node, bool _unchecked);
ASTPointer<PlaceholderStatement> createPlaceholderStatement(Json::Value const& _node); ASTPointer<PlaceholderStatement> createPlaceholderStatement(Json::Value const& _node);
ASTPointer<IfStatement> createIfStatement(Json::Value const& _node); ASTPointer<IfStatement> createIfStatement(Json::Value const& _node);
ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node); ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node);

View File

@ -123,6 +123,9 @@ public:
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; } void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const; ContractDefinition const& mostDerivedContract() const;
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
Arithmetic arithmetic() const { return m_arithmetic; }
/// @returns the next function in the queue of functions that are still to be compiled /// @returns the next function in the queue of functions that are still to be compiled
/// (i.e. that were referenced during compilation but where we did not yet generate code for). /// (i.e. that were referenced during compilation but where we did not yet generate code for).
/// Returns nullptr if the queue is empty. Does not remove the function from the queue, /// Returns nullptr if the queue is empty. Does not remove the function from the queue,
@ -380,6 +383,8 @@ private:
std::map<Declaration const*, std::vector<unsigned>> m_localVariables; std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
/// The contract currently being compiled. Virtual function lookup starts from this contarct. /// The contract currently being compiled. Virtual function lookup starts from this contarct.
ContractDefinition const* m_mostDerivedContract = nullptr; ContractDefinition const* m_mostDerivedContract = nullptr;
/// Whether to use checked arithmetic.
Arithmetic m_arithmetic = Arithmetic::Checked;
/// Stack of current visited AST nodes, used for location attachment /// Stack of current visited AST nodes, used for location attachment
std::stack<ASTNode const*> m_visitedNodes; std::stack<ASTNode const*> m_visitedNodes;
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.

View File

@ -1247,19 +1247,31 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Placeholder cannot be used inside checked block.");
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
solAssert(m_context.arithmetic() == Arithmetic::Checked, "Arithmetic not reset to 'checked'.");
checker.check(); checker.check();
return true; return true;
} }
bool ContractCompiler::visit(Block const& _block) bool ContractCompiler::visit(Block const& _block)
{ {
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
m_context.setArithmetic(Arithmetic::Wrapping);
}
storeStackHeight(&_block); storeStackHeight(&_block);
return true; return true;
} }
void ContractCompiler::endVisit(Block const& _block) void ContractCompiler::endVisit(Block const& _block)
{ {
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Wrapping, "");
m_context.setArithmetic(Arithmetic::Checked);
}
// Frees local variables declared in the scope of this block. // Frees local variables declared in the scope of this block.
popScopedVariables(&_block); popScopedVariables(&_block);
} }
@ -1327,6 +1339,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
if (codeBlock) if (codeBlock)
{ {
m_context.setArithmetic(Arithmetic::Checked);
std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive(); std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive();
m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures); m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures);

View File

@ -275,7 +275,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
solAssert(*_assignment.annotation().type == leftType, ""); solAssert(*_assignment.annotation().type == leftType, "");
bool cleanupNeeded = false; bool cleanupNeeded = false;
if (op != Token::Assign) if (op != Token::Assign)
cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp, m_context.arithmetic());
_assignment.rightHandSide().accept(*this); _assignment.rightHandSide().accept(*this);
// Perform some conversion already. This will convert storage types to memory and literals // Perform some conversion already. This will convert storage types to memory and literals
// to their actual type, but will not convert e.g. memory to storage. // to their actual type, but will not convert e.g. memory to storage.
@ -381,9 +381,10 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
if (_unaryOperation.annotation().type->category() == Type::Category::RationalNumber) Type const& type = *_unaryOperation.annotation().type;
if (type.category() == Type::Category::RationalNumber)
{ {
m_context << _unaryOperation.annotation().type->literalValue(nullptr); m_context << type.literalValue(nullptr);
return false; return false;
} }
@ -406,24 +407,39 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Dec: // -- (pre- or postfix) case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved."); solAssert(!!m_currentLValue, "LValue not retrieved.");
solUnimplementedAssert( solUnimplementedAssert(
_unaryOperation.annotation().type->category() != Type::Category::FixedPoint, type.category() != Type::Category::FixedPoint,
"Not yet implemented - FixedPointType." "Not yet implemented - FixedPointType."
); );
m_currentLValue->retrieveValue(_unaryOperation.location()); m_currentLValue->retrieveValue(_unaryOperation.location());
if (!_unaryOperation.isPrefixOperation()) if (!_unaryOperation.isPrefixOperation())
{ {
// store value for later // store value for later
solUnimplementedAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); solUnimplementedAssert(type.sizeOnStack() == 1, "Stack size != 1 not implemented.");
m_context << Instruction::DUP1; m_context << Instruction::DUP1;
if (m_currentLValue->sizeOnStack() > 0) if (m_currentLValue->sizeOnStack() > 0)
for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i); m_context << swapInstruction(i);
} }
m_context << u256(1);
if (_unaryOperation.getOperator() == Token::Inc) if (_unaryOperation.getOperator() == Token::Inc)
m_context << Instruction::ADD; {
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().incrementCheckedFunction(type), 1, 1);
else
{
m_context << u256(1);
m_context << Instruction::ADD;
}
}
else else
m_context << Instruction::SWAP1 << Instruction::SUB; {
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().decrementCheckedFunction(type), 1, 1);
else
{
m_context << u256(1);
m_context << Instruction::SWAP1 << Instruction::SUB;
}
}
// Stack for prefix: [ref...] (*ref)+-1 // Stack for prefix: [ref...] (*ref)+-1
// Stack for postfix: *ref [ref...] (*ref)+-1 // Stack for postfix: *ref [ref...] (*ref)+-1
for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
@ -437,7 +453,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
// unary add, so basically no-op // unary add, so basically no-op
break; break;
case Token::Sub: // - case Token::Sub: // -
m_context << u256(0) << Instruction::SUB; if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().negateNumberCheckedFunction(type), 1, 1);
else
m_context << u256(0) << Instruction::SUB;
break; break;
default: default:
solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator()))); solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator())));
@ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
m_context << commonType->literalValue(nullptr); m_context << commonType->literalValue(nullptr);
else else
{ {
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op, m_context.arithmetic());
TypePointer leftTargetType = commonType; TypePointer leftTargetType = commonType;
TypePointer rightTargetType = TypePointer rightTargetType =
@ -2112,34 +2131,65 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
solUnimplemented("Not yet implemented - FixedPointType."); solUnimplemented("Not yet implemented - FixedPointType.");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
bool const c_isSigned = type.isSigned(); if (m_context.arithmetic() == Arithmetic::Checked)
switch (_operator)
{ {
case Token::Add: string functionName;
m_context << Instruction::ADD; switch (_operator)
break; {
case Token::Sub: case Token::Add:
m_context << Instruction::SUB; functionName = m_context.utilFunctions().overflowCheckedIntAddFunction(type);
break; break;
case Token::Mul: case Token::Sub:
m_context << Instruction::MUL; functionName = m_context.utilFunctions().overflowCheckedIntSubFunction(type);
break; break;
case Token::Div: case Token::Mul:
case Token::Mod: functionName = m_context.utilFunctions().overflowCheckedIntMulFunction(type);
{ break;
// Test for division by zero case Token::Div:
m_context << Instruction::DUP2 << Instruction::ISZERO; functionName = m_context.utilFunctions().overflowCheckedIntDivFunction(type);
m_context.appendConditionalInvalid(); break;
case Token::Mod:
if (_operator == Token::Div) functionName = m_context.utilFunctions().intModFunction(type);
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); break;
else case Token::Exp:
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); // EXP is handled in a different function.
break; default:
solAssert(false, "Unknown arithmetic operator.");
}
// TODO Maybe we want to force-inline this?
m_context.callYulFunction(functionName, 2, 1);
} }
default: else
solAssert(false, "Unknown arithmetic operator."); {
bool const c_isSigned = type.isSigned();
switch (_operator)
{
case Token::Add:
m_context << Instruction::ADD;
break;
case Token::Sub:
m_context << Instruction::SUB;
break;
case Token::Mul:
m_context << Instruction::MUL;
break;
case Token::Div:
case Token::Mod:
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
else
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD);
break;
}
default:
solAssert(false, "Unknown arithmetic operator.");
}
} }
} }
@ -2237,7 +2287,14 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons
solAssert(_valueType.category() == Type::Category::Integer, ""); solAssert(_valueType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), ""); solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), "");
m_context << Instruction::EXP;
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().overflowCheckedIntExpFunction(
dynamic_cast<IntegerType const&>(_valueType),
dynamic_cast<IntegerType const&>(_exponentType)
), 2, 1);
else
m_context << Instruction::EXP;
} }
void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExternalFunctionCall(
@ -2561,11 +2618,15 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
setLValue<StorageItem>(_expression, *_expression.annotation().type); setLValue<StorageItem>(_expression, *_expression.annotation().type);
} }
bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op) bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op, Arithmetic _arithmetic)
{ {
if (TokenTraits::isCompareOp(_op) || TokenTraits::isShiftOp(_op)) if (TokenTraits::isCompareOp(_op) || TokenTraits::isShiftOp(_op))
return true; return true;
else if (_type == Type::Category::Integer && (_op == Token::Div || _op == Token::Mod || _op == Token::Exp)) else if (
_arithmetic == Arithmetic::Wrapping &&
_type == Type::Category::Integer &&
(_op == Token::Div || _op == Token::Mod || _op == Token::Exp)
)
// We need cleanup for EXP because 0**0 == 1, but 0**0x100 == 0 // We need cleanup for EXP because 0**0 == 1, but 0**0x100 == 0
// It would suffice to clean the exponent, though. // It would suffice to clean the exponent, though.
return true; return true;

View File

@ -132,7 +132,7 @@ private:
/// @returns true if the operator applied to the given type requires a cleanup prior to the /// @returns true if the operator applied to the given type requires a cleanup prior to the
/// operation. /// operation.
static bool cleanupNeededForOp(Type::Category _type, Token _op); static bool cleanupNeededForOp(Type::Category _type, Token _op, Arithmetic _arithmetic);
void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false); void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false);

View File

@ -483,6 +483,22 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type)
{
string functionName = "wrapping_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := <cleanupFunction>(add(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{ {
string functionName = "checked_mul_" + _type.identifier(); string functionName = "checked_mul_" + _type.identifier();
@ -519,6 +535,22 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntMulFunction(IntegerType const& _type)
{
string functionName = "wrapping_mul_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> product {
product := <cleanupFunction>(mul(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{ {
string functionName = "checked_div_" + _type.identifier(); string functionName = "checked_div_" + _type.identifier();
@ -548,9 +580,30 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) string YulUtilFunctions::wrappingIntDivFunction(IntegerType const& _type)
{ {
string functionName = "checked_mod_" + _type.identifier(); string functionName = "wrapping_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <error>() }
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
("signed", _type.isSigned())
("error", panicFunction())
.render();
});
}
string YulUtilFunctions::intModFunction(IntegerType const& _type)
{
string functionName = "mod_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
@ -599,6 +652,22 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
}); });
} }
string YulUtilFunctions::wrappingIntSubFunction(IntegerType const& _type)
{
string functionName = "wrapping_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
diff := <cleanupFunction>(sub(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntExpFunction( string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type, IntegerType const& _type,
IntegerType const& _exponentType IntegerType const& _exponentType
@ -894,6 +963,30 @@ string YulUtilFunctions::overflowCheckedExpLoopFunction()
}); });
} }
string YulUtilFunctions::wrappingIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "wrapping_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
power := <baseCleanupFunction>(exp(base, exponent))
}
)")
("functionName", functionName)
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction() string YulUtilFunctions::extractByteArrayLengthFunction()
{ {
string functionName = "extract_byte_array_length"; string functionName = "extract_byte_array_length";
@ -2951,30 +3044,39 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
string const functionName = "decrement_" + _type.identifier(); string const functionName = "decrement_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 minintval;
// Smallest admissible value to decrement
if (type.isSigned())
minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
else
minintval = 1;
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if <lt>(value, <minval>) { <panic>() } if eq(value, <minval>) { <panic>() }
ret := sub(value, 1) ret := sub(value, 1)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("panic", panicFunction()) ("panic", panicFunction())
("minval", toCompactHexWithPrefix(minintval)) ("minval", toCompactHexWithPrefix(type.min()))
("lt", type.isSigned() ? "slt" : "lt")
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
.render(); .render();
}); });
} }
std::string YulUtilFunctions::decrementWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "decrement_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(sub(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
{ {
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
@ -2982,55 +3084,79 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
string const functionName = "increment_" + _type.identifier(); string const functionName = "increment_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
u256 maxintval;
// Biggest admissible value to increment
if (type.isSigned())
maxintval = (u256(1) << (type.numBits() - 1)) - 2;
else
maxintval = (u256(1) << type.numBits()) - 2;
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if <gt>(value, <maxval>) { <panic>() } if eq(value, <maxval>) { <panic>() }
ret := add(value, 1) ret := add(value, 1)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("maxval", toCompactHexWithPrefix(maxintval)) ("maxval", toCompactHexWithPrefix(type.max()))
("gt", type.isSigned() ? "sgt" : "gt")
("panic", panicFunction()) ("panic", panicFunction())
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
.render(); .render();
}); });
} }
std::string YulUtilFunctions::incrementWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "increment_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(add(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
{ {
IntegerType const& type = dynamic_cast<IntegerType const&>(_type); IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!"); solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier(); string const functionName = "negate_" + _type.identifier();
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
return m_functionCollector.createFunction(functionName, [&]() { return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"( return Whiskers(R"(
function <functionName>(value) -> ret { function <functionName>(value) -> ret {
value := <cleanupFunction>(value) value := <cleanupFunction>(value)
if slt(value, <minval>) { <panic>() } if eq(value, <minval>) { <panic>() }
ret := sub(0, value) ret := sub(0, value)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval)) ("minval", toCompactHexWithPrefix(type.min()))
("cleanupFunction", cleanupFunction(_type)) ("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction()) ("panic", panicFunction())
.render(); .render();
}); });
} }
string YulUtilFunctions::negateNumberWrappingFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(sub(0, value)))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes) string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{ {
solAssert(_type.category() != Type::Category::Mapping, ""); solAssert(_type.category() != Type::Category::Mapping, "");

View File

@ -106,24 +106,35 @@ public:
/// signature: (x, y) -> sum /// signature: (x, y) -> sum
std::string overflowCheckedIntAddFunction(IntegerType const& _type); std::string overflowCheckedIntAddFunction(IntegerType const& _type);
/// signature: (x, y) -> sum
std::string wrappingIntAddFunction(IntegerType const& _type);
/// signature: (x, y) -> product /// signature: (x, y) -> product
std::string overflowCheckedIntMulFunction(IntegerType const& _type); std::string overflowCheckedIntMulFunction(IntegerType const& _type);
/// signature: (x, y) -> product
std::string wrappingIntMulFunction(IntegerType const& _type);
/// @returns name of function to perform division on integers. /// @returns name of function to perform division on integers.
/// Checks for division by zero and the special case of /// Checks for division by zero and the special case of
/// signed division of the smallest number by -1. /// signed division of the smallest number by -1.
std::string overflowCheckedIntDivFunction(IntegerType const& _type); std::string overflowCheckedIntDivFunction(IntegerType const& _type);
/// @returns name of function to perform division on integers.
/// Checks for division by zero.
std::string wrappingIntDivFunction(IntegerType const& _type);
/// @returns name of function to perform modulo on integers. /// @returns name of function to perform modulo on integers.
/// Reverts for modulo by zero. /// Reverts for modulo by zero.
std::string checkedIntModFunction(IntegerType const& _type); std::string intModFunction(IntegerType const& _type);
/// @returns computes the difference between two values. /// @returns computes the difference between two values.
/// Assumes the input to be in range for the type. /// Assumes the input to be in range for the type.
/// signature: (x, y) -> diff /// signature: (x, y) -> diff
std::string overflowCheckedIntSubFunction(IntegerType const& _type); std::string overflowCheckedIntSubFunction(IntegerType const& _type);
/// @returns computes the difference between two values.
/// signature: (x, y) -> diff
std::string wrappingIntSubFunction(IntegerType const& _type);
/// @returns the name of the exponentiation function. /// @returns the name of the exponentiation function.
/// signature: (base, exponent) -> power /// signature: (base, exponent) -> power
std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType); std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
@ -151,6 +162,10 @@ public:
/// signature: (power, base, exponent, max) -> power /// signature: (power, base, exponent, max) -> power
std::string overflowCheckedExpLoopFunction(); std::string overflowCheckedExpLoopFunction();
/// @returns the name of the exponentiation function.
/// signature: (base, exponent) -> power
std::string wrappingIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
/// @returns the name of a function that fetches the length of the given /// @returns the name of a function that fetches the length of the given
/// array /// array
/// signature: (array) -> length /// signature: (array) -> length
@ -367,9 +382,12 @@ public:
std::string forwardingRevertFunction(); std::string forwardingRevertFunction();
std::string incrementCheckedFunction(Type const& _type); std::string incrementCheckedFunction(Type const& _type);
std::string incrementWrappingFunction(Type const& _type);
std::string decrementCheckedFunction(Type const& _type); std::string decrementCheckedFunction(Type const& _type);
std::string decrementWrappingFunction(Type const& _type);
std::string negateNumberCheckedFunction(Type const& _type); std::string negateNumberCheckedFunction(Type const& _type);
std::string negateNumberWrappingFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the /// @returns the name of a function that returns the zero value for the
/// provided type. /// provided type.

View File

@ -132,6 +132,9 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; }; langutil::EVMVersion evmVersion() const { return m_evmVersion; };
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
Arithmetic arithmetic() const { return m_arithmetic; }
ABIFunctions abiFunctions(); ABIFunctions abiFunctions();
/// @returns code that stores @param _message for revert reason /// @returns code that stores @param _message for revert reason
@ -161,6 +164,8 @@ private:
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
MultiUseYulFunctionCollector m_functions; MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0; size_t m_varCounter = 0;
/// Whether to use checked or wrapping arithmetic.
Arithmetic m_arithmetic = Arithmetic::Checked;
/// Flag indicating whether any inline assembly block was seen. /// Flag indicating whether any inline assembly block was seen.
bool m_inlineAssemblySeen = false; bool m_inlineAssemblySeen = false;

View File

@ -474,6 +474,25 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
return false; return false;
} }
bool IRGeneratorForStatements::visit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Checked, "");
m_context.setArithmetic(Arithmetic::Wrapping);
}
return true;
}
void IRGeneratorForStatements::endVisit(Block const& _block)
{
if (_block.unchecked())
{
solAssert(m_context.arithmetic() == Arithmetic::Wrapping, "");
m_context.setArithmetic(Arithmetic::Checked);
}
}
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
{ {
_ifStatement.condition().accept(*this); _ifStatement.condition().accept(*this);
@ -618,11 +637,11 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
else if (op == Token::Sub) else if (op == Token::Sub)
{ {
IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType); IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType);
define(_unaryOperation) << define(_unaryOperation) << (
m_utils.negateNumberCheckedFunction(intType) << m_context.arithmetic() == Arithmetic::Checked ?
"(" << m_utils.negateNumberCheckedFunction(intType) :
IRVariable(_unaryOperation.subExpression()).name() << m_utils.negateNumberWrappingFunction(intType)
")\n"; ) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n";
} }
else else
solUnimplementedAssert(false, "Unary operator not yet implemented"); solUnimplementedAssert(false, "Unary operator not yet implemented");
@ -2560,23 +2579,23 @@ string IRGeneratorForStatements::binaryOperation(
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
{ {
string fun; string fun;
// TODO: Implement all operations for signed and unsigned types. bool checked = m_context.arithmetic() == Arithmetic::Checked;
switch (_operator) switch (_operator)
{ {
case Token::Add: case Token::Add:
fun = m_utils.overflowCheckedIntAddFunction(*type); fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
break; break;
case Token::Sub: case Token::Sub:
fun = m_utils.overflowCheckedIntSubFunction(*type); fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
break; break;
case Token::Mul: case Token::Mul:
fun = m_utils.overflowCheckedIntMulFunction(*type); fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
break; break;
case Token::Div: case Token::Div:
fun = m_utils.overflowCheckedIntDivFunction(*type); fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
break; break;
case Token::Mod: case Token::Mod:
fun = m_utils.checkedIntModFunction(*type); fun = m_utils.intModFunction(*type);
break; break;
case Token::BitOr: case Token::BitOr:
fun = "or"; fun = "or";

View File

@ -66,6 +66,8 @@ public:
bool visit(Conditional const& _conditional) override; bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override; bool visit(Assignment const& _assignment) override;
bool visit(TupleExpression const& _tuple) override; bool visit(TupleExpression const& _tuple) override;
bool visit(Block const& _block) override;
void endVisit(Block const& _block) override;
bool visit(IfStatement const& _ifStatement) override; bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override; bool visit(WhileStatement const& _whileStatement) override;

View File

@ -1096,16 +1096,23 @@ ASTPointer<ParameterList> Parser::parseParameterList(
return nodeFactory.createNode<ParameterList>(parameters); return nodeFactory.createNode<ParameterList>(parameters);
} }
ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString) ASTPointer<Block> Parser::parseBlock(bool _allowUnchecked, ASTPointer<ASTString> const& _docString)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
bool const unchecked = m_scanner->currentToken() == Token::Unchecked;
if (unchecked)
{
if (!_allowUnchecked)
parserError(5296_error, "\"unchecked\" blocks can only be used inside regular blocks.");
m_scanner->next();
}
expectToken(Token::LBrace); expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements; vector<ASTPointer<Statement>> statements;
try try
{ {
while (m_scanner->currentToken() != Token::RBrace) while (m_scanner->currentToken() != Token::RBrace)
statements.push_back(parseStatement()); statements.push_back(parseStatement(true));
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
} }
catch (FatalError const&) catch (FatalError const&)
@ -1122,10 +1129,10 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
expectTokenOrConsumeUntil(Token::RBrace, "Block"); expectTokenOrConsumeUntil(Token::RBrace, "Block");
else else
expectToken(Token::RBrace); expectToken(Token::RBrace);
return nodeFactory.createNode<Block>(_docString, statements); return nodeFactory.createNode<Block>(_docString, unchecked, statements);
} }
ASTPointer<Statement> Parser::parseStatement() ASTPointer<Statement> Parser::parseStatement(bool _allowUnchecked)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTPointer<ASTString> docString; ASTPointer<ASTString> docString;
@ -1144,9 +1151,9 @@ ASTPointer<Statement> Parser::parseStatement()
return parseDoWhileStatement(docString); return parseDoWhileStatement(docString);
case Token::For: case Token::For:
return parseForStatement(docString); return parseForStatement(docString);
case Token::Unchecked:
case Token::LBrace: case Token::LBrace:
return parseBlock(docString); return parseBlock(_allowUnchecked, docString);
// starting from here, all statements must be terminated by a semicolon
case Token::Continue: case Token::Continue:
statement = ASTNodeFactory(*this).createNode<Continue>(docString); statement = ASTNodeFactory(*this).createNode<Continue>(docString);
m_scanner->next(); m_scanner->next();

View File

@ -115,8 +115,8 @@ private:
VarDeclParserOptions const& _options = {}, VarDeclParserOptions const& _options = {},
bool _allowEmpty = true bool _allowEmpty = true
); );
ASTPointer<Block> parseBlock(ASTPointer<ASTString> const& _docString = {}); ASTPointer<Block> parseBlock(bool _allowUncheckedBlock = false, ASTPointer<ASTString> const& _docString = {});
ASTPointer<Statement> parseStatement(); ASTPointer<Statement> parseStatement(bool _allowUncheckedBlock = false);
ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {}); ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {});
ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString); ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString); ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString);

View File

@ -33,7 +33,7 @@ object "Arraysum_33" {
for { } for { }
lt(vloc_i, _2) lt(vloc_i, _2)
{ {
if gt(vloc_i, not(1)) { invalid() } if eq(vloc_i, not(0)) { invalid() }
vloc_i := add(vloc_i, 1) vloc_i := add(vloc_i, 1)
} }
{ {

View File

@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(function_calls)
uint data2; uint data2;
function f(uint x) public { function f(uint x) public {
if (x > 7) if (x > 7)
data2 = g(x**8) + 1; { unchecked { data2 = g(x**8) + 1; } }
else else
data = 1; data = 1;
} }
@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(multiple_external_functions)
uint data2; uint data2;
function f(uint x) public { function f(uint x) public {
if (x > 7) if (x > 7)
data2 = g(x**8) + 1; { unchecked { data2 = g(x**8) + 1; } }
else else
data = 1; data = 1;
} }
@ -223,13 +223,13 @@ BOOST_AUTO_TEST_CASE(exponent_size)
char const* sourceCode = R"( char const* sourceCode = R"(
contract A { contract A {
function f(uint x) public returns (uint) { function f(uint x) public returns (uint) {
return x ** 0; unchecked { return x ** 0; }
} }
function g(uint x) public returns (uint) { function g(uint x) public returns (uint) {
return x ** 0x100; unchecked { return x ** 0x100; }
} }
function h(uint x) public returns (uint) { function h(uint x) public returns (uint) {
return x ** 0x10000; unchecked { return x ** 0x10000; }
} }
} }
)"; )";
@ -289,29 +289,31 @@ BOOST_AUTO_TEST_CASE(complex_control_flow)
char const* sourceCode = R"( char const* sourceCode = R"(
contract log { contract log {
function ln(int128 x) public pure returns (int128 result) { function ln(int128 x) public pure returns (int128 result) {
int128 t = x / 256; unchecked {
int128 y = 5545177; int128 t = x / 256;
x = t; int128 y = 5545177;
t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; } x = t;
t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; } t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; }
t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; } t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; }
t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; } t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; }
t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; } t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; }
t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; } t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; }
t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; } t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; }
t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; } t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; }
t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; } t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; }
t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; } t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; }
t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; } t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; }
t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; } t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; }
t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; } t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; }
t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; } t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; }
t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; } t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; }
t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; } t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; }
t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; } t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; }
t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; } t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; }
t = x + x / 65536; if (t <= 1000000) { y = y - 15; } t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; }
return y; t = x + x / 65536; if (t <= 1000000) { y = y - 15; }
return y;
}
} }
} }
)"; )";

View File

@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions)
contract C { contract C {
uint x; uint x;
constructor() { f(); } constructor() { f(); }
function f() internal { for (uint i = 0; i < 10; ++i) x += 3 + i; } function f() internal { unchecked { for (uint i = 0; i < 10; ++i) x += 3 + i; } }
} }
)"; )";
compiler().setOptimiserSettings(solidity::test::CommonOptions::get().optimize); compiler().setOptimiserSettings(solidity::test::CommonOptions::get().optimize);

View File

@ -757,10 +757,12 @@ BOOST_AUTO_TEST_CASE(high_bits_cleaning)
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function run() public returns(uint256 y) { function run() public returns(uint256 y) {
uint32 t = uint32(0xffffffff); unchecked {
uint32 x = t + 10; uint32 t = uint32(0xffffffff);
if (x >= 0xffffffff) return 0; uint32 x = t + 10;
return x; if (x >= 0xffffffff) return 0;
return x;
}
} }
} }
)"; )";
@ -781,9 +783,11 @@ BOOST_AUTO_TEST_CASE(sign_extension)
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function run() public returns(uint256 y) { function run() public returns(uint256 y) {
int64 x = -int32(0xff); unchecked {
if (x >= 0xff) return 0; int64 x = -int32(0xff);
return 0 - uint256(x); if (x >= 0xff) return 0;
return 0 - uint256(x);
}
} }
} }
)"; )";
@ -803,9 +807,11 @@ BOOST_AUTO_TEST_CASE(small_unsigned_types)
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function run() public returns(uint256 y) { function run() public returns(uint256 y) {
uint32 t = uint32(0xffffff); unchecked {
uint32 x = t * 0xffffff; uint32 t = uint32(0xffffff);
return x / 0x100; uint32 x = t * 0xffffff;
return x / 0x100;
}
} }
} }
)"; )";
@ -1804,8 +1810,11 @@ BOOST_AUTO_TEST_CASE(blockhash)
function g() public returns (bool) { counter++; return true; } function g() public returns (bool) { counter++; return true; }
function f() public returns (bytes32[] memory r) { function f() public returns (bytes32[] memory r) {
r = new bytes32[](259); r = new bytes32[](259);
for (uint i = 0; i < 259; i++) for (uint i = 0; i < 259; i++) {
r[i] = blockhash(block.number - 257 + i); unchecked {
r[i] = blockhash(block.number - 257 + i);
}
}
} }
} }
)"; )";
@ -6881,7 +6890,7 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser)
} }
uint x = 0x0000000000001234123412431234123412412342112341234124312341234124; uint x = 0x0000000000001234123412431234123412412342112341234124312341234124;
// This is just to create many instances of x // This is just to create many instances of x
emit X(x + f() * g(tx.origin) ^ h(block.number)); unchecked { emit X(x + f() * g(tx.origin) ^ h(block.number)); }
assembly { assembly {
// make scratch space dirty // make scratch space dirty
mstore(0, 0x4242424242424242424242424242424242424242424242424242424242424242) mstore(0, 0x4242424242424242424242424242424242424242424242424242424242424242)
@ -6892,10 +6901,10 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser)
return 0x0000000000001234123412431234123412412342112341234124312341234124; return 0x0000000000001234123412431234123412412342112341234124312341234124;
} }
function g(address a) internal pure returns (uint) { function g(address a) internal pure returns (uint) {
return uint(a) * 0x0000000000001234123412431234123412412342112341234124312341234124; unchecked { return uint(a) * 0x0000000000001234123412431234123412412342112341234124312341234124; }
} }
function h(uint a) internal pure returns (uint) { function h(uint a) internal pure returns (uint) {
return a * 0x0000000000001234123412431234123412412342112341234124312341234124; unchecked { return a * 0x0000000000001234123412431234123412412342112341234124312341234124; }
} }
} }
)"; )";

View File

@ -139,6 +139,7 @@ bytes compileFirstExpression(
); );
context.resetVisitedNodes(contract); context.resetVisitedNodes(contract);
context.setMostDerivedContract(*contract); context.setMostDerivedContract(*contract);
context.setArithmetic(Arithmetic::Wrapping);
size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack
context.adjustStackOffset(static_cast<int>(parametersSize)); context.adjustStackOffset(static_cast<int>(parametersSize));
for (vector<string> const& variable: _localVariables) for (vector<string> const& variable: _localVariables)
@ -321,7 +322,7 @@ BOOST_AUTO_TEST_CASE(arithmetic)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function f(uint y) { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } function f(uint y) { unchecked { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } }
} }
)"; )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
@ -402,7 +403,7 @@ BOOST_AUTO_TEST_CASE(unary_operators)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function f(int y) { !(~- y == 2); } function f(int y) { unchecked { !(~- y == 2); } }
} }
)"; )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
@ -435,7 +436,7 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function f(uint a) public returns (uint x) { x = --a ^ (a-- ^ (++a ^ a++)); } function f(uint a) public returns (uint x) { unchecked { x = --a ^ (a-- ^ (++a ^ a++)); } }
} }
)"; )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}});
@ -492,7 +493,7 @@ BOOST_AUTO_TEST_CASE(assignment)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function f(uint a, uint b) { (a += b) * 2; } function f(uint a, uint b) { unchecked { (a += b) * 2; } }
} }
)"; )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}});

View File

@ -224,8 +224,8 @@ BOOST_AUTO_TEST_CASE(function_calls)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract test { contract test {
function f1(uint x) public returns (uint) { return x*x; } function f1(uint x) public returns (uint) { unchecked { return x*x; } }
function f(uint x) public returns (uint) { return f1(7+x) - this.f1(x**9); } function f(uint x) public returns (uint) { unchecked { return f1(7+x) - this.f1(x**9); } }
} }
)"; )";
compileBothVersions(sourceCode); compileBothVersions(sourceCode);

View File

@ -120,8 +120,9 @@ BOOST_AUTO_TEST_CASE(reserved_keywords)
{ {
BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Identifier)); BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Identifier));
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::After)); BOOST_CHECK(TokenTraits::isReservedKeyword(Token::After));
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Unchecked)); BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Unchecked));
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Var)); BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Var));
BOOST_CHECK(TokenTraits::isReservedKeyword(Token::Reference));
BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Illegal)); BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Illegal));
} }
@ -515,7 +516,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
"switch", "switch",
"typedef", "typedef",
"typeof", "typeof",
"unchecked",
"var" "var"
}; };

View File

@ -2,16 +2,16 @@ pragma experimental ABIEncoderV2;
contract C { contract C {
function exp_neg_one(uint exponent) public returns(int) { function exp_neg_one(uint exponent) public returns(int) {
return (-1)**exponent; unchecked { return (-1)**exponent; }
} }
function exp_two(uint exponent) public returns(uint) { function exp_two(uint exponent) public returns(uint) {
return 2**exponent; unchecked { return 2**exponent; }
} }
function exp_zero(uint exponent) public returns(uint) { function exp_zero(uint exponent) public returns(uint) {
return 0**exponent; unchecked { return 0**exponent; }
} }
function exp_one(uint exponent) public returns(uint) { function exp_one(uint exponent) public returns(uint) {
return 1**exponent; unchecked { return 1**exponent; }
} }
} }
// ==== // ====

View File

@ -2,16 +2,16 @@ pragma experimental ABIEncoderV2;
contract C { contract C {
function exp_neg_one(uint exponent) public returns(int) { function exp_neg_one(uint exponent) public returns(int) {
return (-1)**exponent; unchecked { return (-1)**exponent; }
} }
function exp_two(uint exponent) public returns(uint) { function exp_two(uint exponent) public returns(uint) {
return 2**exponent; unchecked { return 2**exponent; }
} }
function exp_zero(uint exponent) public returns(uint) { function exp_zero(uint exponent) public returns(uint) {
return 0**exponent; unchecked { return 0**exponent; }
} }
function exp_one(uint exponent) public returns(uint) { function exp_one(uint exponent) public returns(uint) {
return 1**exponent; unchecked { return 1**exponent; }
} }
} }
// ==== // ====

View File

@ -0,0 +1,13 @@
contract C {
function f() public returns (uint y) {
unchecked{{
uint max = type(uint).max;
uint x = max + 1;
y = x;
}}
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x00

View File

@ -0,0 +1,18 @@
contract C {
uint public x = msg.value - 10;
constructor() payable {}
}
contract D {
function f() public {
unchecked {
new C();
}
}
function g() public payable returns (uint) {
return (new C{value: 11}()).x();
}
}
// ----
// f() -> FAILURE
// g(), 100 wei -> 1

View File

@ -0,0 +1,13 @@
contract C {
// Input is still not checked - this needs ABIEncoderV2!
function f(uint16 a, uint16 b) public returns (uint16) {
return a + b;
}
}
// ====
// ABIEncoderV1Only: true
// ----
// f(uint16,uint16): 65534, 0 -> 0xfffe
// f(uint16,uint16): 65536, 0 -> 0x00
// f(uint16,uint16): 65535, 0 -> 0xffff
// f(uint16,uint16): 65535, 1 -> FAILURE

View File

@ -0,0 +1,14 @@
contract C {
function add(uint16 a, uint16 b) public returns (uint16) {
return a + b;
}
function f(uint16 a, uint16 b, uint16 c) public returns (uint16) {
unchecked { return add(a, b) + c; }
}
}
// ====
// compileViaYul: also
// ----
// f(uint16,uint16,uint16): 0xe000, 0xe500, 2 -> FAILURE
// f(uint16,uint16,uint16): 0xe000, 0x1000, 0x1000 -> 0x00

View File

@ -0,0 +1,15 @@
contract C {
modifier add(uint16 a, uint16 b) {
unchecked { a + b; }
_;
}
function f(uint16 a, uint16 b, uint16 c) public add(a, b) returns (uint16) {
return b + c;
}
}
// ====
// compileViaYul: also
// ----
// f(uint16,uint16,uint16): 0xe000, 0xe500, 2 -> 58626
// f(uint16,uint16,uint16): 0x1000, 0xe500, 0xe000 -> FAILURE

View File

@ -2,6 +2,14 @@ contract C {
function f(int a, int b) public pure returns (int) { function f(int a, int b) public pure returns (int) {
return a % b; return a % b;
} }
function g(bool _check) public pure returns (int) {
int x = type(int).min;
if (_check) {
return x / -1;
} else {
unchecked { return x / -1; }
}
}
} }
// ==== // ====
@ -12,3 +20,5 @@ contract C {
// f(int256,int256): -7, 5 -> -2 // f(int256,int256): -7, 5 -> -2
// f(int256,int256): -7, 5 -> -2 // f(int256,int256): -7, 5 -> -2
// f(int256,int256): -5, -5 -> 0 // f(int256,int256): -5, -5 -> 0
// g(bool): true -> FAILURE
// g(bool): false -> -57896044618658097711785492504343953926634992332820282019728792003956564819968

View File

@ -0,0 +1,17 @@
contract C {
function add(uint16 a, uint16 b) public returns (uint16) {
unchecked {
return a + b;
}
}
function f(uint16 a) public returns (uint16) {
return add(a, 0x100) + 0x100;
}
}
// ====
// compileViaYul: also
// ----
// f(uint16): 7 -> 0x0207
// f(uint16): 0xffff -> 511
// f(uint16): 0xfeff -> FAILURE

View File

@ -0,0 +1,22 @@
contract C {
function div(uint256 a, uint256 b) public returns (uint256) {
// Does not disable div by zero check
unchecked {
return a / b;
}
}
function mod(uint256 a, uint256 b) public returns (uint256) {
// Does not disable div by zero check
unchecked {
return a % b;
}
}
}
// ====
// compileViaYul: also
// ----
// div(uint256,uint256): 7, 2 -> 3
// div(uint256,uint256): 7, 0 -> FAILURE # throws #
// mod(uint256,uint256): 7, 2 -> 1
// mod(uint256,uint256): 7, 0 -> FAILURE # throws #

View File

@ -1,7 +1,9 @@
contract C { contract C {
function f() public pure returns (uint x) { function f() public pure returns (uint x) {
uint8 y = uint8(2)**uint8(8); unchecked {
return 0**y; uint8 y = uint8(2)**uint8(8);
return 0**y;
}
} }
} }

View File

@ -1,6 +1,8 @@
contract C { contract C {
function f() public pure returns (uint8 x) { function f() public pure returns (uint8 x) {
return uint8(0)**uint8(uint8(2)**uint8(8)); unchecked {
return uint8(0)**uint8(uint8(2)**uint8(8));
}
} }
} }

View File

@ -1,6 +1,8 @@
contract C { contract C {
function f() public pure returns (uint8 x) { function f() public pure returns (uint8 x) {
return uint8(0x166)**uint8(uint8(2)**uint8(8)); unchecked {
return uint8(0x166)**uint8(uint8(2)**uint8(8));
}
} }
} }

View File

@ -4,7 +4,9 @@ contract C {
// right before the exp // right before the exp
uint16 e = 0x100; uint16 e = 0x100;
uint8 b = 0x2; uint8 b = 0x2;
return b**e; unchecked {
return b**e;
}
} }
} }
// ---- // ----

View File

@ -1,8 +1,10 @@
contract test { contract test {
function f(uint x) public pure returns (uint, int) { function f(uint x) public pure returns (uint, int) {
uint a = 2 ** x; unchecked {
int b = -2 ** x; uint a = 2 ** x;
return (a, b); int b = -2 ** x;
return (a, b);
}
} }
} }
// ---- // ----

View File

@ -1,12 +1,13 @@
contract test { contract test {
function f() public pure returns (uint) { function f() public pure returns (uint r) {
uint32 x; uint32 x;
uint8 y; uint8 y;
assembly { assembly {
x := 0xfffffffffe x := 0xfffffffffe
y := 0x102 y := 0x102
} }
return x**y; unchecked { r = x**y; }
return r;
} }
} }
// ---- // ----

View File

@ -1,6 +1,6 @@
==== Source: s1.sol ==== ==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol"; import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); } function f() pure returns (uint) { return 1000 + g() - h(); }
==== Source: s2.sol ==== ==== Source: s2.sol ====
import {f as h} from "s1.sol"; import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; } function f() pure returns (uint) { return 2; }
@ -10,5 +10,7 @@ contract C {
return h() - f() - g(); return h() - f() - g();
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// foo() -> -4 // foo() -> 992

View File

@ -1,6 +1,6 @@
==== Source: s1.sol ==== ==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol"; import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); } function f() pure returns (uint) { return 100 + h() - g(); }
==== Source: s2.sol ==== ==== Source: s2.sol ====
import {f as h} from "s1.sol"; import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; } function f() pure returns (uint) { return 2; }
@ -12,5 +12,7 @@ contract C {
return f() - g() - h(); return f() - g() - h();
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// foo() -> -4 // foo() -> 0x60

View File

@ -1,6 +1,6 @@
==== Source: s1.sol ==== ==== Source: s1.sol ====
import {f as g, g as h} from "s2.sol"; import {f as g, g as h} from "s2.sol";
function f() pure returns (uint) { return h() - g(); } function f() pure returns (uint) { return 1000 + h() - g(); }
==== Source: s2.sol ==== ==== Source: s2.sol ====
import {f as h} from "s1.sol"; import {f as h} from "s1.sol";
function f() pure returns (uint) { return 2; } function f() pure returns (uint) { return 2; }
@ -9,8 +9,10 @@ function g() pure returns (uint) { return 4; }
import "s2.sol"; import "s2.sol";
contract C { contract C {
function foo() public pure returns (uint) { function foo() public pure returns (uint) {
return f() - g() - h(); return 10000 + f() - g() - h();
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// foo() -> -4 // foo() -> 0x2324

View File

@ -1,11 +1,15 @@
contract C { contract C {
function f() public returns (uint16 x) { function f() public returns (uint16 x) {
x = 0xffff; unchecked {
x += 32; x = 0xffff;
x <<= 8; x += 32;
x >>= 16; x <<= 8;
x >>= 16;
}
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 0x0 // f() -> 0x0

View File

@ -13,7 +13,7 @@ contract C {
return (2, 3); return (2, 3);
} }
function h(uint x, uint y) public pure returns (uint) { function h(uint x, uint y) public pure returns (uint) {
return x - y; unchecked { return x - y; }
} }
function i(bool b) public pure returns (bool) { function i(bool b) public pure returns (bool) {
return !b; return !b;
@ -28,6 +28,8 @@ contract C {
return a * 7; return a * 7;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// d() -> // d() ->
// e(), 1 wei -> 1 // e(), 1 wei -> 1

View File

@ -4,10 +4,10 @@ contract C {
uint16 b; uint16 b;
function f() public returns (uint256, uint256, uint256, uint256) { function f() public returns (uint256, uint256, uint256, uint256) {
a++; unchecked { a++; }
uint256 c = b; uint256 c = b;
delete b; delete b;
a -= 2; unchecked { a -= 2; }
return (x, c, b, a); return (x, c, b, a);
} }
} }

View File

@ -9,8 +9,10 @@ contract C {
returns (uint256 x1, uint256 x2, uint256 x3, uint256 x4) returns (uint256 x1, uint256 x2, uint256 x3, uint256 x4)
{ {
a = -2; a = -2;
b = (0 - uint8(a)) * 2; unchecked {
c = a * int8(120) * int8(121); b = (0 - uint8(a)) * 2;
c = a * int8(120) * int8(121);
}
x1 = uint256(a); x1 = uint256(a);
x2 = b; x2 = b;
x3 = uint256(c); x3 = uint256(c);
@ -18,5 +20,7 @@ contract C {
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// test() -> -2, 4, -112, 0 // test() -> -2, 4, -112, 0

View File

@ -1,7 +1,7 @@
contract test { contract test {
function f() public returns (bool) { function f() public returns (bool) {
int256 x = -2**255; int256 x = -2**255;
assert(-x == x); unchecked { assert(-x == x); }
return true; return true;
} }
} }

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint256,uint256): 5, 6 -> 11 // f(uint256,uint256): 5, 6 -> 11
// f(uint256,uint256): -2, 1 -> -1 // f(uint256,uint256): -2, 1 -> -1

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(int256,int256): 5, 6 -> 11 // f(int256,int256): 5, 6 -> 11
// f(int256,int256): -2, 1 -> -1 // f(int256,int256): -2, 1 -> -1

View File

@ -10,7 +10,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint256,uint256): 10, 3 -> 3 // f(uint256,uint256): 10, 3 -> 3
// f(uint256,uint256): 1, 0 -> FAILURE // f(uint256,uint256): 1, 0 -> FAILURE

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint256,uint256): 10, 3 -> 1 // f(uint256,uint256): 10, 3 -> 1
// f(uint256,uint256): 10, 2 -> 0 // f(uint256,uint256): 10, 2 -> 0

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(int256,int256): 10, 3 -> 1 // f(int256,int256): 10, 3 -> 1
// f(int256,int256): 10, 2 -> 0 // f(int256,int256): 10, 2 -> 0

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint256,uint256): 5, 6 -> 30 // f(uint256,uint256): 5, 6 -> 30
// f(uint256,uint256): -1, 1 -> -1 // f(uint256,uint256): -1, 1 -> -1

View File

@ -7,51 +7,42 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(int256,int256): 5, 6 -> 30 // f(int256,int256): 5, 6 -> 30
// f(int256,int256): -1, 1 -> -1 // f(int256,int256): -1, 1 -> -1
// f(int256,int256): -1, 2 -> -2 // f(int256,int256): -1, 2 -> -2 # positive, positive #
// # positive, positive #
// f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE // f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE
// f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE // f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE // f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE # positive, negative #
// # positive, negative #
// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, -2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, -2 -> 0x8000000000000000000000000000000000000000000000000000000000000000
// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000001, -2 -> FAILURE // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000001, -2 -> FAILURE
// f(int256,int256): 2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 // f(int256,int256): 2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000
// f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE // f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE # negative, positive #
// # negative, positive #
// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 // f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000
// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000001 -> FAILURE // f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000001 -> FAILURE
// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, 2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, 2 -> 0x8000000000000000000000000000000000000000000000000000000000000000
// f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE // f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE # negative, negative #
// # negative, negative #
// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000001, -2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000001, -2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, -2 -> FAILURE // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, -2 -> FAILURE
// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000001 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE // f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000001 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE
// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE // f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE # small type #
// # small type #
// g(int8,int8): 5, 6 -> 30 // g(int8,int8): 5, 6 -> 30
// g(int8,int8): -1, 1 -> -1 // g(int8,int8): -1, 1 -> -1
// g(int8,int8): -1, 2 -> -2 // g(int8,int8): -1, 2 -> -2 # positive, positive #
// # positive, positive #
// g(int8,int8): 63, 2 -> 126 // g(int8,int8): 63, 2 -> 126
// g(int8,int8): 64, 2 -> FAILURE // g(int8,int8): 64, 2 -> FAILURE
// g(int8,int8): 2, 63 -> 126 // g(int8,int8): 2, 63 -> 126
// g(int8,int8): 2, 64 -> FAILURE // g(int8,int8): 2, 64 -> FAILURE # positive, negative #
// # positive, negative #
// g(int8,int8): 64, -2 -> -128 // g(int8,int8): 64, -2 -> -128
// g(int8,int8): 65, -2 -> FAILURE // g(int8,int8): 65, -2 -> FAILURE
// g(int8,int8): 2, -64 -> -128 // g(int8,int8): 2, -64 -> -128
// g(int8,int8): 2, -65 -> FAILURE // g(int8,int8): 2, -65 -> FAILURE # negative, positive #
// # negative, positive #
// g(int8,int8): -2, 64 -> -128 // g(int8,int8): -2, 64 -> -128
// g(int8,int8): -2, 65 -> FAILURE // g(int8,int8): -2, 65 -> FAILURE
// g(int8,int8): -64, 2 -> -128 // g(int8,int8): -64, 2 -> -128
// g(int8,int8): -65, 2 -> FAILURE // g(int8,int8): -65, 2 -> FAILURE # negative, negative #
// # negative, negative #
// g(int8,int8): -63, -2 -> 126 // g(int8,int8): -63, -2 -> 126
// g(int8,int8): -64, -2 -> FAILURE // g(int8,int8): -64, -2 -> FAILURE
// g(int8,int8): -2, -63 -> 126 // g(int8,int8): -2, -63 -> 126

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint256,uint256): 6, 5 -> 1 // f(uint256,uint256): 6, 5 -> 1
// f(uint256,uint256): 6, 6 -> 0 // f(uint256,uint256): 6, 6 -> 0

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(int256,int256): 5, 6 -> -1 // f(int256,int256): 5, 6 -> -1
// f(int256,int256): -2, 1 -> -3 // f(int256,int256): -2, 1 -> -3

View File

@ -101,4 +101,4 @@ contract ERC20 {
// decreaseAllowance(address,uint256): 2, 0 -> true // decreaseAllowance(address,uint256): 2, 0 -> true
// decreaseAllowance(address,uint256): 2, 1 -> FAILURE // decreaseAllowance(address,uint256): 2, 1 -> FAILURE
// transfer(address,uint256): 2, 14 -> true // transfer(address,uint256): 2, 14 -> true
// transfer(address,uint256): 2, 2 -> FAILURE // transfer(address,uint256): 2, 2 -> FAILURE

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(int8,uint256): 2, 6 -> 64 // f(int8,uint256): 2, 6 -> 64
// f(int8,uint256): 2, 7 -> FAILURE // f(int8,uint256): 2, 7 -> FAILURE

View File

@ -7,7 +7,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: true // compileViaYul: also
// ---- // ----
// f(uint8,uint8): 2, 7 -> 0x80 // f(uint8,uint8): 2, 7 -> 0x80
// f(uint8,uint8): 2, 8 -> FAILURE // f(uint8,uint8): 2, 8 -> FAILURE

View File

@ -0,0 +1,9 @@
contract C {
function f() public pure {
for (unchecked { uint x = 2 }; x < 2; x ++) {
}
}
}
// ----
// ParserError 6933: (57-66): Expected primary expression.

View File

@ -0,0 +1,3 @@
function f() pure returns (uint) unchecked {}
// ----
// ParserError 5296: (33-42): "unchecked" blocks can only be used inside regular blocks.

View File

@ -0,0 +1,5 @@
contract C {
modifier m() { unchecked { _; } }
}
// ----
// SyntaxError 2573: (44-45): The placeholder statement "_" cannot be used inside an "unchecked" block.

View File

@ -0,0 +1,11 @@
contract C {
function f() public pure {
unchecked {
unchecked {
uint x = 2 + 3;
}
}
}
}
// ----
// SyntaxError 1941: (76-133): "unchecked" blocks cannot be nested.

View File

@ -0,0 +1,9 @@
contract C {
function f() public pure {
for (uint x = 2; x < 2; unchecked { x ++; }) {
}
}
}
// ----
// ParserError 6933: (76-85): Expected primary expression.

View File

@ -0,0 +1,8 @@
contract C {
uint x = unchecked { f() + 2 }
function f() public pure returns (uint) {
return 4;
}
}
// ----
// ParserError 6933: (26-35): Expected primary expression.

View File

@ -0,0 +1,9 @@
contract C {
function f() public pure {
while (true) unchecked {
}
}
}
// ----
// ParserError 5296: (65-74): "unchecked" blocks can only be used inside regular blocks.