diff options
-rw-r--r-- | docs/contracts.rst | 40 | ||||
-rw-r--r-- | libsolidity/analysis/SyntaxChecker.cpp | 21 | ||||
-rw-r--r-- | libsolidity/analysis/SyntaxChecker.h | 9 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.cpp | 73 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.h | 7 | ||||
-rw-r--r-- | solc/CommandLineInterface.cpp | 28 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 140 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 30 |
8 files changed, 280 insertions, 68 deletions
diff --git a/docs/contracts.rst b/docs/contracts.rst index 3d592ecf..5f370951 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -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 -whitespace-separated list and will be evaluated in order. Explicit returns from -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 -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). +whitespace-separated list and will be evaluated in order. + +.. warning:: + In an earlier version of Solidity, ``return`` statements in functions + having modifiers behaved differently. + +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 diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index e94ce9fe..593f2f69 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -40,13 +40,26 @@ void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string con m_errors.push_back(err); } +bool SyntaxChecker::visit(ModifierDefinition const&) +{ + m_placeholderFound = false; + return true; +} + +void SyntaxChecker::endVisit(ModifierDefinition const& _modifier) +{ + if (!m_placeholderFound) + syntaxError(_modifier.body().location(), "Modifier body does not contain '_'."); + m_placeholderFound = false; +} + bool SyntaxChecker::visit(WhileStatement const&) { m_inLoopDepth++; return true; } -void SyntaxChecker::endVisit(WhileStatement const&) +void SyntaxChecker::endVisit(WhileStatement const& ) { m_inLoopDepth--; } @@ -78,3 +91,9 @@ bool SyntaxChecker::visit(Break const& _breakStatement) return true; } +bool SyntaxChecker::visit(const PlaceholderStatement&) +{ + m_placeholderFound = true; + return true; +} + diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index c836d49f..3198ffd0 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -31,6 +31,7 @@ namespace solidity /** * The module that performs syntax analysis on the AST: * - whether continue/break is in a for/while loop. + * - whether a modifier contains at least one '_' */ class SyntaxChecker: private ASTConstVisitor { @@ -44,6 +45,9 @@ private: /// Adds a new error to the list of errors. void syntaxError(SourceLocation const& _location, std::string const& _description); + virtual bool visit(ModifierDefinition const& _modifier) override; + virtual void endVisit(ModifierDefinition const& _modifier) override; + virtual bool visit(WhileStatement const& _whileStatement) override; virtual void endVisit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; @@ -52,8 +56,13 @@ private: virtual bool visit(Continue const& _continueStatement) override; virtual bool visit(Break const& _breakStatement) override; + virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; + ErrorList& m_errors; + /// Flag that indicates whether a function modifier actually contains '_'. + bool m_placeholderFound = false; + int m_inLoopDepth = 0; }; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index bcfd33f2..715852be 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -431,16 +431,16 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope()))) appendBaseConstructor(*c); - m_returnTag = m_context.newTag(); + solAssert(m_returnTags.empty(), ""); m_breakTags.clear(); m_continueTags.clear(); m_stackCleanupForReturn = 0; m_currentFunction = &_function; - m_modifierDepth = 0; + m_modifierDepth = -1; 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 // 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) m_context << Instruction::POP; - m_context.appendJumpTo(m_returnTag); + m_context.appendJumpTo(m_returnTags.back()); m_context.adjustStackOffset(m_stackCleanupForReturn); return false; } @@ -755,9 +755,7 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) { StackHeightChecker checker(m_context); CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); - ++m_modifierDepth; appendModifierOrFunctionCode(); - --m_modifierDepth; checker.check(); return true; } @@ -775,10 +773,15 @@ void ContractCompiler::appendMissingFunctions() void ContractCompiler::appendModifierOrFunctionCode() { solAssert(m_currentFunction, ""); + unsigned stackSurplus = 0; + Block const* codeBlock = nullptr; + + m_modifierDepth++; + if (m_modifierDepth >= m_currentFunction->modifiers().size()) { solAssert(m_currentFunction->isImplemented(), ""); - m_currentFunction->body().accept(*this); + codeBlock = &m_currentFunction->body(); } else { @@ -786,37 +789,45 @@ void ContractCompiler::appendModifierOrFunctionCode() // constructor call should be excluded if (dynamic_cast<ContractDefinition const*>(modifierInvocation->name()->annotation().referencedDeclaration)) - { - ++m_modifierDepth; appendModifierOrFunctionCode(); - --m_modifierDepth; - 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) + else { - m_context.addVariable(*modifier.parameters()[i]); - compileExpression( - *modifierInvocation->arguments()[i], - modifier.parameters()[i]->annotation().type - ); + 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]); + 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); + } + + if (codeBlock) + { + m_returnTags.push_back(m_context.newTag()); - unsigned const c_stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters()) + - CompilerUtils::sizeOnStack(modifier.localVariables()); - m_stackCleanupForReturn += c_stackSurplus; + codeBlock->accept(*this); - modifier.body().accept(*this); + solAssert(!m_returnTags.empty(), ""); + m_context << m_returnTags.back(); + m_returnTags.pop_back(); - for (unsigned i = 0; i < c_stackSurplus; ++i) - m_context << Instruction::POP; - m_stackCleanupForReturn -= c_stackSurplus; + CompilerUtils(m_context).popStackSlots(stackSurplus); } + m_modifierDepth--; } void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index d1517e88..0799a543 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -40,11 +40,9 @@ class ContractCompiler: private ASTConstVisitor public: explicit ContractCompiler(CompilerContext& _context, bool _optimise): m_optimise(_optimise), - m_context(_context), - m_returnTag(eth::Tag, u256(-1)) + m_context(_context) { m_context = CompilerContext(); - m_returnTag = m_context.newTag(); } void compileContract( @@ -122,7 +120,8 @@ private: 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 - 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; FunctionDefinition const* m_currentFunction = nullptr; unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index ec87b891..d1834794 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -310,21 +310,18 @@ void CommandLineInterface::handleFormal() void CommandLineInterface::readInputFilesAndConfigureRemappings() { + vector<string> inputFiles; + bool addStdin = false; if (!m_args.count("input-file")) - { - string s; - while (!cin.eof()) - { - getline(cin, s); - m_sourceCodes[g_stdinFileName].append(s + '\n'); - } - } + addStdin = true; else for (string path: m_args["input-file"].as<vector<string>>()) { auto eq = find(path.begin(), path.end(), '='); if (eq != path.end()) path = string(eq + 1, path.end()); + else if (path == "-") + addStdin = true; else { auto infile = boost::filesystem::path(path); @@ -345,6 +342,15 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings() } m_allowedDirectories.push_back(boost::filesystem::path(path).remove_filename()); } + if (addStdin) + { + string s; + while (!cin.eof()) + { + getline(cin, s); + m_sourceCodes[g_stdinFileName].append(s + '\n'); + } + } } bool CommandLineInterface::parseLibraryOption(string const& _input) @@ -399,9 +405,9 @@ bool CommandLineInterface::parseArguments(int _argc, char** _argv) po::options_description desc( R"(solc, the Solidity commandline compiler. Usage: solc [options] [input_file...] -Compiles the given Solidity input files (or the standard input if none given) and -outputs the components specified in the options at standard output or in files in -the output directory, if specified. +Compiles the given Solidity input files (or the standard input if none given or +"-" is used as a file name) and outputs the components specified in the options +at standard output or in files in the output directory, if specified. Example: solc --bin -o /tmp/solcoutput contract.sol Allowed options)", diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 647199ea..f20ea2cb 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2390,7 +2390,8 @@ BOOST_AUTO_TEST_CASE(function_modifier_multi_invocation) 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"( contract C { modifier repeat(bool twice) { if (twice) _ _ } @@ -2399,7 +2400,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_multi_with_return) )"; compileAndRun(sourceCode); 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) @@ -2410,7 +2411,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_overriding) modifier mod { _ } } contract C is A { - modifier mod { } + modifier mod { if (false) _ } } )"; compileAndRun(sourceCode); @@ -2427,7 +2428,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_calling_functions_in_creation_context) function f2() { data |= 0x20; } function f3() { } modifier mod1 { f2(); _ } - modifier mod2 { f3(); } + modifier mod2 { f3(); if (false) _ } function getData() returns (uint r) { return data; } } contract C is A { @@ -6901,6 +6902,137 @@ BOOST_AUTO_TEST_CASE(create_dynamic_array_with_zero_length) 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) { // ecrecover should return zero for malformed input diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 7e81bd7e..e9da390c 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -901,8 +901,8 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables) BOOST_AUTO_TEST_CASE(legal_modifier_override) { char const* text = R"( - contract A { modifier mod(uint a) {} } - contract B is A { modifier mod(uint a) {} } + contract A { modifier mod(uint a) { _ } } + contract B is A { modifier mod(uint a) { _ } } )"; BOOST_CHECK(success(text)); } @@ -910,8 +910,8 @@ BOOST_AUTO_TEST_CASE(legal_modifier_override) BOOST_AUTO_TEST_CASE(illegal_modifier_override) { char const* text = R"( - contract A { modifier mod(uint a) {} } - contract B is A { modifier mod(uint8 a) {} } + contract A { modifier mod(uint a) { _ } } + contract B is A { modifier mod(uint8 a) { _ } } )"; BOOST_CHECK(expectError(text) == Error::Type::TypeError); } @@ -919,8 +919,8 @@ BOOST_AUTO_TEST_CASE(illegal_modifier_override) BOOST_AUTO_TEST_CASE(modifier_overrides_function) { char const* text = R"( - contract A { modifier mod(uint a) {} } - contract B is A { function mod(uint a) {} } + contract A { modifier mod(uint a) { _ } } + contract B is A { function mod(uint a) { } } )"; BOOST_CHECK(expectError(text) == Error::Type::TypeError); } @@ -928,8 +928,8 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function) BOOST_AUTO_TEST_CASE(function_overrides_modifier) { char const* text = R"( - contract A { function mod(uint a) {} } - contract B is A { modifier mod(uint a) {} } + contract A { function mod(uint a) { } } + contract B is A { modifier mod(uint a) { _ } } )"; BOOST_CHECK(expectError(text) == Error::Type::TypeError); } @@ -938,8 +938,8 @@ BOOST_AUTO_TEST_CASE(modifier_returns_value) { char const* text = R"( contract A { - function f(uint a) mod(2) returns (uint r) {} - modifier mod(uint a) { return 7; } + function f(uint a) mod(2) returns (uint r) { } + modifier mod(uint a) { _ return 7; } } )"; BOOST_CHECK(expectError(text) == Error::Type::TypeError); @@ -3823,6 +3823,16 @@ BOOST_AUTO_TEST_CASE(unused_return_value_delegatecall) BOOST_CHECK(expectError(text, true) == Error::Type::Warning); } +BOOST_AUTO_TEST_CASE(modifier_without_underscore) +{ + char const* text = R"( + contract test { + modifier m() {} + } + )"; + BOOST_CHECK(expectError(text, true) == Error::Type::SyntaxError); +} + BOOST_AUTO_TEST_SUITE_END() } |