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: | ||||
|  * 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: 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)``. | ||||
| @ -15,6 +16,9 @@ Language Features: | ||||
|  * New AST Node ``IdentifierPath`` replacing in many places the ``UserDefinedTypeName`` | ||||
| 
 | ||||
| 
 | ||||
| AST Changes: | ||||
|  * New node type: unchecked block - used for ``unchecked { ... }``. | ||||
| 
 | ||||
| ### 0.7.4 (unreleased) | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| .. _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 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 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 call a zero-initialized variable of internal function type. | ||||
| #. 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. | ||||
|  */ | ||||
| block: LBrace statement* RBrace; | ||||
| block: | ||||
| 	LBrace ( statement | uncheckedBlock )* RBrace; | ||||
| 
 | ||||
| uncheckedBlock: Unchecked block; | ||||
| 
 | ||||
| statement: | ||||
| 	block | ||||
|  | ||||
| @ -7,7 +7,7 @@ ReservedKeywords: | ||||
| 	'after' | 'alias' | 'apply' | 'auto' | 'case' | 'copyof' | 'default' | 'define' | 'final' | ||||
| 	| 'implements' | 'in' | 'inline' | 'let' | 'macro' | 'match' | 'mutable' | 'null' | 'of' | ||||
| 	| 'partial' | 'promise' | 'reference' | 'relocatable' | 'sealed' | 'sizeof' | 'static' | ||||
| 	| 'supports' | 'switch' | 'typedef' | 'typeof' | 'unchecked' | 'var'; | ||||
| 	| 'supports' | 'switch' | 'typedef' | 'typeof' | 'var'; | ||||
| 
 | ||||
