Merge pull request #6555 from sifmelcara/break-for-loop

[Yul] Disallow function definitions inside for loop init blocks
This commit is contained in:
chriseth 2019-04-25 11:20:44 +02:00 committed by GitHub
commit b6bb3ae482
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 183 additions and 46 deletions

View File

@ -7,6 +7,7 @@ Important Bugfixes:
Language Features: Language Features:
* Code Generation: Implement copying recursive structs from storage to memory. * Code Generation: Implement copying recursive structs from storage to memory.
* ABIEncoderV2: Implement encoding of calldata arrays and structs. * ABIEncoderV2: Implement encoding of calldata arrays and structs.
* Yul: Disallow function definitions inside for-loop init blocks.
Compiler Features: Compiler Features:

View File

@ -167,6 +167,7 @@ The ``continue`` and ``break`` statements can only be used inside loop bodies
and have to be in the same function as the loop (or both have to be at the and have to be in the same function as the loop (or both have to be at the
top level). top level).
The condition part of the for-loop has to evaluate to exactly one value. The condition part of the for-loop has to evaluate to exactly one value.
Functions cannot be defined inside for loop init blocks.
Literals cannot be larger than the their type. The largest type defined is 256-bit wide. Literals cannot be larger than the their type. The largest type defined is 256-bit wide.

View File

@ -126,31 +126,19 @@ Statement Parser::parseStatement()
case Token::For: case Token::For:
return parseForLoop(); return parseForLoop();
case Token::Break: case Token::Break:
if (m_insideForLoopBody)
{ {
auto stmt = Statement{ createWithLocation<Break>() }; Statement stmt{createWithLocation<Break>()};
checkBreakContinuePosition("break");
m_scanner->next(); m_scanner->next();
return stmt; return stmt;
} }
else
{
m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed.");
m_scanner->next();
return {};
}
case Token::Continue: case Token::Continue:
if (m_insideForLoopBody)
{ {
auto stmt = Statement{ createWithLocation<Continue>() }; Statement stmt{createWithLocation<Continue>()};
checkBreakContinuePosition("continue");
m_scanner->next(); m_scanner->next();
return stmt; return stmt;
} }
else
{
m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed.");
m_scanner->next();
return {};
}
case Token::Assign: case Token::Assign:
{ {
if (m_dialect->flavour != AsmFlavour::Loose) if (m_dialect->flavour != AsmFlavour::Loose)
@ -290,20 +278,24 @@ Case Parser::parseCase()
ForLoop Parser::parseForLoop() ForLoop Parser::parseForLoop()
{ {
bool outerForLoopBody = m_insideForLoopBody;
m_insideForLoopBody = false;
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ForLoopComponent outerForLoopComponent = m_currentForLoopComponent;
ForLoop forLoop = createWithLocation<ForLoop>(); ForLoop forLoop = createWithLocation<ForLoop>();
expectToken(Token::For); expectToken(Token::For);
m_currentForLoopComponent = ForLoopComponent::ForLoopPre;
forLoop.pre = parseBlock(); forLoop.pre = parseBlock();
m_currentForLoopComponent = ForLoopComponent::None;
forLoop.condition = make_unique<Expression>(parseExpression()); forLoop.condition = make_unique<Expression>(parseExpression());
m_currentForLoopComponent = ForLoopComponent::ForLoopPost;
forLoop.post = parseBlock(); forLoop.post = parseBlock();
m_currentForLoopComponent = ForLoopComponent::ForLoopBody;
m_insideForLoopBody = true;
forLoop.body = parseBlock(); forLoop.body = parseBlock();
m_insideForLoopBody = outerForLoopBody;
forLoop.location.end = forLoop.body.location.end; forLoop.location.end = forLoop.body.location.end;
m_currentForLoopComponent = outerForLoopComponent;
return forLoop; return forLoop;
} }
@ -487,9 +479,16 @@ VariableDeclaration Parser::parseVariableDeclaration()
FunctionDefinition Parser::parseFunctionDefinition() FunctionDefinition Parser::parseFunctionDefinition()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
auto outerForLoopBody = m_insideForLoopBody;
m_insideForLoopBody = false; if (m_currentForLoopComponent == ForLoopComponent::ForLoopPre)
ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }}; m_errorReporter.syntaxError(
location(),
"Functions cannot be defined inside a for-loop init block."
);
ForLoopComponent outerForLoopComponent = m_currentForLoopComponent;
m_currentForLoopComponent = ForLoopComponent::None;
FunctionDefinition funDef = createWithLocation<FunctionDefinition>(); FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
expectToken(Token::Function); expectToken(Token::Function);
funDef.name = expectAsmIdentifier(); funDef.name = expectAsmIdentifier();
@ -516,6 +515,8 @@ FunctionDefinition Parser::parseFunctionDefinition()
} }
funDef.body = parseBlock(); funDef.body = parseBlock();
funDef.location.end = funDef.body.location.end; funDef.location.end = funDef.body.location.end;
m_currentForLoopComponent = outerForLoopComponent;
return funDef; return funDef;
} }
@ -642,6 +643,24 @@ YulString Parser::expectAsmIdentifier()
return name; return name;
} }
void Parser::checkBreakContinuePosition(string const& _which)
{
switch (m_currentForLoopComponent)
{
case ForLoopComponent::None:
m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" needs to be inside a for-loop body.");
break;
case ForLoopComponent::ForLoopPre:
m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" in for-loop init block is not allowed.");
break;
case ForLoopComponent::ForLoopPost:
m_errorReporter.syntaxError(location(), "Keyword \"" + _which + "\" in for-loop post block is not allowed.");
break;
case ForLoopComponent::ForLoopBody:
break;
}
}
bool Parser::isValidNumberLiteral(string const& _literal) bool Parser::isValidNumberLiteral(string const& _literal)
{ {
try try

View File

@ -32,14 +32,20 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
namespace yul namespace yul
{ {
class Parser: public langutil::ParserBase class Parser: public langutil::ParserBase
{ {
public: public:
enum class ForLoopComponent
{
None, ForLoopPre, ForLoopPost, ForLoopBody
};
explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr<Dialect> _dialect): explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr<Dialect> _dialect):
ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {} ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {}
/// Parses an inline assembly block starting with `{` and ending with `}`. /// Parses an inline assembly block starting with `{` and ending with `}`.
/// @param _reuseScanner if true, do check for end of input after the `}`. /// @param _reuseScanner if true, do check for end of input after the `}`.
@ -85,11 +91,14 @@ protected:
TypedName parseTypedName(); TypedName parseTypedName();
YulString expectAsmIdentifier(); YulString expectAsmIdentifier();
/// Reports an error if we are currently not inside the body part of a for loop.
void checkBreakContinuePosition(std::string const& _which);
static bool isValidNumberLiteral(std::string const& _literal); static bool isValidNumberLiteral(std::string const& _literal);
private: private:
std::shared_ptr<Dialect> m_dialect; std::shared_ptr<Dialect> m_dialect;
bool m_insideForLoopBody; ForLoopComponent m_currentForLoopComponent = ForLoopComponent::None;
}; };
} }

