mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #835 from chriseth/modifierreturn
BREAKING: return only exits current function/modifier
This commit is contained in:
commit
6baa982a6a
@ -314,14 +314,40 @@ inheritable properties of contracts and may be overridden by derived contracts.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract Mutex {
|
||||||
|
bool locked;
|
||||||
|
modifier noReentrancy() {
|
||||||
|
if (locked) throw;
|
||||||
|
locked = true;
|
||||||
|
_
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is protected by a mutex, which means that
|
||||||
|
/// reentrant calls from within msg.sender.call cannot call f again.
|
||||||
|
/// The `return 7` statement assigns 7 to the return value but still
|
||||||
|
/// executes the statement `locked = false` in the modifier.
|
||||||
|
function f() noReentrancy returns (uint) {
|
||||||
|
if (!msg.sender.call()) throw;
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Multiple modifiers can be applied to a function by specifying them in a
|
Multiple modifiers can be applied to a function by specifying them in a
|
||||||
whitespace-separated list and will be evaluated in order. Explicit returns from
|
whitespace-separated list and will be evaluated in order.
|
||||||
a modifier or function body immediately leave the whole function, while control
|
|
||||||
flow reaching the end of a function or modifier body continues after the "_" in
|
.. warning::
|
||||||
the preceding modifier. Arbitrary expressions are allowed for modifier
|
In an earlier version of Solidity, ``return`` statements in functions
|
||||||
arguments and in this context, all symbols visible from the function are
|
having modifiers behaved differently.
|
||||||
visible in the modifier. Symbols introduced in the modifier are not visible in
|
|
||||||
the function (as they might change by overriding).
|
Explicit returns from a modifier or function body only leave the current
|
||||||
|
modifier or function body. Return variables are assigned and
|
||||||
|
control flow continues after the "_" in the preceding modifier.
|
||||||
|
|
||||||
|
Arbitrary expressions are allowed for modifier arguments and in this context,
|
||||||
|
all symbols visible from the function are visible in the modifier. Symbols
|
||||||
|
introduced in the modifier are not visible in the function (as they might
|
||||||
|
change by overriding).
|
||||||
|
|
||||||
.. index:: ! constant
|
.. index:: ! constant
|
||||||
|
|
||||||
|
@ -431,16 +431,16 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
|
|||||||
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
|
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
|
||||||
appendBaseConstructor(*c);
|
appendBaseConstructor(*c);
|
||||||
|
|
||||||
m_returnTag = m_context.newTag();
|
solAssert(m_returnTags.empty(), "");
|
||||||
m_breakTags.clear();
|
m_breakTags.clear();
|
||||||
m_continueTags.clear();
|
m_continueTags.clear();
|
||||||
m_stackCleanupForReturn = 0;
|
m_stackCleanupForReturn = 0;
|
||||||
m_currentFunction = &_function;
|
m_currentFunction = &_function;
|
||||||
m_modifierDepth = 0;
|
m_modifierDepth = -1;
|
||||||
|
|
||||||
appendModifierOrFunctionCode();
|
appendModifierOrFunctionCode();
|
||||||
|
|
||||||
m_context << m_returnTag;
|
solAssert(m_returnTags.empty(), "");
|
||||||
|
|
||||||
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
|
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
|
||||||
// that shows the target positions of the elements, where "-1" denotes that this element needs
|
// that shows the target positions of the elements, where "-1" denotes that this element needs
|
||||||
@ -695,7 +695,7 @@ bool ContractCompiler::visit(Return const& _return)
|
|||||||
}
|
}
|
||||||
for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
|
for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
|
||||||
m_context << Instruction::POP;
|
m_context << Instruction::POP;
|
||||||
m_context.appendJumpTo(m_returnTag);
|
m_context.appendJumpTo(m_returnTags.back());
|
||||||
m_context.adjustStackOffset(m_stackCleanupForReturn);
|
m_context.adjustStackOffset(m_stackCleanupForReturn);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -755,9 +755,7 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
|
|||||||
{
|
{
|
||||||
StackHeightChecker checker(m_context);
|
StackHeightChecker checker(m_context);
|
||||||
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
|
CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
|
||||||
++m_modifierDepth;
|
|
||||||
appendModifierOrFunctionCode();
|
appendModifierOrFunctionCode();
|
||||||
--m_modifierDepth;
|
|
||||||
checker.check();
|
checker.check();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -775,10 +773,15 @@ void ContractCompiler::appendMissingFunctions()
|
|||||||
void ContractCompiler::appendModifierOrFunctionCode()
|
void ContractCompiler::appendModifierOrFunctionCode()
|
||||||
{
|
{
|
||||||
solAssert(m_currentFunction, "");
|
solAssert(m_currentFunction, "");
|
||||||
|
unsigned stackSurplus = 0;
|
||||||
|
Block const* codeBlock = nullptr;
|
||||||
|
|
||||||
|
m_modifierDepth++;
|
||||||
|
|
||||||
if (m_modifierDepth >= m_currentFunction->modifiers().size())
|
if (m_modifierDepth >= m_currentFunction->modifiers().size())
|
||||||
{
|
{
|
||||||
solAssert(m_currentFunction->isImplemented(), "");
|
solAssert(m_currentFunction->isImplemented(), "");
|
||||||
m_currentFunction->body().accept(*this);
|
codeBlock = &m_currentFunction->body();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -786,37 +789,45 @@ void ContractCompiler::appendModifierOrFunctionCode()
|
|||||||
|
|
||||||
// constructor call should be excluded
|
// constructor call should be excluded
|
||||||
if (dynamic_cast<ContractDefinition const*>(modifierInvocation->name()->annotation().referencedDeclaration))
|
if (dynamic_cast<ContractDefinition const*>(modifierInvocation->name()->annotation().referencedDeclaration))
|
||||||
{
|
|
||||||
++m_modifierDepth;
|
|
||||||
appendModifierOrFunctionCode();
|
appendModifierOrFunctionCode();
|
||||||
--m_modifierDepth;
|
else
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name());
|
|
||||||
CompilerContext::LocationSetter locationSetter(m_context, modifier);
|
|
||||||
solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), "");
|
|
||||||
for (unsigned i = 0; i < modifier.parameters().size(); ++i)
|
|
||||||
{
|
{
|
||||||
m_context.addVariable(*modifier.parameters()[i]);
|
ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name());
|
||||||
compileExpression(
|
CompilerContext::LocationSetter locationSetter(m_context, modifier);
|
||||||
*modifierInvocation->arguments()[i],
|
solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), "");
|
||||||
modifier.parameters()[i]->annotation().type
|
for (unsigned i = 0; i < modifier.parameters().size(); ++i)
|
||||||
);
|
{
|
||||||
|
m_context.addVariable(*modifier.parameters()[i]);
|
||||||
|
compileExpression(
|
||||||
|
*modifierInvocation->arguments()[i],
|
||||||
|
modifier.parameters()[i]->annotation().type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (VariableDeclaration const* localVariable: modifier.localVariables())
|
||||||
|
appendStackVariableInitialisation(*localVariable);
|
||||||
|
|
||||||
|
stackSurplus =
|
||||||
|
CompilerUtils::sizeOnStack(modifier.parameters()) +
|
||||||
|
CompilerUtils::sizeOnStack(modifier.localVariables());
|
||||||
|
codeBlock = &modifier.body();
|
||||||
|
|
||||||
|
codeBlock = &modifier.body();
|
||||||
}
|
}
|
||||||
for (VariableDeclaration const* localVariable: modifier.localVariables())
|
|
||||||
appendStackVariableInitialisation(*localVariable);
|
|
||||||
|
|
||||||
unsigned const c_stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters()) +
|
|
||||||
CompilerUtils::sizeOnStack(modifier.localVariables());
|
|
||||||
m_stackCleanupForReturn += c_stackSurplus;
|
|
||||||
|
|
||||||
modifier.body().accept(*this);
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < c_stackSurplus; ++i)
|
|
||||||
m_context << Instruction::POP;
|
|
||||||
m_stackCleanupForReturn -= c_stackSurplus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (codeBlock)
|
||||||
|
{
|
||||||
|
m_returnTags.push_back(m_context.newTag());
|
||||||
|
|
||||||
|
codeBlock->accept(*this);
|
||||||
|
|
||||||
|
solAssert(!m_returnTags.empty(), "");
|
||||||
|
m_context << m_returnTags.back();
|
||||||
|
m_returnTags.pop_back();
|
||||||
|
|
||||||
|
CompilerUtils(m_context).popStackSlots(stackSurplus);
|
||||||
|
}
|
||||||
|
m_modifierDepth--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable)
|
void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable)
|
||||||
|
@ -40,11 +40,9 @@ class ContractCompiler: private ASTConstVisitor
|
|||||||
public:
|
public:
|
||||||
explicit ContractCompiler(CompilerContext& _context, bool _optimise):
|
explicit ContractCompiler(CompilerContext& _context, bool _optimise):
|
||||||
m_optimise(_optimise),
|
m_optimise(_optimise),
|
||||||
m_context(_context),
|
m_context(_context)
|
||||||
m_returnTag(eth::Tag, u256(-1))
|
|
||||||
{
|
{
|
||||||
m_context = CompilerContext();
|
m_context = CompilerContext();
|
||||||
m_returnTag = m_context.newTag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void compileContract(
|
void compileContract(
|
||||||
@ -122,7 +120,8 @@ private:
|
|||||||
CompilerContext& m_context;
|
CompilerContext& m_context;
|
||||||
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
|
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
|
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
|
||||||
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
|
/// Tag to jump to for a "return" statement, needs to be stacked because of modifiers.
|
||||||
|
std::vector<eth::AssemblyItem> m_returnTags;
|
||||||
unsigned m_modifierDepth = 0;
|
unsigned m_modifierDepth = 0;
|
||||||
FunctionDefinition const* m_currentFunction = nullptr;
|
FunctionDefinition const* m_currentFunction = nullptr;
|
||||||
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
|
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
|
||||||
|
@ -2390,7 +2390,8 @@ BOOST_AUTO_TEST_CASE(function_modifier_multi_invocation)
|
|||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(function_modifier_multi_with_return)
|
BOOST_AUTO_TEST_CASE(function_modifier_multi_with_return)
|
||||||
{
|
{
|
||||||
// Here, the explicit return prevents the second execution
|
// Note that return sets the return variable and jumps to the end of the current function or
|
||||||
|
// modifier code block.
|
||||||
char const* sourceCode = R"(
|
char const* sourceCode = R"(
|
||||||
contract C {
|
contract C {
|
||||||
modifier repeat(bool twice) { if (twice) _ _ }
|
modifier repeat(bool twice) { if (twice) _ _ }
|
||||||
@ -2399,7 +2400,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_multi_with_return)
|
|||||||
)";
|
)";
|
||||||
compileAndRun(sourceCode);
|
compileAndRun(sourceCode);
|
||||||
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(1));
|
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(1));
|
||||||
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(1));
|
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(function_modifier_overriding)
|
BOOST_AUTO_TEST_CASE(function_modifier_overriding)
|
||||||
@ -6880,6 +6881,137 @@ BOOST_AUTO_TEST_CASE(create_dynamic_array_with_zero_length)
|
|||||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7)));
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(return_does_not_skip_modifier)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
uint public x;
|
||||||
|
modifier setsx {
|
||||||
|
_
|
||||||
|
x = 9;
|
||||||
|
}
|
||||||
|
function f() setsx returns (uint) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0)));
|
||||||
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2)));
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(9)));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(break_in_modifier)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
uint public x;
|
||||||
|
modifier run() {
|
||||||
|
for (uint i = 0; i < 10; i++) {
|
||||||
|
_
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function f() run {
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0)));
|
||||||
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs());
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(stacked_return_with_modifiers)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
uint public x;
|
||||||
|
modifier run() {
|
||||||
|
for (uint i = 0; i < 10; i++) {
|
||||||
|
_
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function f() run {
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0)));
|
||||||
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs());
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(mutex)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract mutexed {
|
||||||
|
bool locked;
|
||||||
|
modifier protected {
|
||||||
|
if (locked) throw;
|
||||||
|
locked = true;
|
||||||
|
_
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contract Fund is mutexed {
|
||||||
|
uint shares;
|
||||||
|
function Fund() { shares = msg.value; }
|
||||||
|
function withdraw(uint amount) protected returns (uint) {
|
||||||
|
// NOTE: It is very bad practice to write this function this way.
|
||||||
|
// Please refer to the documentation of how to do this properly.
|
||||||
|
if (amount > shares) throw;
|
||||||
|
if (!msg.sender.call.value(amount)()) throw;
|
||||||
|
shares -= amount;
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
function withdrawUnprotected(uint amount) returns (uint) {
|
||||||
|
// NOTE: It is very bad practice to write this function this way.
|
||||||
|
// Please refer to the documentation of how to do this properly.
|
||||||
|
if (amount > shares) throw;
|
||||||
|
if (!msg.sender.call.value(amount)()) throw;
|
||||||
|
shares -= amount;
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contract Attacker {
|
||||||
|
Fund public fund;
|
||||||
|
uint callDepth;
|
||||||
|
bool protected;
|
||||||
|
function setProtected(bool _protected) { protected = _protected; }
|
||||||
|
function Attacker(Fund _fund) { fund = _fund; }
|
||||||
|
function attack() returns (uint) {
|
||||||
|
callDepth = 0;
|
||||||
|
return attackInternal();
|
||||||
|
}
|
||||||
|
function attackInternal() internal returns (uint) {
|
||||||
|
if (protected)
|
||||||
|
return fund.withdraw(10);
|
||||||
|
else
|
||||||
|
return fund.withdrawUnprotected(10);
|
||||||
|
}
|
||||||
|
function() {
|
||||||
|
callDepth++;
|
||||||
|
if (callDepth < 4)
|
||||||
|
attackInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 500, "Fund");
|
||||||
|
auto fund = m_contractAddress;
|
||||||
|
BOOST_CHECK_EQUAL(balanceAt(fund), 500);
|
||||||
|
compileAndRun(sourceCode, 0, "Attacker", encodeArgs(u160(fund)));
|
||||||
|
BOOST_CHECK(callContractFunction("setProtected(bool)", true) == encodeArgs());
|
||||||
|
BOOST_CHECK(callContractFunction("attack()") == encodeArgs());
|
||||||
|
BOOST_CHECK_EQUAL(balanceAt(fund), 500);
|
||||||
|
BOOST_CHECK(callContractFunction("setProtected(bool)", false) == encodeArgs());
|
||||||
|
BOOST_CHECK(callContractFunction("attack()") == encodeArgs(u256(460)));
|
||||||
|
BOOST_CHECK_EQUAL(balanceAt(fund), 460);
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input)
|
BOOST_AUTO_TEST_CASE(failing_ecrecover_invalid_input)
|
||||||
{
|
{
|
||||||
// ecrecover should return zero for malformed input
|
// ecrecover should return zero for malformed input
|
||||||
|
Loading…
Reference in New Issue
Block a user