| Pragma: 'pragma' -> pushMode(PragmaMode); | ||||
| Abstract: 'abstract'; | ||||
| @ -87,6 +87,7 @@ True: 'true'; | ||||
| Try: 'try'; | ||||
| Type: 'type'; | ||||
| Ufixed: 'ufixed' | ('ufixed' [0-9]+ 'x' [0-9]+); | ||||
| Unchecked: 'unchecked'; | ||||
| /** | ||||
|  * Sized unsigned integer types. | ||||
|  * 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. | ||||
| They resemble integers when the values are small, but behave differently if the numbers are larger. | ||||
| 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 | ||||
| to store a number (or piece of data) that is outside the range of the variable's data type. | ||||
| An *underflow* is the converse situation: ``uint8(0) - uint8(1) == 255``. | ||||
| They resemble integers when the values are small, but cannot represent arbitrarily large numbers. | ||||
| 
 | ||||
| The following code causes an overflow because the result of the addition is too large | ||||
| to be stored in the type ``uint8``: | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|   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 | ||||
| more special edge cases for signed numbers. | ||||
| 
 | ||||
| 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 | ||||
| `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. | ||||
| :ref:`SMT checker<smt_checker>` to find potential overflows. | ||||
| 
 | ||||
| .. _clearing-mappings: | ||||
| 
 | ||||
|  | ||||
| @ -46,8 +46,10 @@ access the minimum and maximum value representable by the type. | ||||
| .. warning:: | ||||
| 
 | ||||
|   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 | ||||
|   serious consequences that you should :ref:`be aware of and mitigate against<underflow-overflow>`. | ||||
|   There are two modes in which arithmetic is performed on these types: The "wrapping" or "unchecked" mode and the "checked" mode. | ||||
|   By default, arithmetic is always "checked", which mean that if the result of an operation falls outside the value range | ||||
|   of the type, the call is reverted through a :ref:`failing assertion<assert-and-require>`. You can switch to "unchecked" mode | ||||
|   using ``unchecked { ... }``. More details can be found in the section about :ref:`unchecked <unchecked>`. | ||||
| 
 | ||||
| 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 have the usual semantics. | ||||
| They wrap in two's complement representation, meaning that | ||||
| for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows | ||||
| into account when designing safe smart contracts. | ||||
| Addition, subtraction and multiplication have the usual semantics, with two different | ||||
| modes in regard to over- and underflow: | ||||
| 
 | ||||
| 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 | ||||
| ``T`` is the type of ``x``. It can only be applied to signed types. | ||||
| The value of ``-x`` can be | ||||
| positive if ``x`` is negative. There is another caveat also resulting | ||||
| 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. | ||||
| from two's complement representation: | ||||
| 
 | ||||
| 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 | ||||
| ^^^^^^^^ | ||||
| @ -106,7 +107,12 @@ Note that in contrast, division on :ref:`literals<rational_literals>` results in | ||||
| of arbitrary precision. | ||||
| 
 | ||||
| .. 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 | ||||
| ^^^^^^ | ||||
| @ -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)`` | ||||
| 
 | ||||
| .. 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 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 | ||||
| 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 that ``0**0`` is defined by the EVM as ``1``. | ||||
|  | ||||
| @ -191,6 +191,7 @@ namespace solidity::langutil | ||||
| 	K(Throw, "throw", 0)                                               \ | ||||
| 	K(Try, "try", 0)                                                   \ | ||||
| 	K(Type, "type", 0)                                                 \ | ||||
| 	K(Unchecked, "unchecked", 0)                                       \ | ||||
| 	K(Unicode, "unicode", 0)                                           \ | ||||
| 	K(Using, "using", 0)                                               \ | ||||
| 	K(View, "view", 0)                                                 \ | ||||
| @ -266,7 +267,6 @@ namespace solidity::langutil | ||||
| 	K(Switch, "switch", 0)                                             \ | ||||
| 	K(Typedef, "typedef", 0)                                           \ | ||||
| 	K(TypeOf, "typeof", 0)                                             \ | ||||
| 	K(Unchecked, "unchecked", 0)                                       \ | ||||
| 	K(Var, "var", 0)                                                   \ | ||||
| 	\ | ||||
| 	/* Yul-specific tokens, but not keywords. */                       \ | ||||
|  | ||||
| @ -190,6 +190,28 @@ void SyntaxChecker::endVisit(ForStatement const&) | ||||
| 	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) | ||||
| { | ||||
| 	if (m_inLoopDepth <= 0) | ||||
| @ -288,8 +310,15 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly) | ||||
| 	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; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @ -71,6 +71,9 @@ private: | ||||
| 	bool visit(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(Break const& _breakStatement) override; | ||||
| 
 | ||||
| @ -100,6 +103,9 @@ private: | ||||
| 	/// Flag that indicates whether some version pragma was present.
 | ||||
| 	bool m_versionPragmaFound = false; | ||||
| 
 | ||||
| 	/// Flag that indicates whether we are inside an unchecked block.
 | ||||
| 	bool m_uncheckedArithmetic = false; | ||||
| 
 | ||||
| 	int m_inLoopDepth = 0; | ||||
| 	std::optional<ContractKind> m_currentContractKind; | ||||
| 
 | ||||
|  | ||||
| @ -1384,18 +1384,24 @@ public: | ||||
| 		int64_t _id, | ||||
| 		SourceLocation const& _location, | ||||
| 		ASTPointer<ASTString> const& _docString, | ||||
| 		bool _unchecked, | ||||
| 		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(ASTConstVisitor& _visitor) const override; | ||||
| 
 | ||||
| 	std::vector<ASTPointer<Statement>> const& statements() const { return m_statements; } | ||||
| 	bool unchecked() const { return m_unchecked; } | ||||
| 
 | ||||
| 	BlockAnnotation& annotation() const override; | ||||
| 
 | ||||
| private: | ||||
| 	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.
 | ||||
| enum class Visibility { Default, Private, Internal, Public, External }; | ||||
| 
 | ||||
| enum class Arithmetic { Checked, Wrapping }; | ||||
| 
 | ||||
| inline std::string stateMutabilityToString(StateMutability const& _stateMutability) | ||||
| { | ||||
| 	switch (_stateMutability) | ||||
|  | ||||
| @ -596,7 +596,7 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node) | ||||
| 
 | ||||
| bool ASTJsonConverter::visit(Block const& _node) | ||||
| { | ||||
| 	setJsonNode(_node, "Block", { | ||||
| 	setJsonNode(_node, _node.unchecked() ? "UncheckedBlock" : "Block", { | ||||
| 		make_pair("statements", toJson(_node.statements())) | ||||
| 	}); | ||||
| 	return false; | ||||
|  | ||||
| @ -154,7 +154,9 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js | ||||
| 	if (nodeType == "InlineAssembly") | ||||
| 		return createInlineAssembly(_json); | ||||
| 	if (nodeType == "Block") | ||||
| 		return createBlock(_json); | ||||
| 		return createBlock(_json, false); | ||||
| 	if (nodeType == "UncheckedBlock") | ||||
| 		return createBlock(_json, true); | ||||
| 	if (nodeType == "PlaceholderStatement") | ||||
| 		return createPlaceholderStatement(_json); | ||||
| 	if (nodeType == "IfStatement") | ||||
| @ -439,7 +441,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V | ||||
| 		createParameterList(member(_node, "parameters")), | ||||
| 		modifiers, | ||||
| 		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")), | ||||
| 		memberAsBool(_node, "virtual"), | ||||
| 		_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; | ||||
| 	for (auto& stat: member(_node, "statements")) | ||||
| @ -597,6 +599,7 @@ ASTPointer<Block> ASTJsonImporter::createBlock(Json::Value const& _node) | ||||
| 	return createASTNode<Block>( | ||||
| 		_node, | ||||
| 		nullOrASTString(_node, "documentation"), | ||||
| 		_unchecked, | ||||
| 		statements | ||||
| 	); | ||||
| } | ||||
|  | ||||
| @ -93,7 +93,7 @@ private: | ||||
| 	ASTPointer<Mapping> createMapping(Json::Value const& _node); | ||||
| 	ASTPointer<ArrayTypeName> createArrayTypeName(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<IfStatement> createIfStatement(Json::Value const& _node); | ||||
| 	ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node); | ||||
|  | ||||
| @ -123,6 +123,9 @@ public: | ||||
| 	void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; } | ||||
| 	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
 | ||||
| 	/// (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,
 | ||||
| @ -380,6 +383,8 @@ private: | ||||
| 	std::map<Declaration const*, std::vector<unsigned>> m_localVariables; | ||||
| 	/// The contract currently being compiled. Virtual function lookup starts from this contarct.
 | ||||
| 	ContractDefinition const* m_mostDerivedContract = nullptr; | ||||
| 	/// Whether to use checked arithmetic.
 | ||||
| 	Arithmetic m_arithmetic = Arithmetic::Checked; | ||||
| 	/// Stack of current visited AST nodes, used for location attachment
 | ||||
| 	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.
 | ||||
|  | ||||
| @ -1247,19 +1247,31 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) | ||||
| { | ||||
| 	StackHeightChecker checker(m_context); | ||||
| 	CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); | ||||
| 	solAssert(m_context.arithmetic() == Arithmetic::Checked, "Placeholder cannot be used inside checked block."); | ||||
| 	appendModifierOrFunctionCode(); | ||||
| 	solAssert(m_context.arithmetic() == Arithmetic::Checked, "Arithmetic not reset to 'checked'."); | ||||
| 	checker.check(); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool ContractCompiler::visit(Block const& _block) | ||||
| { | ||||
| 	if (_block.unchecked()) | ||||
| 	{ | ||||
| 		solAssert(m_context.arithmetic() == Arithmetic::Checked, ""); | ||||
| 		m_context.setArithmetic(Arithmetic::Wrapping); | ||||
| 	} | ||||
| 	storeStackHeight(&_block); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| 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.
 | ||||
| 	popScopedVariables(&_block); | ||||
| } | ||||
| @ -1327,6 +1339,8 @@ void ContractCompiler::appendModifierOrFunctionCode() | ||||
| 
 | ||||
| 	if (codeBlock) | ||||
| 	{ | ||||
| 		m_context.setArithmetic(Arithmetic::Checked); | ||||
| 
 | ||||
| 		std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive(); | ||||
| 		m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures); | ||||
| 
 | ||||
|  | ||||
| @ -275,7 +275,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) | ||||
| 		solAssert(*_assignment.annotation().type == leftType, ""); | ||||
| 	bool cleanupNeeded = false; | ||||
| 	if (op != Token::Assign) | ||||
| 		cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); | ||||
| 		cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp, m_context.arithmetic()); | ||||
| 	_assignment.rightHandSide().accept(*this); | ||||
| 	// 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.
 | ||||
