aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/contracts.rst40
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp21
-rw-r--r--libsolidity/analysis/SyntaxChecker.h9
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp73
-rw-r--r--libsolidity/codegen/ContractCompiler.h7
-rw-r--r--solc/CommandLineInterface.cpp28
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp140
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp30
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()
}