diff --git a/Changelog.md b/Changelog.md index 831c121c3..5fe86364c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 9cd670091..b9aab52be 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -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. diff --git a/docs/grammar/Solidity.g4 b/docs/grammar/Solidity.g4 index 8246ba073..81a1bae35 100644 --- a/docs/grammar/Solidity.g4 +++ b/docs/grammar/Solidity.g4 @@ -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 diff --git a/docs/grammar/SolidityLexer.g4 b/docs/grammar/SolidityLexer.g4 index c70a99d91..6bb4f3809 100644 --- a/docs/grammar/SolidityLexer.g4 +++ b/docs/grammar/SolidityLexer.g4 @@ -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. diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index f3da86594..ce768eb75 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -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` to find potential overflows, or use a library like -`SafeMath `_ -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` to find potential overflows. .. _clearing-mappings: diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index e05685c9c..996d88a63 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -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`. + 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`. You can switch to "unchecked" mode + using ``unchecked { ... }``. More details can be found in the section about :ref:`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`, 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` 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``. diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 183d23887..2eb97054e 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -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. */ \ diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 076592053..0fc1fa4ab 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -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; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 52c82ff6e..46f6c486e 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -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 m_currentContractKind; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 505028a9f..a4d1f1654 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1384,18 +1384,24 @@ public: int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, + bool _unchecked, std::vector> _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> const& statements() const { return m_statements; } + bool unchecked() const { return m_unchecked; } BlockAnnotation& annotation() const override; private: std::vector> m_statements; + bool m_unchecked; }; /** diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index 09dff7337..87f9817c3 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -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) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index c1137f2b9..0d24726db 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -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; diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 1dec4b4cb..a3d88b435 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -154,7 +154,9 @@ ASTPointer 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 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 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 ASTJsonImporter::createInlineAssembly(Json::Value con ); } -ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) +ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node, bool _unchecked) { std::vector> statements; for (auto& stat: member(_node, "statements")) @@ -597,6 +599,7 @@ ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) return createASTNode( _node, nullOrASTString(_node, "documentation"), + _unchecked, statements ); } diff --git a/libsolidity/ast/ASTJsonImporter.h b/libsolidity/ast/ASTJsonImporter.h index 824249e59..c64790d4d 100644 --- a/libsolidity/ast/ASTJsonImporter.h +++ b/libsolidity/ast/ASTJsonImporter.h @@ -93,7 +93,7 @@ private: ASTPointer createMapping(Json::Value const& _node); ASTPointer createArrayTypeName(Json::Value const& _node); ASTPointer createInlineAssembly(Json::Value const& _node); - ASTPointer createBlock(Json::Value const& _node); + ASTPointer createBlock(Json::Value const& _node, bool _unchecked); ASTPointer createPlaceholderStatement(Json::Value const& _node); ASTPointer createIfStatement(Json::Value const& _node); ASTPointer createTryCatchClause(Json::Value const& _node); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index bd37c3686..b552bf6b1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -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> 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 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. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 35780fdc2..eb3081e13 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -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 experimentalFeaturesOutside = m_context.experimentalFeaturesActive(); m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9dff56afc..7303f1429 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -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 - m_context << Instruction::SWAP1 << Instruction::SUB; + { + if (m_context.arithmetic() == Arithmetic::Checked) + m_context.callYulFunction(m_context.utilFunctions().decrementCheckedFunction(type), 1, 1); + else + { + m_context << u256(1); + m_context << Instruction::SWAP1 << Instruction::SUB; + } + } // Stack for prefix: [ref...] (*ref)+-1 // Stack for postfix: *ref [ref...] (*ref)+-1 for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) @@ -437,7 +453,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) // unary add, so basically no-op break; case Token::Sub: // - - m_context << u256(0) << Instruction::SUB; + if (m_context.arithmetic() == Arithmetic::Checked) + m_context.callYulFunction(m_context.utilFunctions().negateNumberCheckedFunction(type), 1, 1); + else + m_context << u256(0) << Instruction::SUB; break; default: solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator()))); @@ -460,7 +479,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) m_context << commonType->literalValue(nullptr); else { - bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); + bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op, m_context.arithmetic()); TypePointer leftTargetType = commonType; TypePointer rightTargetType = @@ -2112,34 +2131,65 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons solUnimplemented("Not yet implemented - FixedPointType."); IntegerType const& type = dynamic_cast(_type); - bool const c_isSigned = type.isSigned(); - - switch (_operator) + if (m_context.arithmetic() == Arithmetic::Checked) { - case Token::Add: - m_context << Instruction::ADD; - break; - case Token::Sub: - m_context << Instruction::SUB; - break; - case Token::Mul: - m_context << Instruction::MUL; - break; - case Token::Div: - case Token::Mod: - { - // Test for division by zero - m_context << Instruction::DUP2 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); - - if (_operator == Token::Div) - m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); - else - m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); - break; + 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); } - default: - solAssert(false, "Unknown arithmetic operator."); + else + { + bool const c_isSigned = type.isSigned(); + + switch (_operator) + { + case Token::Add: + m_context << Instruction::ADD; + break; + case Token::Sub: + m_context << Instruction::SUB; + break; + case Token::Mul: + m_context << Instruction::MUL; + break; + case Token::Div: + case Token::Mod: + { + // Test for division by zero + m_context << Instruction::DUP2 << Instruction::ISZERO; + m_context.appendConditionalInvalid(); + + if (_operator == Token::Div) + m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); + else + m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); + break; + } + default: + solAssert(false, "Unknown arithmetic operator."); + } } } @@ -2237,7 +2287,14 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons solAssert(_valueType.category() == Type::Category::Integer, ""); solAssert(!dynamic_cast(_exponentType).isSigned(), ""); - m_context << Instruction::EXP; + + if (m_context.arithmetic() == Arithmetic::Checked) + m_context.callYulFunction(m_context.utilFunctions().overflowCheckedIntExpFunction( + dynamic_cast(_valueType), + dynamic_cast(_exponentType) + ), 2, 1); + else + m_context << Instruction::EXP; } void ExpressionCompiler::appendExternalFunctionCall( @@ -2561,11 +2618,15 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue(_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; diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 7b574b59c..66399d070 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -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); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 14c907f49..95f11da7d 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -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 (x, y) -> sum { + sum := (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 (x, y) -> product { + product := (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 (x, y) -> r { + x := (x) + y := (y) + if iszero(y) { () } + r := sdiv(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 (x, y) -> diff { + diff := (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 (base, exponent) -> power { + base := (base) + exponent := (exponent) + power := (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 (value) -> ret { value := (value) - if (value, ) { () } + if eq(value, ) { () } 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(_type); + + string const functionName = "decrement_wrapping_" + _type.identifier(); + + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (value) -> ret { + ret := (sub(value, 1)) + } + )") + ("functionName", functionName) + ("cleanupFunction", cleanupFunction(type)) + .render(); + }); +} + std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) { IntegerType const& type = dynamic_cast(_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 (value) -> ret { value := (value) - if (value, ) { () } + if eq(value, ) { () } 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(_type); + + string const functionName = "increment_wrapping_" + _type.identifier(); + + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (value) -> ret { + ret := (add(value, 1)) + } + )") + ("functionName", functionName) + ("cleanupFunction", cleanupFunction(type)) + .render(); + }); +} + string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) { IntegerType const& type = dynamic_cast(_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 (value) -> ret { value := (value) - if slt(value, ) { () } + if eq(value, ) { () } 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(_type); + solAssert(type.isSigned(), "Expected signed type!"); + + string const functionName = "negate_" + _type.identifier(); + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function (value) -> ret { + value := (sub(0, value))) + } + )") + ("functionName", functionName) + ("cleanupFunction", cleanupFunction(type)) + .render(); + }); +} + string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes) { solAssert(_type.category() != Type::Category::Mapping, ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 7b6d66d96..731a7f939 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -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. diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 8f102dfda..d044a6d4a 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -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> 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; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index c26012e17..6102b8603 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -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(&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(&_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"; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 3d87eae57..2f17436c6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -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; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index a870d9064..79eeb015a 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1096,16 +1096,23 @@ ASTPointer Parser::parseParameterList( return nodeFactory.createNode(parameters); } -ASTPointer Parser::parseBlock(ASTPointer const& _docString) +ASTPointer Parser::parseBlock(bool _allowUnchecked, ASTPointer 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> 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 Parser::parseBlock(ASTPointer const& _docString) expectTokenOrConsumeUntil(Token::RBrace, "Block"); else expectToken(Token::RBrace); - return nodeFactory.createNode(_docString, statements); + return nodeFactory.createNode(_docString, unchecked, statements); } -ASTPointer Parser::parseStatement() +ASTPointer Parser::parseStatement(bool _allowUnchecked) { RecursionGuard recursionGuard(*this); ASTPointer docString; @@ -1144,9 +1151,9 @@ ASTPointer 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(docString); m_scanner->next(); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 08426287c..c5c85d76c 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -115,8 +115,8 @@ private: VarDeclParserOptions const& _options = {}, bool _allowEmpty = true ); - ASTPointer parseBlock(ASTPointer const& _docString = {}); - ASTPointer parseStatement(); + ASTPointer parseBlock(bool _allowUncheckedBlock = false, ASTPointer const& _docString = {}); + ASTPointer parseStatement(bool _allowUncheckedBlock = false); ASTPointer parseInlineAssembly(ASTPointer const& _docString = {}); ASTPointer parseIfStatement(ASTPointer const& _docString); ASTPointer parseTryStatement(ASTPointer const& _docString); diff --git a/test/cmdlineTests/optimizer_array_sload/output b/test/cmdlineTests/optimizer_array_sload/output index 332df6778..aaa162966 100644 --- a/test/cmdlineTests/optimizer_array_sload/output +++ b/test/cmdlineTests/optimizer_array_sload/output @@ -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) } { diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 4071f9c98..5be5bfb23 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -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,29 +289,31 @@ BOOST_AUTO_TEST_CASE(complex_control_flow) char const* sourceCode = R"( contract log { function ln(int128 x) public pure returns (int128 result) { - int128 t = x / 256; - int128 y = 5545177; - x = t; - t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; } - t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; } - t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; } - t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; } - t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; } - t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; } - t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; } - t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; } - t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; } - t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; } - t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; } - t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; } - t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; } - t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; } - t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; } - t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; } - t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; } - t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; } - t = x + x / 65536; if (t <= 1000000) { y = y - 15; } - return y; + unchecked { + int128 t = x / 256; + int128 y = 5545177; + x = t; + t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; } + t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; } + t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; } + t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; } + t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; } + t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; } + t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; } + t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; } + t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; } + t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; } + t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; } + t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; } + t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; } + t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; } + t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; } + t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; } + t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; } + t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; } + t = x + x / 65536; if (t <= 1000000) { y = y - 15; } + return y; + } } } )"; diff --git a/test/libsolidity/SolidityCompiler.cpp b/test/libsolidity/SolidityCompiler.cpp index 13f714e5e..045ac167a 100644 --- a/test/libsolidity/SolidityCompiler.cpp +++ b/test/libsolidity/SolidityCompiler.cpp @@ -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); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a14f84680..a2a3d58ff 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -757,10 +757,12 @@ BOOST_AUTO_TEST_CASE(high_bits_cleaning) char const* sourceCode = R"( contract test { function run() public returns(uint256 y) { - uint32 t = uint32(0xffffffff); - uint32 x = t + 10; - if (x >= 0xffffffff) return 0; - return x; + unchecked { + uint32 t = uint32(0xffffffff); + uint32 x = t + 10; + if (x >= 0xffffffff) return 0; + return x; + } } } )"; @@ -781,9 +783,11 @@ BOOST_AUTO_TEST_CASE(sign_extension) char const* sourceCode = R"( contract test { function run() public returns(uint256 y) { - int64 x = -int32(0xff); - if (x >= 0xff) return 0; - return 0 - uint256(x); + unchecked { + int64 x = -int32(0xff); + if (x >= 0xff) return 0; + return 0 - uint256(x); + } } } )"; @@ -803,9 +807,11 @@ BOOST_AUTO_TEST_CASE(small_unsigned_types) char const* sourceCode = R"( contract test { function run() public returns(uint256 y) { - uint32 t = uint32(0xffffff); - uint32 x = t * 0xffffff; - return x / 0x100; + unchecked { + uint32 t = uint32(0xffffff); + uint32 x = t * 0xffffff; + return x / 0x100; + } } } )"; @@ -1804,8 +1810,11 @@ BOOST_AUTO_TEST_CASE(blockhash) function g() public returns (bool) { counter++; return true; } function f() public returns (bytes32[] memory r) { r = new bytes32[](259); - for (uint i = 0; i < 259; i++) - r[i] = blockhash(block.number - 257 + i); + for (uint i = 0; i < 259; i++) { + unchecked { + r[i] = blockhash(block.number - 257 + i); + } + } } } )"; @@ -6881,7 +6890,7 @@ BOOST_AUTO_TEST_CASE(dirty_scratch_space_prior_to_constant_optimiser) } uint x = 0x0000000000001234123412431234123412412342112341234124312341234124; // 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; } } } )"; diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 8c0ce780d..3778b1489 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -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(parametersSize)); for (vector 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"}}); diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 89960186d..5fd800ab3 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -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); diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index c5f483355..57adb3186 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -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" }; diff --git a/test/libsolidity/gasTests/exp.sol b/test/libsolidity/gasTests/exp.sol index 238af8990..2539a2fd4 100644 --- a/test/libsolidity/gasTests/exp.sol +++ b/test/libsolidity/gasTests/exp.sol @@ -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; } } } // ==== diff --git a/test/libsolidity/gasTests/exp_optimized.sol b/test/libsolidity/gasTests/exp_optimized.sol index 499a3b5a5..b1cfaed8a 100644 --- a/test/libsolidity/gasTests/exp_optimized.sol +++ b/test/libsolidity/gasTests/exp_optimized.sol @@ -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; } } } // ==== diff --git a/test/libsolidity/semanticTests/arithmetics/block_inside_unchecked.sol b/test/libsolidity/semanticTests/arithmetics/block_inside_unchecked.sol new file mode 100644 index 000000000..59aa6ca56 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/block_inside_unchecked.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/check_var_init.sol b/test/libsolidity/semanticTests/arithmetics/check_var_init.sol new file mode 100644 index 000000000..243c43bb1 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/check_var_init.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/checked_add.sol b/test/libsolidity/semanticTests/arithmetics/checked_add.sol new file mode 100644 index 000000000..21db3db74 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/checked_add.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/checked_called_by_unchecked.sol b/test/libsolidity/semanticTests/arithmetics/checked_called_by_unchecked.sol new file mode 100644 index 000000000..c12e42550 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/checked_called_by_unchecked.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/checked_modifier_called_by_unchecked.sol b/test/libsolidity/semanticTests/arithmetics/checked_modifier_called_by_unchecked.sol new file mode 100644 index 000000000..fedea12c7 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/checked_modifier_called_by_unchecked.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/signed_mod.sol b/test/libsolidity/semanticTests/arithmetics/signed_mod.sol index a992bd0be..36487f91b 100644 --- a/test/libsolidity/semanticTests/arithmetics/signed_mod.sol +++ b/test/libsolidity/semanticTests/arithmetics/signed_mod.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/unchecked_called_by_checked.sol b/test/libsolidity/semanticTests/arithmetics/unchecked_called_by_checked.sol new file mode 100644 index 000000000..504bc23b9 --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/unchecked_called_by_checked.sol @@ -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 diff --git a/test/libsolidity/semanticTests/arithmetics/unchecked_div_by_zero.sol b/test/libsolidity/semanticTests/arithmetics/unchecked_div_by_zero.sol new file mode 100644 index 000000000..9bf68490c --- /dev/null +++ b/test/libsolidity/semanticTests/arithmetics/unchecked_div_by_zero.sol @@ -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 # diff --git a/test/libsolidity/semanticTests/cleanup/exp_cleanup.sol b/test/libsolidity/semanticTests/cleanup/exp_cleanup.sol index e6bd08b8f..103de86cc 100644 --- a/test/libsolidity/semanticTests/cleanup/exp_cleanup.sol +++ b/test/libsolidity/semanticTests/cleanup/exp_cleanup.sol @@ -1,7 +1,9 @@ contract C { function f() public pure returns (uint x) { - uint8 y = uint8(2)**uint8(8); - return 0**y; + unchecked { + uint8 y = uint8(2)**uint8(8); + return 0**y; + } } } diff --git a/test/libsolidity/semanticTests/cleanup/exp_cleanup_direct.sol b/test/libsolidity/semanticTests/cleanup/exp_cleanup_direct.sol index a9b5e81b5..78221939e 100644 --- a/test/libsolidity/semanticTests/cleanup/exp_cleanup_direct.sol +++ b/test/libsolidity/semanticTests/cleanup/exp_cleanup_direct.sol @@ -1,6 +1,8 @@ contract C { function f() public pure returns (uint8 x) { - return uint8(0)**uint8(uint8(2)**uint8(8)); + unchecked { + return uint8(0)**uint8(uint8(2)**uint8(8)); + } } } diff --git a/test/libsolidity/semanticTests/cleanup/exp_cleanup_nonzero_base.sol b/test/libsolidity/semanticTests/cleanup/exp_cleanup_nonzero_base.sol index 4f817d186..7379440d3 100644 --- a/test/libsolidity/semanticTests/cleanup/exp_cleanup_nonzero_base.sol +++ b/test/libsolidity/semanticTests/cleanup/exp_cleanup_nonzero_base.sol @@ -1,6 +1,8 @@ contract C { function f() public pure returns (uint8 x) { - return uint8(0x166)**uint8(uint8(2)**uint8(8)); + unchecked { + return uint8(0x166)**uint8(uint8(2)**uint8(8)); + } } } diff --git a/test/libsolidity/semanticTests/cleanup/exp_cleanup_smaller_base.sol b/test/libsolidity/semanticTests/cleanup/exp_cleanup_smaller_base.sol index 3ef61f219..7b81d0a1c 100644 --- a/test/libsolidity/semanticTests/cleanup/exp_cleanup_smaller_base.sol +++ b/test/libsolidity/semanticTests/cleanup/exp_cleanup_smaller_base.sol @@ -4,7 +4,9 @@ contract C { // right before the exp uint16 e = 0x100; uint8 b = 0x2; - return b**e; + unchecked { + return b**e; + } } } // ---- diff --git a/test/libsolidity/semanticTests/exponentiation/literal_base.sol b/test/libsolidity/semanticTests/exponentiation/literal_base.sol index 2c11519d0..3dcba494a 100644 --- a/test/libsolidity/semanticTests/exponentiation/literal_base.sol +++ b/test/libsolidity/semanticTests/exponentiation/literal_base.sol @@ -1,8 +1,10 @@ contract test { function f(uint x) public pure returns (uint, int) { - uint a = 2 ** x; - int b = -2 ** x; - return (a, b); + unchecked { + uint a = 2 ** x; + int b = -2 ** x; + return (a, b); + } } } // ---- diff --git a/test/libsolidity/semanticTests/exponentiation/small_exp.sol b/test/libsolidity/semanticTests/exponentiation/small_exp.sol index 9dfb5599b..a26675fb7 100644 --- a/test/libsolidity/semanticTests/exponentiation/small_exp.sol +++ b/test/libsolidity/semanticTests/exponentiation/small_exp.sol @@ -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; } } // ---- diff --git a/test/libsolidity/semanticTests/multiSource/circular_import_2.sol b/test/libsolidity/semanticTests/multiSource/circular_import_2.sol index 2c458aa93..920890b37 100644 --- a/test/libsolidity/semanticTests/multiSource/circular_import_2.sol +++ b/test/libsolidity/semanticTests/multiSource/circular_import_2.sol @@ -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 diff --git a/test/libsolidity/semanticTests/multiSource/circular_reimport.sol b/test/libsolidity/semanticTests/multiSource/circular_reimport.sol index 05c0eeb57..4514b24b0 100644 --- a/test/libsolidity/semanticTests/multiSource/circular_reimport.sol +++ b/test/libsolidity/semanticTests/multiSource/circular_reimport.sol @@ -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 diff --git a/test/libsolidity/semanticTests/multiSource/circular_reimport_2.sol b/test/libsolidity/semanticTests/multiSource/circular_reimport_2.sol index 14f5123f3..07faf9356 100644 --- a/test/libsolidity/semanticTests/multiSource/circular_reimport_2.sol +++ b/test/libsolidity/semanticTests/multiSource/circular_reimport_2.sol @@ -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 diff --git a/test/libsolidity/semanticTests/shifts/shift_cleanup.sol b/test/libsolidity/semanticTests/shifts/shift_cleanup.sol index 81296ba95..722fdae18 100644 --- a/test/libsolidity/semanticTests/shifts/shift_cleanup.sol +++ b/test/libsolidity/semanticTests/shifts/shift_cleanup.sol @@ -1,11 +1,15 @@ contract C { function f() public returns (uint16 x) { - x = 0xffff; - x += 32; - x <<= 8; - x >>= 16; + unchecked { + x = 0xffff; + x += 32; + x <<= 8; + x >>= 16; + } } } +// ==== +// compileViaYul: also // ---- // f() -> 0x0 diff --git a/test/libsolidity/semanticTests/smoke/basic.sol b/test/libsolidity/semanticTests/smoke/basic.sol index 892ee8702..de252d89c 100644 --- a/test/libsolidity/semanticTests/smoke/basic.sol +++ b/test/libsolidity/semanticTests/smoke/basic.sol @@ -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 diff --git a/test/libsolidity/semanticTests/storage/packed_storage_overflow.sol b/test/libsolidity/semanticTests/storage/packed_storage_overflow.sol index 9b20dbfd2..4a84544ca 100644 --- a/test/libsolidity/semanticTests/storage/packed_storage_overflow.sol +++ b/test/libsolidity/semanticTests/storage/packed_storage_overflow.sol @@ -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); } } diff --git a/test/libsolidity/semanticTests/storage/packed_storage_signed.sol b/test/libsolidity/semanticTests/storage/packed_storage_signed.sol index 99501f73e..08ea7adae 100644 --- a/test/libsolidity/semanticTests/storage/packed_storage_signed.sol +++ b/test/libsolidity/semanticTests/storage/packed_storage_signed.sol @@ -9,8 +9,10 @@ contract C { returns (uint256 x1, uint256 x2, uint256 x3, uint256 x4) { a = -2; - b = (0 - uint8(a)) * 2; - c = a * int8(120) * int8(121); + 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 diff --git a/test/libsolidity/semanticTests/various/flipping_sign_tests.sol b/test/libsolidity/semanticTests/various/flipping_sign_tests.sol index 8309615a5..922494be7 100644 --- a/test/libsolidity/semanticTests/various/flipping_sign_tests.sol +++ b/test/libsolidity/semanticTests/various/flipping_sign_tests.sol @@ -1,7 +1,7 @@ contract test { function f() public returns (bool) { int256 x = -2**255; - assert(-x == x); + unchecked { assert(-x == x); } return true; } } diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol index f16e4dac5..7ea2f0306 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint256,uint256): 5, 6 -> 11 // f(uint256,uint256): -2, 1 -> -1 diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol index 6e44c44ba..a2a90bd2a 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(int256,int256): 5, 6 -> 11 // f(int256,int256): -2, 1 -> -1 diff --git a/test/libsolidity/semanticTests/viaYul/detect_div_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_div_overflow.sol index 1de251b61..babda4683 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_div_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_div_overflow.sol @@ -10,7 +10,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint256,uint256): 10, 3 -> 3 // f(uint256,uint256): 1, 0 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol index 6340c71c3..5933f0462 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint256,uint256): 10, 3 -> 1 // f(uint256,uint256): 10, 2 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol index d93e18d06..b0dd8ea79 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(int256,int256): 10, 3 -> 1 // f(int256,int256): 10, 2 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol index c49758686..0c0d002ae 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint256,uint256): 5, 6 -> 30 // f(uint256,uint256): -1, 1 -> -1 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol index 930a24ed7..f490fe2b8 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol @@ -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 diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol index 5342052ff..d15fd0693 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint256,uint256): 6, 5 -> 1 // f(uint256,uint256): 6, 6 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol index 1a87ef9ec..15899939e 100644 --- a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(int256,int256): 5, 6 -> -1 // f(int256,int256): -2, 1 -> -3 diff --git a/test/libsolidity/semanticTests/viaYul/erc20.sol b/test/libsolidity/semanticTests/viaYul/erc20.sol index 7f16b8c28..b792fa56b 100644 --- a/test/libsolidity/semanticTests/viaYul/erc20.sol +++ b/test/libsolidity/semanticTests/viaYul/erc20.sol @@ -101,4 +101,4 @@ contract ERC20 { // decreaseAllowance(address,uint256): 2, 0 -> true // decreaseAllowance(address,uint256): 2, 1 -> FAILURE // transfer(address,uint256): 2, 14 -> true -// transfer(address,uint256): 2, 2 -> FAILURE \ No newline at end of file +// transfer(address,uint256): 2, 2 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/exp_neg_overflow.sol b/test/libsolidity/semanticTests/viaYul/exp_neg_overflow.sol index a55cbebaa..c3a5064d4 100644 --- a/test/libsolidity/semanticTests/viaYul/exp_neg_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/exp_neg_overflow.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(int8,uint256): 2, 6 -> 64 // f(int8,uint256): 2, 7 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/exp_overflow.sol b/test/libsolidity/semanticTests/viaYul/exp_overflow.sol index c23b61a86..18074226a 100644 --- a/test/libsolidity/semanticTests/viaYul/exp_overflow.sol +++ b/test/libsolidity/semanticTests/viaYul/exp_overflow.sol @@ -7,7 +7,7 @@ contract C { } } // ==== -// compileViaYul: true +// compileViaYul: also // ---- // f(uint8,uint8): 2, 7 -> 0x80 // f(uint8,uint8): 2, 8 -> FAILURE diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_for_header.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_for_header.sol new file mode 100644 index 000000000..a5b4ecbdc --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_for_header.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_function_body.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_function_body.sol new file mode 100644 index 000000000..ef45a8e2b --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_function_body.sol @@ -0,0 +1,3 @@ +function f() pure returns (uint) unchecked {} +// ---- +// ParserError 5296: (33-42): "unchecked" blocks can only be used inside regular blocks. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_modifier.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_modifier.sol new file mode 100644 index 000000000..2d4242369 --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_modifier.sol @@ -0,0 +1,5 @@ +contract C { + modifier m() { unchecked { _; } } +} +// ---- +// SyntaxError 2573: (44-45): The placeholder statement "_" cannot be used inside an "unchecked" block. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_nested.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_nested.sol new file mode 100644 index 000000000..6c64ce522 --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_nested.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_post_for.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_post_for.sol new file mode 100644 index 000000000..2b77920dc --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_post_for.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_vardecl.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_vardecl.sol new file mode 100644 index 000000000..b49112f5e --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_vardecl.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/unchecked/unchecked_while_body.sol b/test/libsolidity/syntaxTests/unchecked/unchecked_while_body.sol new file mode 100644 index 000000000..4b7c6f2ab --- /dev/null +++ b/test/libsolidity/syntaxTests/unchecked/unchecked_while_body.sol @@ -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.