| @ -381,9 +381,10 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) | ||||
| bool ExpressionCompiler::visit(UnaryOperation const& _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; | ||||
| 	} | ||||
| 
 | ||||
| @ -406,24 +407,39 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) | ||||
| 	case Token::Dec: // -- (pre- or postfix)
 | ||||
| 		solAssert(!!m_currentLValue, "LValue not retrieved."); | ||||
| 		solUnimplementedAssert( | ||||
| 			_unaryOperation.annotation().type->category() != Type::Category::FixedPoint, | ||||
| 			type.category() != Type::Category::FixedPoint, | ||||
| 			"Not yet implemented - FixedPointType." | ||||
| 		); | ||||
| 		m_currentLValue->retrieveValue(_unaryOperation.location()); | ||||
| 		if (!_unaryOperation.isPrefixOperation()) | ||||
| 		{ | ||||
| 			// 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; | ||||
| 			if (m_currentLValue->sizeOnStack() > 0) | ||||
| 				for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) | ||||
| 					m_context << swapInstruction(i); | ||||
| 		} | ||||
| 		m_context << u256(1); | ||||
| 		if (_unaryOperation.getOperator() == Token::Inc) | ||||
| 			m_context << Instruction::ADD; | ||||
| 		{ | ||||
| 			if (m_context.arithmetic() == Arithmetic::Checked) | ||||
| 				m_context.callYulFunction(m_context.utilFunctions().incrementCheckedFunction(type), 1, 1); | ||||
| 			else | ||||
| 			{ | ||||
| 				m_context << u256(1); | ||||
| 				m_context << Instruction::ADD; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (m_context.arithmetic() == Arithmetic::Checked) | ||||
| 				m_context.callYulFunction(m_context.utilFunctions().decrementCheckedFunction(type), 1, 1); | ||||
| 			else | ||||
| 			{ | ||||
| 				m_context << u256(1); | ||||
| 				m_context << Instruction::SWAP1 << Instruction::SUB; | ||||
| 			} | ||||
| 		} | ||||
| 		// Stack for prefix: [ref...] (*ref)+-1
 | ||||
| 		// Stack for postfix: *ref [ref...] (*ref)+-1
 | ||||
| 		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
 | ||||
| 		break; | ||||
| 	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; | ||||
| 		break; | ||||
| 	default: | ||||
| @ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) | ||||
| 		m_context << commonType->literalValue(nullptr); | ||||
| 	else | ||||
| 	{ | ||||
| 		bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); | ||||
| 		bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op, m_context.arithmetic()); | ||||
| 
 | ||||
| 		TypePointer leftTargetType = commonType; | ||||
| 		TypePointer rightTargetType = | ||||
| @ -2112,6 +2131,36 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons | ||||
| 		solUnimplemented("Not yet implemented - FixedPointType."); | ||||
| 
 | ||||
| 	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(); | ||||
| 
 | ||||
| 		switch (_operator) | ||||
| @ -2141,6 +2190,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons | ||||
| 		default: | ||||
| 			solAssert(false, "Unknown arithmetic 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(!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; | ||||
| } | ||||
| 
 | ||||
| @ -2561,11 +2618,15 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) | ||||
| 	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)) | ||||
| 		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
 | ||||
| 		// It would suffice to clean the exponent, though.
 | ||||
| 		return true; | ||||
|  | ||||
| @ -132,7 +132,7 @@ private: | ||||
| 
 | ||||
| 	/// @returns true if the operator applied to the given type requires a cleanup prior to the
 | ||||
| 	/// 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); | ||||
| 
 | ||||
|  | ||||
| @ -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 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 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 | ||||
| 			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( | ||||
| 	IntegerType const& _type, | ||||
| 	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 functionName = "extract_byte_array_length"; | ||||
| @ -2951,30 +3044,39 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type) | ||||
| 	string const functionName = "decrement_" + _type.identifier(); | ||||
| 
 | ||||
