aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog.md2
-rw-r--r--docs/contracts.rst33
-rw-r--r--libsolidity/codegen/CompilerContext.cpp25
-rw-r--r--libsolidity/codegen/CompilerContext.h6
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp9
-rw-r--r--libsolidity/codegen/CompilerUtils.h4
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp103
-rw-r--r--libsolidity/codegen/ContractCompiler.h24
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp16
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp356
10 files changed, 513 insertions, 65 deletions
diff --git a/Changelog.md b/Changelog.md
index 70542342..a7b452ac 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -11,6 +11,7 @@ Breaking Changes:
* ABI Encoder: Properly pad data from calldata (``msg.data`` and external function parameters). Use ``abi.encodePacked`` for unpadded encoding.
* Code Generator: Signed right shift uses proper arithmetic shift, i.e. rounding towards negative infinity. Warning: this may silently change the semantics of existing code!
* Code Generator: Revert at runtime if calldata is too short or points out of bounds. This is done inside the ``ABI decoder`` and therefore also applies to ``abi.decode()``.
+ * Code Generator: Use ``STATICCALL`` for ``pure`` and ``view`` functions. This was already the case in the experimental 0.5.0 mode.
* Commandline interface: Remove obsolete ``--formal`` option.
* Commandline interface: Rename the ``--julia`` option to ``--yul``.
* Commandline interface: Require ``-`` if standard input is used as source.
@@ -57,6 +58,7 @@ Compiler Features:
* C API (``libsolc``): Export the ``solidity_license``, ``solidity_version`` and ``solidity_compile`` methods.
* Type Checker: Show named argument in case of error.
* Tests: Determine transaction status during IPC calls.
+ * Code Generator: Allocate and free local variables according to their scope.
Bugfixes:
* Tests: Fix chain parameters to make ipc tests work with newer versions of cpp-ethereum.
diff --git a/docs/contracts.rst b/docs/contracts.rst
index a745bea8..862ec54d 100644
--- a/docs/contracts.rst
+++ b/docs/contracts.rst
@@ -451,6 +451,10 @@ View Functions
Functions can be declared ``view`` in which case they promise not to modify the state.
+.. note::
+ If the compiler's EVM target is Byzantium or newer (default) the opcode
+ ``STATICCALL`` is used.
+
The following statements are considered modifying the state:
#. Writing to state variables.
@@ -464,7 +468,7 @@ The following statements are considered modifying the state:
::
- pragma solidity ^0.4.16;
+ pragma solidity >0.4.24;
contract C {
function f(uint a, uint b) public view returns (uint) {
@@ -479,11 +483,12 @@ The following statements are considered modifying the state:
Getter methods are marked ``view``.
.. note::
- If invalid explicit type conversions are used, state modifications are possible
- even though a ``view`` function was called.
- You can switch the compiler to use ``STATICCALL`` when calling such functions and thus
- prevent modifications to the state on the level of the EVM by adding
- ``pragma experimental "v0.5.0";``
+ Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode
+ for ``view`` functions.
+ This enabled state modifications in ``view`` functions through the use of
+ invalid explicit type conversions.
+ By using ``STATICCALL`` for ``view`` functions, modifications to the
+ state are prevented on the level of the EVM.
.. index:: ! pure function, function;pure
@@ -494,6 +499,9 @@ Pure Functions
Functions can be declared ``pure`` in which case they promise not to read from or modify the state.
+.. note::
+ If the compiler's EVM target is Byzantium or newer (default) the opcode ``STATICCALL`` is used.
+
In addition to the list of state modifying statements explained above, the following are considered reading from the state:
#. Reading from state variables.
@@ -504,7 +512,7 @@ In addition to the list of state modifying statements explained above, the follo
::
- pragma solidity ^0.4.16;
+ pragma solidity >0.4.24;
contract C {
function f(uint a, uint b) public pure returns (uint) {
@@ -513,11 +521,12 @@ In addition to the list of state modifying statements explained above, the follo
}
.. note::
- If invalid explicit type conversions are used, state modifications are possible
- even though a ``pure`` function was called.
- You can switch the compiler to use ``STATICCALL`` when calling such functions and thus
- prevent modifications to the state on the level of the EVM by adding
- ``pragma experimental "v0.5.0";``
+ Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode
+ for ``pure`` functions.
+ This enabled state modifications in ``pure`` functions through the use of
+ invalid explicit type conversions.
+ By using ``STATICCALL`` for ``pure`` functions, modifications to the
+ state are prevented on the level of the EVM.
.. warning::
It is not possible to prevent functions from reading the state at the level
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index a35eea73..3b1b4ec0 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -127,10 +127,14 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, "");
+ unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack();
+ // Variables should not have stack size other than [1, 2],
+ // but that might change when new types are introduced.
+ solAssert(sizeOnStack == 1 || sizeOnStack == 2, "");
m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent);
}
-void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
+void CompilerContext::removeVariable(Declaration const& _declaration)
{
solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), "");
m_localVariables[&_declaration].pop_back();
@@ -138,6 +142,25 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
m_localVariables.erase(&_declaration);
}
+void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight)
+{
+ vector<Declaration const*> toRemove;
+ for (auto _var: m_localVariables)
+ {
+ solAssert(!_var.second.empty(), "");
+ solAssert(_var.second.back() <= stackHeight(), "");
+ if (_var.second.back() >= _stackHeight)
+ toRemove.push_back(_var.first);
+ }
+ for (auto _var: toRemove)
+ removeVariable(*_var);
+}
+
+unsigned CompilerContext::numberOfLocalVariables() const
+{
+ return m_localVariables.size();
+}
+
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const
{
auto ret = m_compiledContracts.find(&_contract);
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 5776b5d1..3f357821 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -71,7 +71,11 @@ public:
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
- void removeVariable(VariableDeclaration const& _declaration);
+ void removeVariable(Declaration const& _declaration);
+ /// Removes all local variables currently allocated above _stackHeight.
+ void removeVariablesAboveStackHeight(unsigned _stackHeight);
+ /// Returns the number of currently allocated local variables.
+ unsigned numberOfLocalVariables() const;
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 2e335ca5..2d81a106 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -1170,6 +1170,15 @@ void CompilerUtils::popStackSlots(size_t _amount)
m_context << Instruction::POP;
}
+void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo)
+{
+ solAssert(m_context.stackHeight() >= _toHeight, "");
+ unsigned amount = m_context.stackHeight() - _toHeight;
+ popStackSlots(amount);
+ m_context.appendJumpTo(_jumpTo);
+ m_context.adjustStackOffset(amount);
+}
+
unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes)
{
unsigned size = 0;
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index 0ff3ad7c..26df4765 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -241,6 +241,10 @@ public:
void popStackElement(Type const& _type);
/// Removes element from the top of the stack _amount times.
void popStackSlots(size_t _amount);
+ /// Pops slots from the stack such that its height is _toHeight.
+ /// Adds jump to _jumpTo.
+ /// Readjusts the stack offset to the original value.
+ void popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo);
template <class T>
static unsigned sizeOnStack(std::vector<T> const& _variables);
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 81aba21e..93f698bc 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -427,7 +427,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context.startFunction(_function);
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
- // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
+ // reserve additional slots: [retarg0] ... [retargm]
unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters());
if (!_function.isConstructor())
@@ -441,8 +441,6 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- appendStackVariableInitialisation(*localVariable);
if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
@@ -451,12 +449,11 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
solAssert(m_returnTags.empty(), "");
m_breakTags.clear();
m_continueTags.clear();
- m_stackCleanupForReturn = 0;
m_currentFunction = &_function;
m_modifierDepth = -1;
+ m_scopeStackHeight.clear();
appendModifierOrFunctionCode();
-
solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
@@ -467,14 +464,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters());
unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters());
- unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables());
vector<int> stackLayout;
stackLayout.push_back(c_returnValuesSize); // target of return address
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
for (unsigned i = 0; i < c_returnValuesSize; ++i)
stackLayout.push_back(i);
- stackLayout += vector<int>(c_localVariablesSize, -1);
if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION(
@@ -493,18 +488,23 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
- //@todo assert that everything is in place now
+ for (int i = 0; i < int(stackLayout.size()); ++i)
+ if (stackLayout[i] != i)
+ solAssert(false, "Invalid stack layout on cleanup.");
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters())
m_context.removeVariable(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize);
/// The constructor and the fallback function doesn't to jump out.
- if (!_function.isConstructor() && !_function.isFallback())
- m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ if (!_function.isConstructor())
+ {
+ solAssert(m_context.numberOfLocalVariables() == 0, "");
+ if (!_function.isFallback())
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ }
+
return false;
}
@@ -669,14 +669,14 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
- m_breakTags.push_back(loopEnd);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
m_context << loopStart;
if (_whileStatement.isDoWhile())
{
eth::AssemblyItem condition = m_context.newTag();
- m_continueTags.push_back(condition);
+ m_continueTags.push_back({condition, m_context.stackHeight()});
_whileStatement.body().accept(*this);
@@ -687,7 +687,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
}
else
{
- m_continueTags.push_back(loopStart);
+ m_continueTags.push_back({loopStart, m_context.stackHeight()});
compileExpression(_whileStatement.condition());
m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd);
@@ -712,12 +712,14 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
eth::AssemblyItem loopNext = m_context.newTag();
- m_continueTags.push_back(loopNext);
- m_breakTags.push_back(loopEnd);
+
+ storeStackHeight(&_forStatement);
if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
+ m_continueTags.push_back({loopNext, m_context.stackHeight()});
m_context << loopStart;
// if there is no terminating condition in for, default is to always be true
@@ -737,11 +739,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
_forStatement.loopExpression()->accept(*this);
m_context.appendJumpTo(loopStart);
+
m_context << loopEnd;
m_continueTags.pop_back();
m_breakTags.pop_back();
+ // For the case where no break/return is executed:
+ // loop initialization variables have to be freed
+ popScopedVariables(&_forStatement);
+
checker.check();
return false;
}
@@ -750,7 +757,7 @@ bool ContractCompiler::visit(Continue const& _continueStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
solAssert(!m_continueTags.empty(), "");
- m_context.appendJumpTo(m_continueTags.back());
+ CompilerUtils(m_context).popAndJump(m_continueTags.back().second, m_continueTags.back().first);
return false;
}
@@ -758,7 +765,7 @@ bool ContractCompiler::visit(Break const& _breakStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
solAssert(!m_breakTags.empty(), "");
- m_context.appendJumpTo(m_breakTags.back());
+ CompilerUtils(m_context).popAndJump(m_breakTags.back().second, m_breakTags.back().first);
return false;
}
@@ -784,10 +791,8 @@ bool ContractCompiler::visit(Return const& _return)
for (auto const& retVariable: boost::adaptors::reverse(returnParameters))
CompilerUtils(m_context).moveToStackVariable(*retVariable);
}
- for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
- m_context << Instruction::POP;
- m_context.appendJumpTo(m_returnTags.back());
- m_context.adjustStackOffset(m_stackCleanupForReturn);
+
+ CompilerUtils(m_context).popAndJump(m_returnTags.back().second, m_returnTags.back().first);
return false;
}
@@ -810,8 +815,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit)
bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
- StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
+
+ // Local variable slots are reserved when their declaration is visited,
+ // and freed in the end of their scope.
+ for (auto _decl: _variableDeclarationStatement.declarations())
+ if (_decl)
+ appendStackVariableInitialisation(*_decl);
+
+ StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue())
{
CompilerUtils utils(m_context);
@@ -861,6 +873,18 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
return true;
}
+bool ContractCompiler::visit(Block const& _block)
+{
+ storeStackHeight(&_block);
+ return true;
+}
+
+void ContractCompiler::endVisit(Block const& _block)
+{
+ // Frees local variables declared in the scope of this block.
+ popScopedVariables(&_block);
+}
+
void ContractCompiler::appendMissingFunctions()
{
while (Declaration const* function = m_context.nextFunctionToCompile())
@@ -916,27 +940,19 @@ void ContractCompiler::appendModifierOrFunctionCode()
modifier.parameters()[i]->annotation().type
);
}
- for (VariableDeclaration const* localVariable: modifier.localVariables())
- {
- addedVariables.push_back(localVariable);
- appendStackVariableInitialisation(*localVariable);
- }
- stackSurplus =
- CompilerUtils::sizeOnStack(modifier.parameters()) +
- CompilerUtils::sizeOnStack(modifier.localVariables());
+ stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters());
codeBlock = &modifier.body();
}
}
if (codeBlock)
{
- m_returnTags.push_back(m_context.newTag());
-
+ m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()});
codeBlock->accept(*this);
solAssert(!m_returnTags.empty(), "");
- m_context << m_returnTags.back();
+ m_context << m_returnTags.back().first;
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
@@ -983,3 +999,20 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() const
a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a);
}
+
+void ContractCompiler::popScopedVariables(ASTNode const* _node)
+{
+ unsigned blockHeight = m_scopeStackHeight.at(m_modifierDepth).at(_node);
+ m_context.removeVariablesAboveStackHeight(blockHeight);
+ solAssert(m_context.stackHeight() >= blockHeight, "");
+ unsigned stackDiff = m_context.stackHeight() - blockHeight;
+ CompilerUtils(m_context).popStackSlots(stackDiff);
+ m_scopeStackHeight[m_modifierDepth].erase(_node);
+ if (m_scopeStackHeight[m_modifierDepth].size() == 0)
+ m_scopeStackHeight.erase(m_modifierDepth);
+}
+
+void ContractCompiler::storeStackHeight(ASTNode const* _node)
+{
+ m_scopeStackHeight[m_modifierDepth][_node] = m_context.stackHeight();
+}
diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h
index 02a3452f..8516ec2c 100644
--- a/libsolidity/codegen/ContractCompiler.h
+++ b/libsolidity/codegen/ContractCompiler.h
@@ -109,6 +109,8 @@ private:
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
virtual bool visit(ExpressionStatement const& _expressionStatement) override;
virtual bool visit(PlaceholderStatement const&) override;
+ virtual bool visit(Block const& _block) override;
+ virtual void endVisit(Block const& _block) override;
/// Repeatedly visits all function which are referenced but which are not compiled yet.
void appendMissingFunctions();
@@ -123,19 +125,31 @@ private:
/// @returns the runtime assembly for clone contracts.
eth::AssemblyPointer cloneRuntime() const;
+ /// Frees the variables of a certain scope (to be used when leaving).
+ void popScopedVariables(ASTNode const* _node);
+
+ /// Sets the stack height for the visited loop.
+ void storeStackHeight(ASTNode const* _node);
+
bool const m_optimise;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;
- std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
- std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
- /// Tag to jump to for a "return" statement, needs to be stacked because of modifiers.
- std::vector<eth::AssemblyItem> m_returnTags;
+ /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_breakTags;
+ /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_continueTags;
+ /// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables.
+ /// Needs to be stacked because of modifiers.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_returnTags;
unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction = nullptr;
- unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
+
// arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
+
+ /// Stores the variables that were declared inside a specific scope, for each modifier depth.
+ std::map<unsigned, std::map<ASTNode const*, unsigned>> m_scopeStackHeight;
};
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 483faae4..4cec69c8 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -349,6 +349,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved.");
+ solUnimplementedAssert(
+ _unaryOperation.annotation().type->category() != Type::Category::FixedPoint,
+ "Not yet implemented - FixedPointType."
+ );
m_currentLValue->retrieveValue(_unaryOperation.location());
if (!_unaryOperation.isPrefixOperation())
{
@@ -1647,12 +1651,12 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
{
- IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
- bool const c_isSigned = type.isSigned();
-
if (_type.category() == Type::Category::FixedPoint)
solUnimplemented("Not yet implemented - FixedPointType.");
+ IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
+ bool const c_isSigned = type.isSigned();
+
switch (_operator)
{
case Token::Add:
@@ -1810,15 +1814,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (_functionType.bound())
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
- bool const v050 = m_context.experimentalFeatureActive(ExperimentalFeature::V050);
auto funKind = _functionType.kind();
bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall;
bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
- bool useStaticCall =
- _functionType.stateMutability() <= StateMutability::View &&
- v050 &&
- m_context.evmVersion().hasStaticCall();
+ bool useStaticCall = _functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall();
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index dd20756f..ad425d98 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -523,6 +523,96 @@ BOOST_AUTO_TEST_CASE(do_while_loop_continue)
ABI_CHECK(callContractFunction("f()"), encodeArgs(42));
}
+BOOST_AUTO_TEST_CASE(array_multiple_local_vars)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint256[] seq) external pure returns (uint256) {
+ uint i = 0;
+ uint sum = 0;
+ while (i < seq.length)
+ {
+ uint idx = i;
+ if (idx >= 10) break;
+ uint x = seq[idx];
+ if (x >= 1000) {
+ uint n = i + 1;
+ i = n;
+ continue;
+ }
+ else {
+ uint y = sum + x;
+ sum = y;
+ }
+ if (sum >= 500) return sum;
+ i++;
+ }
+ return sum;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+
+ ABI_CHECK(callContractFunction("f(uint256[])", 32, 3, u256(1000), u256(1), u256(2)), encodeArgs(3));
+ ABI_CHECK(callContractFunction("f(uint256[])", 32, 3, u256(100), u256(500), u256(300)), encodeArgs(600));
+ ABI_CHECK(callContractFunction(
+ "f(uint256[])", 32, 11,
+ u256(1), u256(2), u256(3), u256(4), u256(5), u256(6), u256(7), u256(8), u256(9), u256(10), u256(111)
+ ), encodeArgs(55));
+}
+
+
+BOOST_AUTO_TEST_CASE(do_while_loop_multiple_local_vars)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint x) public pure returns(uint r) {
+ uint i = 0;
+ do
+ {
+ uint z = x * 2;
+ if (z < 4) break;
+ else {
+ uint k = z + 1;
+ if (k < 8) {
+ x++;
+ continue;
+ }
+ }
+ if (z > 12) return 0;
+ x++;
+ i++;
+ } while (true);
+ return 42;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+
+ auto do_while = [](u256 n) -> u256
+ {
+ u256 i = 0;
+ do
+ {
+ u256 z = n * 2;
+ if (z < 4) break;
+ else {
+ u256 k = z + 1;
+ if (k < 8) {
+ n++;
+ continue;
+ }
+ }
+ if (z > 12) return 0;
+ n++;
+ i++;
+ } while (true);
+ return 42;
+ };
+
+ testContractAgainstCppOnRange("f(uint256)", do_while, 0, 12);
+}
+
BOOST_AUTO_TEST_CASE(nested_loops)
{
// tests that break and continue statements in nested loops jump to the correct place
@@ -574,6 +664,178 @@ BOOST_AUTO_TEST_CASE(nested_loops)
testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12);
}
+BOOST_AUTO_TEST_CASE(nested_loops_multiple_local_vars)
+{
+ // tests that break and continue statements in nested loops jump to the correct place
+ // and free local variables properly
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint x) returns(uint y) {
+ while (x > 0) {
+ uint z = x + 10;
+ uint k = z + 1;
+ if (k > 20) {
+ break;
+ uint p = 100;
+ k += p;
+ }
+ if (k > 15) {
+ x--;
+ continue;
+ uint t = 1000;
+ x += t;
+ }
+ while (k > 10) {
+ uint m = k - 1;
+ if (m == 10) return x;
+ return k;
+ uint h = 10000;
+ z += h;
+ }
+ x--;
+ break;
+ }
+ return x;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+
+ auto nested_loops_cpp = [](u256 n) -> u256
+ {
+ while (n > 0)
+ {
+ u256 z = n + 10;
+ u256 k = z + 1;
+ if (k > 20) break;
+ if (k > 15) {
+ n--;
+ continue;
+ }
+ while (k > 10)
+ {
+ u256 m = k - 1;
+ if (m == 10) return n;
+ return k;
+ }
+ n--;
+ break;
+ }
+
+ return n;
+ };
+
+ testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12);
+}
+
+BOOST_AUTO_TEST_CASE(for_loop_multiple_local_vars)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint x) public pure returns(uint r) {
+ for (uint i = 0; i < 12; i++)
+ {
+ uint z = x + 1;
+ if (z < 4) break;
+ else {
+ uint k = z * 2;
+ if (i + k < 10) {
+ x++;
+ continue;
+ }
+ }
+ if (z > 8) return 0;
+ x++;
+ }
+ return 42;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+
+ auto for_loop = [](u256 n) -> u256
+ {
+ for (u256 i = 0; i < 12; i++)
+ {
+ u256 z = n + 1;
+ if (z < 4) break;
+ else {
+ u256 k = z * 2;
+ if (i + k < 10) {
+ n++;
+ continue;
+ }
+ }
+ if (z > 8) return 0;
+ n++;
+ }
+ return 42;
+ };
+
+ testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12);
+}
+
+BOOST_AUTO_TEST_CASE(nested_for_loop_multiple_local_vars)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint x) public pure returns(uint r) {
+ for (uint i = 0; i < 5; i++)
+ {
+ uint z = x + 1;
+ if (z < 3) {
+ break;
+ uint p = z + 2;
+ }
+ for (uint j = 0; j < 5; j++)
+ {
+ uint k = z * 2;
+ if (j + k < 8) {
+ x++;
+ continue;
+ uint t = z * 3;
+ }
+ x++;
+ if (x > 20) {
+ return 84;
+ uint h = x + 42;
+ }
+ }
+ if (x > 30) {
+ return 42;
+ uint b = 0xcafe;
+ }
+ }
+ return 42;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+
+ auto for_loop = [](u256 n) -> u256
+ {
+ for (u256 i = 0; i < 5; i++)
+ {
+ u256 z = n + 1;
+ if (z < 3) break;
+ for (u256 j = 0; j < 5; j++)
+ {
+ u256 k = z * 2;
+ if (j + k < 8) {
+ n++;
+ continue;
+ }
+ n++;
+ if (n > 20) return 84;
+ }
+ if (n > 30) return 42;
+ }
+ return 42;
+ };
+
+ testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12);
+}
+
BOOST_AUTO_TEST_CASE(for_loop)
{
char const* sourceCode = R"(
@@ -9232,7 +9494,9 @@ BOOST_AUTO_TEST_CASE(break_in_modifier)
}
}
function f() run {
- x++;
+ uint k = x;
+ uint t = k + 1;
+ x = t;
}
}
)";
@@ -9242,6 +9506,54 @@ BOOST_AUTO_TEST_CASE(break_in_modifier)
ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(1)));
}
+BOOST_AUTO_TEST_CASE(continue_in_modifier)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint public x;
+ modifier run() {
+ for (uint i = 0; i < 10; i++) {
+ if (i % 2 == 1) continue;
+ _;
+ }
+ }
+ function f() run {
+ uint k = x;
+ uint t = k + 1;
+ x = t;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("f()"), encodeArgs());
+ ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(5)));
+}
+
+BOOST_AUTO_TEST_CASE(return_in_modifier)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint public x;
+ modifier run() {
+ for (uint i = 1; i < 10; i++) {
+ if (i == 5) return;
+ _;
+ }
+ }
+ function f() run {
+ uint k = x;
+ uint t = k + 1;
+ x = t;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("f()"), encodeArgs());
+ ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(4)));
+}
+
BOOST_AUTO_TEST_CASE(stacked_return_with_modifiers)
{
char const* sourceCode = R"(
@@ -9254,7 +9566,9 @@ BOOST_AUTO_TEST_CASE(stacked_return_with_modifiers)
}
}
function f() run {
- x++;
+ uint k = x;
+ uint t = k + 1;
+ x = t;
}
}
)";
@@ -12173,7 +12487,6 @@ BOOST_AUTO_TEST_CASE(abi_encode_call)
BOOST_AUTO_TEST_CASE(staticcall_for_view_and_pure)
{
char const* sourceCode = R"(
- pragma experimental "v0.5.0";
contract C {
uint x;
function f() public returns (uint) {
@@ -12408,6 +12721,43 @@ BOOST_AUTO_TEST_CASE(senders_balance)
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(27)));
}
+BOOST_AUTO_TEST_CASE(write_storage_external)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint public x;
+ function f(uint y) public payable {
+ x = y;
+ }
+ function g(uint y) external {
+ x = y;
+ }
+ function h() public {
+ this.g(12);
+ }
+ }
+ contract D {
+ C c = new C();
+ function f() public payable returns (uint) {
+ c.g(3);
+ return c.x();
+ }
+ function g() public returns (uint) {
+ c.g(8);
+ return c.x();
+ }
+ function h() public returns (uint) {
+ c.h();
+ return c.x();
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "D");
+ ABI_CHECK(callContractFunction("f()"), encodeArgs(3));
+ ABI_CHECK(callContractFunction("g()"), encodeArgs(8));
+ ABI_CHECK(callContractFunction("h()"), encodeArgs(12));
+}
+
BOOST_AUTO_TEST_SUITE_END()
}