mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9465 from ethereum/unchecked
Checked arithmetic by default.
This commit is contained in:
commit
33a5fbf7a6
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -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``.
|
||||||
|
@ -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. */ \
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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, "");
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
@ -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);
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
@ -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"}});
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
|
@ -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
|
@ -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
|
13
test/libsolidity/semanticTests/arithmetics/checked_add.sol
Normal file
13
test/libsolidity/semanticTests/arithmetics/checked_add.sol
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
||||||
|
@ -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
|
@ -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 #
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
@ -0,0 +1,3 @@
|
|||||||
|
function f() pure returns (uint) unchecked {}
|
||||||
|
// ----
|
||||||
|
// ParserError 5296: (33-42): "unchecked" blocks can only be used inside regular blocks.
|
@ -0,0 +1,5 @@
|
|||||||
|
contract C {
|
||||||
|
modifier m() { unchecked { _; } }
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// SyntaxError 2573: (44-45): The placeholder statement "_" cannot be used inside an "unchecked" block.
|
11
test/libsolidity/syntaxTests/unchecked/unchecked_nested.sol
Normal file
11
test/libsolidity/syntaxTests/unchecked/unchecked_nested.sol
Normal 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.
|
@ -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.
|
@ -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.
|
@ -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.
|
Loading…
Reference in New Issue
Block a user