View File

@ -82,8 +82,8 @@ BOOST_AUTO_TEST_CASE(simple_inside_structures)
"}" "}"
"}"), "g,f"); "}"), "g,f");
BOOST_CHECK_EQUAL(inlinableFunctions("{" BOOST_CHECK_EQUAL(inlinableFunctions("{"
"for {"
"function g(a:u256) -> b:u256 { b := a }" "function g(a:u256) -> b:u256 { b := a }"
"for {"
"} 1:u256 {" "} 1:u256 {"
"function f() -> x:u256 { x := g(2:u256) }" "function f() -> x:u256 { x := g(2:u256) }"
"}" "}"

View File

@ -295,6 +295,28 @@ BOOST_AUTO_TEST_CASE(if_statement)
BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }"));
} }
BOOST_AUTO_TEST_CASE(break_outside_of_for_loop)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ let x if x { break } }",
SyntaxError,
"Keyword \"break\" needs to be inside a for-loop body.",
dialect
);
}
BOOST_AUTO_TEST_CASE(continue_outside_of_for_loop)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ let x if x { continue } }",
SyntaxError,
"Keyword \"continue\" needs to be inside a for-loop body.",
dialect
);
}
BOOST_AUTO_TEST_CASE(for_statement) BOOST_AUTO_TEST_CASE(for_statement)
{ {
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
@ -313,8 +335,9 @@ BOOST_AUTO_TEST_CASE(for_statement_break_init)
CHECK_ERROR_DIALECT( CHECK_ERROR_DIALECT(
"{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }", "{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }",
SyntaxError, SyntaxError,
"Keyword break outside for-loop body is not allowed.", "Keyword \"break\" in for-loop init block is not allowed.",
dialect); dialect
);
} }
BOOST_AUTO_TEST_CASE(for_statement_break_post) BOOST_AUTO_TEST_CASE(for_statement_break_post)
@ -323,8 +346,9 @@ BOOST_AUTO_TEST_CASE(for_statement_break_post)
CHECK_ERROR_DIALECT( CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }", "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }",
SyntaxError, SyntaxError,
"Keyword break outside for-loop body is not allowed.", "Keyword \"break\" in for-loop post block is not allowed.",
dialect); dialect
);
} }
BOOST_AUTO_TEST_CASE(for_statement_nested_break) BOOST_AUTO_TEST_CASE(for_statement_nested_break)
@ -333,8 +357,9 @@ BOOST_AUTO_TEST_CASE(for_statement_nested_break)
CHECK_ERROR_DIALECT( CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }", "{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }",
SyntaxError, SyntaxError,
"Keyword break outside for-loop body is not allowed.", "Keyword \"break\" needs to be inside a for-loop body.",
dialect); dialect
);
} }
BOOST_AUTO_TEST_CASE(for_statement_continue) BOOST_AUTO_TEST_CASE(for_statement_continue)
@ -349,8 +374,9 @@ BOOST_AUTO_TEST_CASE(for_statement_continue_fail_init)
CHECK_ERROR_DIALECT( CHECK_ERROR_DIALECT(
"{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }", "{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }",
SyntaxError, SyntaxError,
"Keyword continue outside for-loop body is not allowed.", "Keyword \"continue\" in for-loop init block is not allowed.",
dialect); dialect
);
} }
BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post) BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post)
@ -359,8 +385,89 @@ BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post)
CHECK_ERROR_DIALECT( CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }", "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }",
SyntaxError, SyntaxError,
"Keyword continue outside for-loop body is not allowed.", "Keyword \"continue\" in for-loop post block is not allowed.",
dialect); dialect
);
}
BOOST_AUTO_TEST_CASE(for_statement_nested_continue)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { continue } } }",
SyntaxError,
"Keyword \"continue\" needs to be inside a for-loop body.",
dialect
);
}
BOOST_AUTO_TEST_CASE(for_statement_continue_nested_init_in_body)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople());
CHECK_ERROR_DIALECT(
"{ for {} 1 {} {let x for { continue } x {} {}} }",
SyntaxError,
"Keyword \"continue\" in for-loop init block is not allowed.",
dialect
);
}
BOOST_AUTO_TEST_CASE(for_statement_continue_nested_body_in_init)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse("{ for {let x for {} x {} { continue }} 1 {} {} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_break_nested_body_in_init)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse("{ for {let x for {} x {} { break }} 1 {} {} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_continue_nested_body_in_post)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse("{ for {} 1 {let x for {} x {} { continue }} {} }", dialect));
}
BOOST_AUTO_TEST_CASE(for_statement_break_nested_body_in_post)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse("{ for {} 1 {let x for {} x {} { break }} {} }", dialect));
}
BOOST_AUTO_TEST_CASE(function_defined_in_init_block)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse("{ for { } 1 { function f() {} } {} }", dialect));
BOOST_CHECK(successParse("{ for { } 1 {} { function f() {} } }", dialect));
CHECK_ERROR_DIALECT(
"{ for { function f() {} } 1 {} {} }",
SyntaxError,
"Functions cannot be defined inside a for-loop init block.",
dialect
);
}
BOOST_AUTO_TEST_CASE(function_defined_in_init_nested)
{
auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion{});
BOOST_CHECK(successParse(
"{ for {"
"for { } 1 { function f() {} } {}"
"} 1 {} {} }", dialect));
CHECK_ERROR_DIALECT(
"{ for { for {function foo() {}} 1 {} {} } 1 {} {} }",
SyntaxError,
"Functions cannot be defined inside a for-loop init block.",
dialect
);
CHECK_ERROR_DIALECT(
"{ for {} 1 {for {function foo() {}} 1 {} {} } {} }",
SyntaxError,
"Functions cannot be defined inside a for-loop init block.",
dialect
);
} }
BOOST_AUTO_TEST_CASE(if_statement_invalid) BOOST_AUTO_TEST_CASE(if_statement_invalid)