Merge pull request #9465 from ethereum/unchecked

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

View File

@ -2,6 +2,7 @@
Breaking Changes:
* 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:

View File

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

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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``.

View File

@ -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. */ \

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};
/**

View File

@ -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)

View File

@ -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;

View File

@ -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
);
}

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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<IntegerType const&>(_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<IntegerType const&>(_exponentType).isSigned(), "");
m_context << Instruction::EXP;
if (m_context.arithmetic() == Arithmetic::Checked)
m_context.callYulFunction(m_context.utilFunctions().overflowCheckedIntExpFunction(
dynamic_cast<IntegerType const&>(_valueType),
dynamic_cast<IntegerType const&>(_exponentType)
), 2, 1);
else
m_context << Instruction::EXP;
}
void ExpressionCompiler::appendExternalFunctionCall(
@ -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;

View File

@ -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);

View File

@ -483,6 +483,22 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
});
}
string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type)
{
string functionName = "wrapping_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := <cleanupFunction>(add(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{
string 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, "");

View File

@ -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.

View File

@ -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;

View File

@ -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";

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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)
}
{

View File

@ -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;
}
}
}
)";

View File

@ -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);

View File

@ -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; }
}
}
)";

View File

@ -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"}});

View File

@ -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);

View File

@ -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"
};

View File

@ -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; }
}
}
// ====

View File

@ -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; }
}
}
// ====

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,14 @@ contract C {
function f(int a, int b) public pure returns (int) {
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

View File

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

View File

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

View File

@ -1,7 +1,9 @@
contract C {
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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

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

View File

@ -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);
}
}
}
// ----

View File

@ -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;
}
}
// ----

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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
// transfer(address,uint256): 2, 2 -> FAILURE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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