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 | 			else | ||||||
|  | 			{ | ||||||
|  | 				m_context << u256(1); | ||||||
|  | 				m_context << Instruction::ADD; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			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; | 				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,6 +453,9 @@ 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: // -
 | ||||||
|  | 		if (m_context.arithmetic() == Arithmetic::Checked) | ||||||
|  | 			m_context.callYulFunction(m_context.utilFunctions().negateNumberCheckedFunction(type), 1, 1); | ||||||
|  | 		else | ||||||
| 			m_context << u256(0) << Instruction::SUB; | 			m_context << u256(0) << Instruction::SUB; | ||||||
| 		break; | 		break; | ||||||
| 	default: | 	default: | ||||||
| @ -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,6 +2131,36 @@ 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); | ||||||
|  | 	if (m_context.arithmetic() == Arithmetic::Checked) | ||||||
|  | 	{ | ||||||
|  | 		string functionName; | ||||||
|  | 		switch (_operator) | ||||||
|  | 		{ | ||||||
|  | 		case Token::Add: | ||||||
|  | 			functionName = m_context.utilFunctions().overflowCheckedIntAddFunction(type); | ||||||
|  | 			break; | ||||||
|  | 		case Token::Sub: | ||||||
|  | 			functionName = m_context.utilFunctions().overflowCheckedIntSubFunction(type); | ||||||
|  | 			break; | ||||||
|  | 		case Token::Mul: | ||||||
|  | 			functionName = m_context.utilFunctions().overflowCheckedIntMulFunction(type); | ||||||
|  | 			break; | ||||||
|  | 		case Token::Div: | ||||||
|  | 			functionName = m_context.utilFunctions().overflowCheckedIntDivFunction(type); | ||||||
|  | 			break; | ||||||
|  | 		case Token::Mod: | ||||||
|  | 			functionName = m_context.utilFunctions().intModFunction(type); | ||||||
|  | 			break; | ||||||
|  | 		case Token::Exp: | ||||||
|  | 			// EXP is handled in a different function.
 | ||||||
|  | 		default: | ||||||
|  | 			solAssert(false, "Unknown arithmetic operator."); | ||||||
|  | 		} | ||||||
|  | 		// TODO Maybe we want to force-inline this?
 | ||||||
|  | 		m_context.callYulFunction(functionName, 2, 1); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
| 		bool const c_isSigned = type.isSigned(); | 		bool const c_isSigned = type.isSigned(); | ||||||
| 
 | 
 | ||||||
| 		switch (_operator) | 		switch (_operator) | ||||||
| @ -2142,6 +2191,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons | |||||||
| 			solAssert(false, "Unknown arithmetic operator."); | 			solAssert(false, "Unknown arithmetic operator."); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void ExpressionCompiler::appendBitOperatorCode(Token _operator) | void ExpressionCompiler::appendBitOperatorCode(Token _operator) | ||||||
| { | { | ||||||
| @ -2237,6 +2287,13 @@ 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(), ""); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 	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; | 		m_context << Instruction::EXP; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -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,6 +289,7 @@ 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) { | ||||||
|  | 				unchecked { | ||||||
| 					int128 t = x / 256; | 					int128 t = x / 256; | ||||||
| 					int128 y = 5545177; | 					int128 y = 5545177; | ||||||
| 					x = t; | 					x = t; | ||||||
| @ -314,6 +315,7 @@ BOOST_AUTO_TEST_CASE(complex_control_flow) | |||||||
| 					return y; | 					return y; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	)"; | 	)"; | ||||||
| 	testCreationTimeGas(sourceCode); | 	testCreationTimeGas(sourceCode); | ||||||
| 	// max gas is used for small x
 | 	// max gas is used for small x
 | ||||||
|  | |||||||
| @ -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,12 +757,14 @@ 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) { | ||||||
|  | 				unchecked { | ||||||
| 					uint32 t = uint32(0xffffffff); | 					uint32 t = uint32(0xffffffff); | ||||||
| 					uint32 x = t + 10; | 					uint32 x = t + 10; | ||||||
| 					if (x >= 0xffffffff) return 0; | 					if (x >= 0xffffffff) return 0; | ||||||
| 					return x; | 					return x; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	)"; | 	)"; | ||||||
| 	compileAndRun(sourceCode); | 	compileAndRun(sourceCode); | ||||||
| 	auto high_bits_cleaning_cpp = []() -> u256 | 	auto high_bits_cleaning_cpp = []() -> u256 | ||||||
| @ -781,11 +783,13 @@ 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) { | ||||||
|  | 				unchecked { | ||||||
| 					int64 x = -int32(0xff); | 					int64 x = -int32(0xff); | ||||||
| 					if (x >= 0xff) return 0; | 					if (x >= 0xff) return 0; | ||||||
| 					return 0 - uint256(x); | 					return 0 - uint256(x); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	)"; | 	)"; | ||||||
| 	compileAndRun(sourceCode); | 	compileAndRun(sourceCode); | ||||||
| 	auto sign_extension_cpp = []() -> u256 | 	auto sign_extension_cpp = []() -> u256 | ||||||
| @ -803,11 +807,13 @@ 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) { | ||||||
|  | 				unchecked { | ||||||
| 					uint32 t = uint32(0xffffff); | 					uint32 t = uint32(0xffffff); | ||||||
| 					uint32 x = t * 0xffffff; | 					uint32 x = t * 0xffffff; | ||||||
| 					return x / 0x100; | 					return x / 0x100; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	)"; | 	)"; | ||||||
| 	compileAndRun(sourceCode); | 	compileAndRun(sourceCode); | ||||||
| 	auto small_unsigned_types_cpp = []() -> u256 | 	auto small_unsigned_types_cpp = []() -> u256 | ||||||
| @ -1804,10 +1810,13 @@ 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++) { | ||||||
|  | 					unchecked { | ||||||
| 						r[i] = blockhash(block.number - 257 + i); | 						r[i] = blockhash(block.number - 257 + i); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	)"; | 	)"; | ||||||
| 	compileAndRun(sourceCode); | 	compileAndRun(sourceCode); | ||||||
| 	// generate a sufficient amount of blocks
 | 	// generate a sufficient amount of blocks
 | ||||||
| @ -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,9 +1,11 @@ | |||||||
| contract C { | contract C { | ||||||
|     function f() public pure returns (uint x) { |     function f() public pure returns (uint x) { | ||||||
|  |         unchecked { | ||||||
|             uint8 y = uint8(2)**uint8(8); |             uint8 y = uint8(2)**uint8(8); | ||||||
|             return 0**y; |             return 0**y; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // ---- | // ---- | ||||||
| // f() -> 0x1 | // f() -> 0x1 | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| contract C { | contract C { | ||||||
|     function f() public pure returns (uint8 x) { |     function f() public pure returns (uint8 x) { | ||||||
|  |         unchecked { | ||||||
|             return uint8(0)**uint8(uint8(2)**uint8(8)); |             return uint8(0)**uint8(uint8(2)**uint8(8)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // ---- | // ---- | ||||||
| // f() -> 0x1 | // f() -> 0x1 | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| contract C { | contract C { | ||||||
|     function f() public pure returns (uint8 x) { |     function f() public pure returns (uint8 x) { | ||||||
|  |         unchecked { | ||||||
|             return uint8(0x166)**uint8(uint8(2)**uint8(8)); |             return uint8(0x166)**uint8(uint8(2)**uint8(8)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| // ---- | // ---- | ||||||
| // f() -> 0x1 | // f() -> 0x1 | ||||||
|  | |||||||
| @ -4,8 +4,10 @@ contract C { | |||||||
|         // right before the exp |         // right before the exp | ||||||
|         uint16 e = 0x100; |         uint16 e = 0x100; | ||||||
|         uint8 b = 0x2; |         uint8 b = 0x2; | ||||||
|  |         unchecked { | ||||||
|             return b**e; |             return b**e; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| // ---- | // ---- | ||||||
| // f() -> 0x00 | // f() -> 0x00 | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| contract test { | contract test { | ||||||
|     function f(uint x) public pure returns (uint, int) { |     function f(uint x) public pure returns (uint, int) { | ||||||
|  |         unchecked { | ||||||
|             uint a = 2 ** x; |             uint a = 2 ** x; | ||||||
|             int b = -2 ** x; |             int b = -2 ** x; | ||||||
|             return (a, b); |             return (a, b); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| // ---- | // ---- | ||||||
| // f(uint256): 0 -> 1, 1 | // f(uint256): 0 -> 1, 1 | ||||||
| // f(uint256): 1 -> 2, -2 | // f(uint256): 1 -> 2, -2 | ||||||
|  | |||||||
| @ -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) { | ||||||
|  |         unchecked { | ||||||
|             x = 0xffff; |             x = 0xffff; | ||||||
|             x += 32; |             x += 32; | ||||||
|             x <<= 8; |             x <<= 8; | ||||||
|             x >>= 16; |             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; | ||||||
|  |         unchecked { | ||||||
|             b = (0 - uint8(a)) * 2; |             b = (0 - uint8(a)) * 2; | ||||||
|             c = a * int8(120) * int8(121); |             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 | ||||||
|  | |||||||
| @ -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