| 	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"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				value := <cleanupFunction>(value) | ||||
| 				if <lt>(value, <minval>) { <panic>() } | ||||
| 				if eq(value, <minval>) { <panic>() } | ||||
| 				ret := sub(value, 1) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("panic", panicFunction()) | ||||
| 		("minval", toCompactHexWithPrefix(minintval)) | ||||
| 		("lt", type.isSigned() ? "slt" : "lt") | ||||
| 		("minval", toCompactHexWithPrefix(type.min())) | ||||
| 		("cleanupFunction", cleanupFunction(_type)) | ||||
| 		.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) | ||||
| { | ||||
| 	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(); | ||||
| 
 | ||||
| 	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"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				value := <cleanupFunction>(value) | ||||
| 				if <gt>(value, <maxval>) { <panic>() } | ||||
| 				if eq(value, <maxval>) { <panic>() } | ||||
| 				ret := add(value, 1) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("maxval", toCompactHexWithPrefix(maxintval)) | ||||
| 		("gt", type.isSigned() ? "sgt" : "gt") | ||||
| 		("maxval", toCompactHexWithPrefix(type.max())) | ||||
| 		("panic", panicFunction()) | ||||
| 		("cleanupFunction", cleanupFunction(_type)) | ||||
| 		.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) | ||||
| { | ||||
| 	IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 	solAssert(type.isSigned(), "Expected signed type!"); | ||||
| 
 | ||||
| 	string const functionName = "negate_" + _type.identifier(); | ||||
| 
 | ||||
| 	u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; | ||||
| 
 | ||||
| 	return m_functionCollector.createFunction(functionName, [&]() { | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				value := <cleanupFunction>(value) | ||||
| 				if slt(value, <minval>) { <panic>() } | ||||
| 				if eq(value, <minval>) { <panic>() } | ||||
| 				ret := sub(0, value) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("minval", toCompactHexWithPrefix(minintval)) | ||||
| 		("minval", toCompactHexWithPrefix(type.min())) | ||||
| 		("cleanupFunction", cleanupFunction(_type)) | ||||
| 		("panic", panicFunction()) | ||||
| 		.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) | ||||
| { | ||||
| 	solAssert(_type.category() != Type::Category::Mapping, ""); | ||||
|  | ||||
| @ -106,24 +106,35 @@ public: | ||||
| 
 | ||||
| 	/// signature: (x, y) -> sum
 | ||||
| 	std::string overflowCheckedIntAddFunction(IntegerType const& _type); | ||||
| 	/// signature: (x, y) -> sum
 | ||||
| 	std::string wrappingIntAddFunction(IntegerType const& _type); | ||||
| 
 | ||||
| 	/// signature: (x, y) -> product
 | ||||
| 	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.
 | ||||
| 	/// Checks for division by zero and the special case of
 | ||||
| 	/// signed division of the smallest number by -1.
 | ||||
| 	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.
 | ||||
| 	/// Reverts for modulo by zero.
 | ||||
| 	std::string checkedIntModFunction(IntegerType const& _type); | ||||
| 	std::string intModFunction(IntegerType const& _type); | ||||
| 
 | ||||
| 	/// @returns computes the difference between two values.
 | ||||
| 	/// Assumes the input to be in range for the type.
 | ||||
| 	/// signature: (x, y) -> diff
 | ||||
| 	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.
 | ||||
| 	/// signature: (base, exponent) -> power
 | ||||
| 	std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType); | ||||
| @ -151,6 +162,10 @@ public: | ||||
| 	/// signature: (power, base, exponent, max) -> power
 | ||||
| 	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
 | ||||
| 	/// array
 | ||||
| 	/// signature: (array) -> length
 | ||||
| @ -367,9 +382,12 @@ public: | ||||
| 	std::string forwardingRevertFunction(); | ||||
| 
 | ||||
| 	std::string incrementCheckedFunction(Type const& _type); | ||||
| 	std::string incrementWrappingFunction(Type const& _type); | ||||
| 	std::string decrementCheckedFunction(Type const& _type); | ||||
| 	std::string decrementWrappingFunction(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
 | ||||
| 	/// provided type.
 | ||||
|  | ||||
| @ -132,6 +132,9 @@ public: | ||||
| 
 | ||||
| 	langutil::EVMVersion evmVersion() const { return m_evmVersion; }; | ||||
| 
 | ||||
| 	void setArithmetic(Arithmetic _value) { m_arithmetic = _value; } | ||||
| 	Arithmetic arithmetic() const { return m_arithmetic; } | ||||
| 
 | ||||
| 	ABIFunctions abiFunctions(); | ||||
| 
 | ||||
| 	/// @returns code that stores @param _message for revert reason
 | ||||
| @ -161,6 +164,8 @@ private: | ||||
| 	std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; | ||||
| 	MultiUseYulFunctionCollector m_functions; | ||||
| 	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.
 | ||||
| 	bool m_inlineAssemblySeen = false; | ||||
|  | ||||
| @ -474,6 +474,25 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) | ||||
| 	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) | ||||
| { | ||||
| 	_ifStatement.condition().accept(*this); | ||||
| @ -618,11 +637,11 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) | ||||
| 		else if (op == Token::Sub) | ||||
| 		{ | ||||
| 			IntegerType const& intType = *dynamic_cast<IntegerType const*>(&resultType); | ||||
| 			define(_unaryOperation) << | ||||
| 				m_utils.negateNumberCheckedFunction(intType) << | ||||
| 				"(" << | ||||
| 				IRVariable(_unaryOperation.subExpression()).name() << | ||||
| 				")\n"; | ||||
| 			define(_unaryOperation) << ( | ||||
| 				m_context.arithmetic() == Arithmetic::Checked ? | ||||
| 				m_utils.negateNumberCheckedFunction(intType) : | ||||
| 				m_utils.negateNumberWrappingFunction(intType) | ||||
| 			) << "(" << IRVariable(_unaryOperation.subExpression()).name() << ")\n"; | ||||
| 		} | ||||
| 		else | ||||
| 			solUnimplementedAssert(false, "Unary operator not yet implemented"); | ||||
| @ -2560,23 +2579,23 @@ string IRGeneratorForStatements::binaryOperation( | ||||
| 	if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) | ||||
| 	{ | ||||
| 		string fun; | ||||
| 		// TODO: Implement all operations for signed and unsigned types.
 | ||||
| 		bool checked = m_context.arithmetic() == Arithmetic::Checked; | ||||
| 		switch (_operator) | ||||
| 		{ | ||||
| 			case Token::Add: | ||||
| 				fun = m_utils.overflowCheckedIntAddFunction(*type); | ||||
| 				fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type); | ||||
| 				break; | ||||
| 			case Token::Sub: | ||||
| 				fun = m_utils.overflowCheckedIntSubFunction(*type); | ||||
| 				fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type); | ||||
| 				break; | ||||
| 			case Token::Mul: | ||||
| 				fun = m_utils.overflowCheckedIntMulFunction(*type); | ||||
| 				fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type); | ||||
| 				break; | ||||
| 			case Token::Div: | ||||
| 				fun = m_utils.overflowCheckedIntDivFunction(*type); | ||||
| 				fun = checked ?  m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type); | ||||
| 				break; | ||||
| 			case Token::Mod: | ||||
| 				fun = m_utils.checkedIntModFunction(*type); | ||||
| 				fun = m_utils.intModFunction(*type); | ||||
| 				break; | ||||
| 			case Token::BitOr: | ||||
| 				fun = "or"; | ||||
|  | ||||
| @ -66,6 +66,8 @@ public: | ||||
| 	bool visit(Conditional const& _conditional) override; | ||||
| 	bool visit(Assignment const& _assignment) 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(ForStatement const& _forStatement) override; | ||||
| 	bool visit(WhileStatement const& _whileStatement) override; | ||||
|  | ||||
| @ -1096,16 +1096,23 @@ ASTPointer<ParameterList> Parser::parseParameterList( | ||||
| 	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); | ||||
| 	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); | ||||
| 	vector<ASTPointer<Statement>> statements; | ||||
| 	try | ||||
| 	{ | ||||
| 		while (m_scanner->currentToken() != Token::RBrace) | ||||
| 			statements.push_back(parseStatement()); | ||||
| 			statements.push_back(parseStatement(true)); | ||||
| 		nodeFactory.markEndPosition(); | ||||
| 	} | ||||
| 	catch (FatalError const&) | ||||
| @ -1122,10 +1129,10 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString) | ||||
| 		expectTokenOrConsumeUntil(Token::RBrace, "Block"); | ||||
| 	else | ||||
| 		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); | ||||
| 	ASTPointer<ASTString> docString; | ||||
| @ -1144,9 +1151,9 @@ ASTPointer<Statement> Parser::parseStatement() | ||||
| 			return parseDoWhileStatement(docString); | ||||
| 		case Token::For: | ||||
| 			return parseForStatement(docString); | ||||
| 		case Token::Unchecked: | ||||
| 		case Token::LBrace: | ||||
| 			return parseBlock(docString); | ||||
| 			// starting from here, all statements must be terminated by a semicolon
 | ||||
| 			return parseBlock(_allowUnchecked, docString); | ||||
| 		case Token::Continue: | ||||
| 			statement = ASTNodeFactory(*this).createNode<Continue>(docString); | ||||
| 			m_scanner->next(); | ||||
|  | ||||
| @ -115,8 +115,8 @@ private: | ||||
| 		VarDeclParserOptions const& _options = {}, | ||||
| 		bool _allowEmpty = true | ||||
| 	); | ||||
| 	ASTPointer<Block> parseBlock(ASTPointer<ASTString> const& _docString = {}); | ||||
| 	ASTPointer<Statement> parseStatement(); | ||||
| 	ASTPointer<Block> parseBlock(bool _allowUncheckedBlock = false, ASTPointer<ASTString> const& _docString = {}); | ||||
| 	ASTPointer<Statement> parseStatement(bool _allowUncheckedBlock = false); | ||||
| 	ASTPointer<InlineAssembly> parseInlineAssembly(ASTPointer<ASTString> const& _docString = {}); | ||||
| 	ASTPointer<IfStatement> parseIfStatement(ASTPointer<ASTString> const& _docString); | ||||
| 	ASTPointer<TryStatement> parseTryStatement(ASTPointer<ASTString> const& _docString); | ||||
|  | ||||
| @ -33,7 +33,7 @@ object "Arraysum_33" { | ||||
|                         for { } | ||||
|                         lt(vloc_i, _2) | ||||
|                         { | ||||
|                             if gt(vloc_i, not(1)) { invalid() } | ||||
|                             if eq(vloc_i, not(0)) { invalid() } | ||||
|                             vloc_i := add(vloc_i, 1) | ||||
|                         } | ||||
|                         { | ||||
|  | ||||
| @ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(function_calls) | ||||
| 			uint data2; | ||||
| 			function f(uint x) public { | ||||
| 				if (x > 7) | ||||
| 					data2 = g(x**8) + 1; | ||||
| 					{ unchecked { data2 = g(x**8) + 1; } } | ||||
| 				else | ||||
| 					data = 1; | ||||
| 			} | ||||
| @ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(multiple_external_functions) | ||||
| 			uint data2; | ||||
| 			function f(uint x) public { | ||||
| 				if (x > 7) | ||||
| 					data2 = g(x**8) + 1; | ||||
| 					{ unchecked { data2 = g(x**8) + 1; } } | ||||
| 				else | ||||
| 					data = 1; | ||||
| 			} | ||||
| @ -223,13 +223,13 @@ BOOST_AUTO_TEST_CASE(exponent_size) | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract A { | ||||
| 			function f(uint x) public returns (uint) { | ||||
| 				return x ** 0; | ||||
| 				unchecked { return x ** 0; } | ||||
| 			} | ||||
| 			function g(uint x) public returns (uint) { | ||||
| 				return x ** 0x100; | ||||
| 				unchecked { return x ** 0x100; } | ||||
| 			} | ||||
| 			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"( | ||||
| 		contract log { | ||||
| 			function ln(int128 x) public pure returns (int128 result) { | ||||
| 				unchecked { | ||||
| 					int128 t = x / 256; | ||||
| 					int128 y = 5545177; | ||||
| 					x = t; | ||||
| @ -314,6 +315,7 @@ BOOST_AUTO_TEST_CASE(complex_control_flow) | ||||
| 					return y; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	testCreationTimeGas(sourceCode); | ||||
| 	// 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 { | ||||
| 			uint x; | ||||
| 			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); | ||||
|  | ||||
| @ -757,12 +757,14 @@ BOOST_AUTO_TEST_CASE(high_bits_cleaning) | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract test { | ||||
| 			function run() public returns(uint256 y) { | ||||
| 				unchecked { | ||||
| 					uint32 t = uint32(0xffffffff); | ||||
| 					uint32 x = t + 10; | ||||
| 					if (x >= 0xffffffff) return 0; | ||||
| 					return x; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	auto high_bits_cleaning_cpp = []() -> u256 | ||||
| @ -781,11 +783,13 @@ BOOST_AUTO_TEST_CASE(sign_extension) | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract test { | ||||
| 			function run() public returns(uint256 y) { | ||||
| 				unchecked { | ||||
| 					int64 x = -int32(0xff); | ||||
| 					if (x >= 0xff) return 0; | ||||
| 					return 0 - uint256(x); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	auto sign_extension_cpp = []() -> u256 | ||||
| @ -803,11 +807,13 @@ BOOST_AUTO_TEST_CASE(small_unsigned_types) | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract test { | ||||
| 			function run() public returns(uint256 y) { | ||||
| 				unchecked { | ||||
| 					uint32 t = uint32(0xffffff); | ||||
| 					uint32 x = t * 0xffffff; | ||||
| 					return x / 0x100; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	auto small_unsigned_types_cpp = []() -> u256 | ||||
| @ -1804,10 +1810,13 @@ BOOST_AUTO_TEST_CASE(blockhash) | ||||
| 			function g() public returns (bool) { counter++; return true; } | ||||
| 			function f() public returns (bytes32[] memory r) { | ||||
| 				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); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	// generate a sufficient amount of blocks
 | ||||
| @ -6881,7 +6890,7 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser) | ||||
| 				} | ||||
| 				uint x = 0x0000000000001234123412431234123412412342112341234124312341234124; | ||||
| 				// 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 { | ||||
| 					// make scratch space dirty
 | ||||
| 					mstore(0, 0x4242424242424242424242424242424242424242424242424242424242424242) | ||||
| @ -6892,10 +6901,10 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser) | ||||
| 				return 0x0000000000001234123412431234123412412342112341234124312341234124; | ||||
| 			} | ||||
| 			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) { | ||||
| 				return a * 0x0000000000001234123412431234123412412342112341234124312341234124; | ||||
| 				unchecked { return a * 0x0000000000001234123412431234123412412342112341234124312341234124; } | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
|  | ||||
| @ -139,6 +139,7 @@ bytes compileFirstExpression( | ||||
| 			); | ||||
| 			context.resetVisitedNodes(contract); | ||||
| 			context.setMostDerivedContract(*contract); | ||||
| 			context.setArithmetic(Arithmetic::Wrapping); | ||||
| 			size_t parametersSize = _localVariables.size(); // assume they are all one slot on the stack
 | ||||
| 			context.adjustStackOffset(static_cast<int>(parametersSize)); | ||||
| 			for (vector<string> const& variable: _localVariables) | ||||
| @ -321,7 +322,7 @@ BOOST_AUTO_TEST_CASE(arithmetic) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		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"}}); | ||||
| @ -402,7 +403,7 @@ BOOST_AUTO_TEST_CASE(unary_operators) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract test { | ||||
| 			function f(int y) { !(~- y == 2); } | ||||
| 			function f(int y) { unchecked { !(~- y == 2); } } | ||||
| 		} | ||||
| 	)"; | ||||
| 	bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); | ||||
| @ -435,7 +436,7 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		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"}}); | ||||
| @ -492,7 +493,7 @@ BOOST_AUTO_TEST_CASE(assignment) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		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"}}); | ||||
|  | ||||
| @ -224,8 +224,8 @@ BOOST_AUTO_TEST_CASE(function_calls) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract test { | ||||
| 			function f1(uint x) public returns (uint) { return x*x; } | ||||
| 			function f(uint x) public returns (uint) { return f1(7+x) - this.f1(x**9); } | ||||
| 			function f1(uint x) public returns (uint) { unchecked { return x*x; } } | ||||
| 			function f(uint x) public returns (uint) { unchecked { return f1(7+x) - this.f1(x**9); } } | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileBothVersions(sourceCode); | ||||
|  | ||||
| @ -120,8 +120,9 @@ BOOST_AUTO_TEST_CASE(reserved_keywords) | ||||
| { | ||||
| 	BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Identifier)); | ||||
| 	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::Reference)); | ||||
| 	BOOST_CHECK(!TokenTraits::isReservedKeyword(Token::Illegal)); | ||||
| } | ||||
| 
 | ||||
| @ -515,7 +516,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) | ||||
| 		"switch", | ||||
| 		"typedef", | ||||
| 		"typeof", | ||||
| 		"unchecked", | ||||
| 		"var" | ||||
| 	}; | ||||
| 
 | ||||
|  | ||||
| @ -2,16 +2,16 @@ pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| contract C { | ||||
| 	function exp_neg_one(uint exponent) public returns(int) { | ||||
| 		return (-1)**exponent; | ||||
| 		unchecked { return (-1)**exponent; } | ||||
| 	} | ||||
| 	function exp_two(uint exponent) public returns(uint) { | ||||
| 		return 2**exponent; | ||||
| 		unchecked { return 2**exponent; } | ||||
| 	} | ||||
| 	function exp_zero(uint exponent) public returns(uint) { | ||||
| 		return 0**exponent; | ||||
| 		unchecked { return 0**exponent; } | ||||
| 	} | ||||
| 	function exp_one(uint exponent) public returns(uint) { | ||||
| 		return 1**exponent; | ||||
| 		unchecked { return 1**exponent; } | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
|  | ||||
| @ -2,16 +2,16 @@ pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| contract C { | ||||
| 	function exp_neg_one(uint exponent) public returns(int) { | ||||
| 		return (-1)**exponent; | ||||
| 		unchecked { return (-1)**exponent; } | ||||
| 	} | ||||
| 	function exp_two(uint exponent) public returns(uint) { | ||||
| 		return 2**exponent; | ||||
| 		unchecked { return 2**exponent; } | ||||
| 	} | ||||
| 	function exp_zero(uint exponent) public returns(uint) { | ||||
| 		return 0**exponent; | ||||
| 		unchecked { return 0**exponent; } | ||||
| 	} | ||||
| 	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) { | ||||
|         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): -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,8 +1,10 @@ | ||||
| contract C { | ||||
|     function f() public pure returns (uint x) { | ||||
|         unchecked { | ||||
|             uint8 y = uint8(2)**uint8(8); | ||||
|             return 0**y; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ---- | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| contract C { | ||||
|     function f() public pure returns (uint8 x) { | ||||
|         unchecked { | ||||
|             return uint8(0)**uint8(uint8(2)**uint8(8)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ---- | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| contract C { | ||||
|     function f() public pure returns (uint8 x) { | ||||
|         unchecked { | ||||
|             return uint8(0x166)**uint8(uint8(2)**uint8(8)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ---- | ||||
|  | ||||
| @ -4,8 +4,10 @@ contract C { | ||||
|         // right before the exp | ||||
|         uint16 e = 0x100; | ||||
|         uint8 b = 0x2; | ||||
|         unchecked { | ||||
|             return b**e; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // f() -> 0x00 | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| contract test { | ||||
|     function f(uint x) public pure returns (uint, int) { | ||||
|         unchecked { | ||||
|             uint a = 2 ** x; | ||||
|             int b = -2 ** x; | ||||
|             return (a, b); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // f(uint256): 0 -> 1, 1 | ||||
|  | ||||
| @ -1,12 +1,13 @@ | ||||
| contract test { | ||||
|     function f() public pure returns (uint) { | ||||
|     function f() public pure returns (uint r) { | ||||
|         uint32 x; | ||||
|         uint8 y; | ||||
|         assembly { | ||||
|             x := 0xfffffffffe | ||||
|             y := 0x102 | ||||
|         } | ||||
|         return x**y; | ||||
|         unchecked { r = x**y; } | ||||
|         return r; | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| ==== Source: s1.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 ==== | ||||
| import {f as h} from "s1.sol"; | ||||
| function f() pure returns (uint) { return 2; } | ||||
| @ -10,5 +10,7 @@ contract C { | ||||
|     return h() - f() - g(); | ||||
|   } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // foo() -> -4 | ||||
| // foo() -> 992 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| ==== Source: s1.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 ==== | ||||
| import {f as h} from "s1.sol"; | ||||
| function f() pure returns (uint) { return 2; } | ||||
| @ -12,5 +12,7 @@ contract C { | ||||
|     return f() - g() - h(); | ||||
|   } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // foo() -> -4 | ||||
| // foo() -> 0x60 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| ==== Source: s1.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 ==== | ||||
| import {f as h} from "s1.sol"; | ||||
| function f() pure returns (uint) { return 2; } | ||||
| @ -9,8 +9,10 @@ function g() pure returns (uint) { return 4; } | ||||
| import "s2.sol"; | ||||
| contract C { | ||||
|   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 { | ||||
|     function f() public returns (uint16 x) { | ||||
|         unchecked { | ||||
|             x = 0xffff; | ||||
|             x += 32; | ||||
|             x <<= 8; | ||||
|             x >>= 16; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0x0 | ||||
|  | ||||
| @ -13,7 +13,7 @@ contract C { | ||||
|         return (2, 3); | ||||
|     } | ||||
|     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) { | ||||
|         return !b; | ||||
| @ -28,6 +28,8 @@ contract C { | ||||
|         return a * 7; | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // d() -> | ||||
| // e(), 1 wei -> 1 | ||||
|  | ||||
| @ -4,10 +4,10 @@ contract C { | ||||
|     uint16 b; | ||||
| 
 | ||||
|     function f() public returns (uint256, uint256, uint256, uint256) { | ||||
|         a++; | ||||
|         unchecked { a++; } | ||||
|         uint256 c = b; | ||||
|         delete b; | ||||
|         a -= 2; | ||||
|         unchecked { a -= 2; } | ||||
|         return (x, c, b, a); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,8 +9,10 @@ contract C { | ||||
|         returns (uint256 x1, uint256 x2, uint256 x3, uint256 x4) | ||||
|     { | ||||
|         a = -2; | ||||
|         unchecked { | ||||
|             b = (0 - uint8(a)) * 2; | ||||
|             c = a * int8(120) * int8(121); | ||||
|         } | ||||
|         x1 = uint256(a); | ||||
|         x2 = b; | ||||
|         x3 = uint256(c); | ||||
| @ -18,5 +20,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // test() -> -2, 4, -112, 0 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| contract test { | ||||
|     function f() public returns (bool) { | ||||
|         int256 x = -2**255; | ||||
|         assert(-x == x); | ||||
|         unchecked { assert(-x == x); } | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(uint256,uint256): 5, 6 -> 11 | ||||
| // 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): -2, 1 -> -1 | ||||
|  | ||||
| @ -10,7 +10,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(uint256,uint256): 10, 3 -> 3 | ||||
| // 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, 2 -> 0 | ||||
|  | ||||
| @ -7,7 +7,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(int256,int256): 10, 3 -> 1 | ||||
| // 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): -1, 1 -> -1 | ||||
|  | ||||
| @ -7,51 +7,42 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(int256,int256): 5, 6 -> 30 | ||||
| // f(int256,int256): -1, 1 -> -1 | ||||
| // f(int256,int256): -1, 2 -> -2 | ||||
| // # positive, positive # | ||||
| // f(int256,int256): -1, 2 -> -2 # positive, positive # | ||||
| // f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE | ||||
| // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE | ||||
| // f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE | ||||
| // f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE | ||||
| // # positive, negative # | ||||
| // f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE # positive, negative # | ||||
| // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, -2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 | ||||
| // f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000001, -2 -> FAILURE | ||||
| // f(int256,int256): 2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 | ||||
| // f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE | ||||
| // # negative, positive # | ||||
| // f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE # negative, positive # | ||||
| // f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 | ||||
| // f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000001 -> FAILURE | ||||
| // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, 2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 | ||||
| // f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE | ||||
| // # negative, negative # | ||||
| // f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE # negative, negative # | ||||
| // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000001, -2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE | ||||
| // f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, -2 -> FAILURE | ||||
| // f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000001 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE | ||||
| // f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE | ||||
| // # small type # | ||||
| // f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE # small type # | ||||
| // g(int8,int8): 5, 6 -> 30 | ||||
| // g(int8,int8): -1, 1 -> -1 | ||||
| // g(int8,int8): -1, 2 -> -2 | ||||
| // # positive, positive # | ||||
| // g(int8,int8): -1, 2 -> -2 # positive, positive # | ||||
| // g(int8,int8): 63, 2 -> 126 | ||||
| // g(int8,int8): 64, 2 -> FAILURE | ||||
| // g(int8,int8): 2, 63 -> 126 | ||||
| // g(int8,int8): 2, 64 -> FAILURE | ||||
| // # positive, negative # | ||||
| // g(int8,int8): 2, 64 -> FAILURE # positive, negative # | ||||
| // g(int8,int8): 64, -2 -> -128 | ||||
| // g(int8,int8): 65, -2 -> FAILURE | ||||
| // g(int8,int8): 2, -64 -> -128 | ||||
| // g(int8,int8): 2, -65 -> FAILURE | ||||
| // # negative, positive # | ||||
| // g(int8,int8): 2, -65 -> FAILURE # negative, positive # | ||||
| // g(int8,int8): -2, 64 -> -128 | ||||
| // g(int8,int8): -2, 65 -> FAILURE | ||||
| // g(int8,int8): -64, 2 -> -128 | ||||
| // g(int8,int8): -65, 2 -> FAILURE | ||||
| // # negative, negative # | ||||
| // g(int8,int8): -65, 2 -> FAILURE # negative, negative # | ||||
| // g(int8,int8): -63, -2 -> 126 | ||||
| // g(int8,int8): -64, -2 -> FAILURE | ||||
| // 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, 6 -> 0 | ||||
|  | ||||
| @ -7,7 +7,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(int256,int256): 5, 6 -> -1 | ||||
| // 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, 7 -> FAILURE | ||||
|  | ||||
| @ -7,7 +7,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: true | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(uint8,uint8): 2, 7 -> 0x80 | ||||
| // 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