diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 88ca727ce..0ece09f16 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -94,6 +94,12 @@ public: (*this)(_for.body); (*this)(_for.post); } + void operator()(yul::Break const&) + { + } + void operator()(yul::Continue const&) + { + } void operator()(yul::Block const& _block) { for (auto const& s: _block.statements) diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index abf2e3d1d..77c98d616 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -33,6 +34,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -472,7 +474,7 @@ bool AsmAnalyzer::operator()(ForLoop const& _for) { solAssert(_for.condition, ""); - Scope* originalScope = m_currentScope; + Scope* outerScope = m_currentScope; bool success = true; if (!(*this)(_for.pre)) @@ -485,18 +487,37 @@ bool AsmAnalyzer::operator()(ForLoop const& _for) if (!expectExpression(*_for.condition)) success = false; m_stackHeight--; + + // backup outer for-loop & create new state + auto outerForLoop = m_currentForLoop; + m_currentForLoop = &_for; + if (!(*this)(_for.body)) success = false; + if (!(*this)(_for.post)) success = false; m_stackHeight -= scope(&_for.pre).numberOfVariables(); m_info.stackHeightInfo[&_for] = m_stackHeight; - m_currentScope = originalScope; + m_currentScope = outerScope; + m_currentForLoop = outerForLoop; return success; } +bool AsmAnalyzer::operator()(Break const& _break) +{ + m_info.stackHeightInfo[&_break] = m_stackHeight; + return true; +} + +bool AsmAnalyzer::operator()(Continue const& _continue) +{ + m_info.stackHeightInfo[&_continue] = m_stackHeight; + return true; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 7d11391f9..d25b3340a 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -34,6 +34,7 @@ #include #include +#include #include namespace langutil @@ -93,6 +94,8 @@ public: bool operator()(If const& _if); bool operator()(Switch const& _switch); bool operator()(ForLoop const& _forLoop); + bool operator()(Break const&); + bool operator()(Continue const&); bool operator()(Block const& _block); private: @@ -124,6 +127,7 @@ private: langutil::EVMVersion m_evmVersion; std::shared_ptr m_dialect; boost::optional m_errorTypeForLoose; + ForLoop const* m_currentForLoop = nullptr; }; } diff --git a/libyul/AsmData.h b/libyul/AsmData.h index fd8eab38b..3b1eaaf8b 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -78,6 +78,10 @@ struct Case { langutil::SourceLocation location; std::unique_ptr value; /// Switch statement struct Switch { langutil::SourceLocation location; std::unique_ptr expression; std::vector cases; }; struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr condition; Block post; Block body; }; +/// Break statement (valid within for loop) +struct Break { langutil::SourceLocation location; }; +/// Continue statement (valid within for loop) +struct Continue { langutil::SourceLocation location; }; struct LocationExtractor: boost::static_visitor { diff --git a/libyul/AsmDataForward.h b/libyul/AsmDataForward.h index de5644252..d01b1dffa 100644 --- a/libyul/AsmDataForward.h +++ b/libyul/AsmDataForward.h @@ -41,12 +41,14 @@ struct If; struct Switch; struct Case; struct ForLoop; +struct Break; +struct Continue; struct ExpressionStatement; struct Block; struct TypedName; using Expression = boost::variant; -using Statement = boost::variant; +using Statement = boost::variant; } diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 0adab10b6..320da666b 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -104,6 +105,32 @@ Statement Parser::parseStatement() } case Token::For: return parseForLoop(); + case Token::Break: + if (m_insideForLoopBody) + { + auto stmt = Statement{ createWithLocation() }; + m_scanner->next(); + return stmt; + } + else + { + m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed."); + m_scanner->next(); + return {}; + } + case Token::Continue: + if (m_insideForLoopBody) + { + auto stmt = Statement{ createWithLocation() }; + m_scanner->next(); + return stmt; + } + else + { + m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed."); + m_scanner->next(); + return {}; + } case Token::Assign: { if (m_dialect->flavour != AsmFlavour::Loose) @@ -243,13 +270,19 @@ Case Parser::parseCase() ForLoop Parser::parseForLoop() { + bool outerForLoopBody = m_insideForLoopBody; + m_insideForLoopBody = false; + RecursionGuard recursionGuard(*this); ForLoop forLoop = createWithLocation(); expectToken(Token::For); forLoop.pre = parseBlock(); forLoop.condition = make_unique(parseExpression()); forLoop.post = parseBlock(); + + m_insideForLoopBody = true; forLoop.body = parseBlock(); + m_insideForLoopBody = outerForLoopBody; forLoop.location.end = forLoop.body.location.end; return forLoop; } @@ -455,6 +488,9 @@ VariableDeclaration Parser::parseVariableDeclaration() FunctionDefinition Parser::parseFunctionDefinition() { RecursionGuard recursionGuard(*this); + auto outerForLoopBody = m_insideForLoopBody; + m_insideForLoopBody = false; + ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }}; FunctionDefinition funDef = createWithLocation(); expectToken(Token::Function); funDef.name = expectAsmIdentifier(); diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index a02f0b4db..098e237b9 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -39,7 +39,7 @@ class Parser: public langutil::ParserBase { public: explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr _dialect): - ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {} + ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @param _reuseScanner if true, do check for end of input after the `}`. @@ -87,6 +87,7 @@ protected: private: std::shared_ptr m_dialect; + bool m_insideForLoopBody; }; } diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index b7af47784..fd360bc32 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -224,6 +224,16 @@ string AsmPrinter::operator()(ForLoop const& _forLoop) const return out; } +string AsmPrinter::operator()(Break const&) const +{ + return "break"; +} + +string AsmPrinter::operator()(Continue const&) const +{ + return "continue"; +} + string AsmPrinter::operator()(Block const& _block) const { if (_block.statements.empty()) diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index a1b9d6cd0..82fed24f2 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -50,6 +50,8 @@ public: std::string operator()(If const& _if) const; std::string operator()(Switch const& _switch) const; std::string operator()(ForLoop const& _forLoop) const; + std::string operator()(Break const& _break) const; + std::string operator()(Continue const& _continue) const; std::string operator()(Block const& _block) const; private: diff --git a/libyul/AsmScopeFiller.h b/libyul/AsmScopeFiller.h index e8fb88d5f..7c8834637 100644 --- a/libyul/AsmScopeFiller.h +++ b/libyul/AsmScopeFiller.h @@ -63,6 +63,8 @@ public: bool operator()(If const& _if); bool operator()(Switch const& _switch); bool operator()(ForLoop const& _forLoop); + bool operator()(Break const&) { return true; } + bool operator()(Continue const&) { return true; } bool operator()(Block const& _block); private: diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 11c6ed3fc..78c5e7d63 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -69,7 +69,6 @@ void VariableReferenceCounter::operator()(ForLoop const& _forLoop) m_scope = originalScope; } - void VariableReferenceCounter::operator()(Block const& _block) { Scope* originalScope = m_scope; @@ -92,7 +91,6 @@ void VariableReferenceCounter::increaseRefIfFound(YulString _variableName) )); } - CodeTransform::CodeTransform( AbstractAssembly& _assembly, AsmAnalysisInfo& _analysisInfo, @@ -605,11 +603,9 @@ void CodeTransform::operator()(ForLoop const& _forLoop) visitStatements(_forLoop.pre.statements); - // TODO: When we implement break and continue, the labels and the stack heights at that point - // have to be stored in a stack. AbstractAssembly::LabelID loopStart = m_assembly.newLabelId(); - AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); AbstractAssembly::LabelID postPart = m_assembly.newLabelId(); + AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); m_assembly.setSourceLocation(_forLoop.location); m_assembly.appendLabel(loopStart); @@ -619,6 +615,8 @@ void CodeTransform::operator()(ForLoop const& _forLoop) m_assembly.appendInstruction(solidity::Instruction::ISZERO); m_assembly.appendJumpToIf(loopEnd); + int const stackHeightBody = m_assembly.stackHeight(); + m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} }); (*this)(_forLoop.body); m_assembly.setSourceLocation(_forLoop.location); @@ -631,7 +629,19 @@ void CodeTransform::operator()(ForLoop const& _forLoop) m_assembly.appendLabel(loopEnd); finalizeBlock(_forLoop.pre, stackStartHeight); + m_context->forLoopStack.pop(); m_scope = originalScope; + checkStackHeight(&_forLoop); +} + +void CodeTransform::operator()(Break const&) +{ + yulAssert(false, "Code generation for break statement in Yul is not implemented yet."); +} + +void CodeTransform::operator()(Continue const&) +{ + yulAssert(false, "Code generation for continue statement in Yul is not implemented yet."); } void CodeTransform::operator()(Block const& _block) diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index 3ebf0f490..141dd91ef 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -30,6 +30,8 @@ #include #include +#include + namespace langutil { class ErrorReporter; @@ -57,6 +59,20 @@ struct CodeTransformContext std::map functionEntryIDs; std::map variableStackHeights; std::map variableReferences; + + struct JumpInfo + { + AbstractAssembly::LabelID label; ///< Jump's LabelID to jump to. + int targetStackHeight; ///< Stack height after the jump. + }; + + struct ForLoopLabels + { + JumpInfo post; ///< Jump info for jumping to post branch. + JumpInfo done; ///< Jump info for jumping to done branch. + }; + + std::stack forLoopStack; }; /** @@ -166,6 +182,8 @@ public: void operator()(Switch const& _switch); void operator()(FunctionDefinition const&); void operator()(ForLoop const&); + void operator()(Break const&); + void operator()(Continue const&); void operator()(Block const& _block); private: diff --git a/libyul/optimiser/ASTCopier.cpp b/libyul/optimiser/ASTCopier.cpp index f18b0e6bb..f98cb5653 100644 --- a/libyul/optimiser/ASTCopier.cpp +++ b/libyul/optimiser/ASTCopier.cpp @@ -138,6 +138,15 @@ Statement ASTCopier::operator()(ForLoop const& _forLoop) translate(_forLoop.body) }; } +Statement ASTCopier::operator()(Break const& _break) +{ + return Break{ _break }; +} + +Statement ASTCopier::operator()(Continue const& _continue) +{ + return Continue{ _continue }; +} Statement ASTCopier::operator ()(Block const& _block) { diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index 7e5e9c868..a408b43fb 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -58,6 +58,8 @@ public: virtual Statement operator()(Switch const& _switch) = 0; virtual Statement operator()(FunctionDefinition const&) = 0; virtual Statement operator()(ForLoop const&) = 0; + virtual Statement operator()(Break const&) = 0; + virtual Statement operator()(Continue const&) = 0; virtual Statement operator()(Block const& _block) = 0; }; @@ -83,6 +85,8 @@ public: Statement operator()(Switch const& _switch) override; Statement operator()(FunctionDefinition const&) override; Statement operator()(ForLoop const&) override; + Statement operator()(Break const&) override; + Statement operator()(Continue const&) override; Statement operator()(Block const& _block) override; virtual Expression translate(Expression const& _expression); diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index 6adcb2e15..fe08c4313 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -161,6 +161,14 @@ void ASTModifier::operator()(ForLoop& _for) (*this)(_for.body); } +void ASTModifier::operator()(Break&) +{ +} + +void ASTModifier::operator()(Continue&) +{ +} + void ASTModifier::operator()(Block& _block) { walkVector(_block.statements); diff --git a/libyul/optimiser/ASTWalker.h b/libyul/optimiser/ASTWalker.h index 272725f9f..a1f244795 100644 --- a/libyul/optimiser/ASTWalker.h +++ b/libyul/optimiser/ASTWalker.h @@ -56,6 +56,8 @@ public: virtual void operator()(Switch const& _switch); virtual void operator()(FunctionDefinition const&); virtual void operator()(ForLoop const&); + virtual void operator()(Break const&) {} + virtual void operator()(Continue const&) {} virtual void operator()(Block const& _block); virtual void visit(Statement const& _st); @@ -91,6 +93,8 @@ public: virtual void operator()(Switch& _switch); virtual void operator()(FunctionDefinition&); virtual void operator()(ForLoop&); + virtual void operator()(Break&); + virtual void operator()(Continue&); virtual void operator()(Block& _block); virtual void visit(Statement& _st); diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index b4f2d1f96..c34384024 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -130,6 +130,16 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) popScope(); } +void DataFlowAnalyzer::operator()(Break&) +{ + yulAssert(false, "Not implemented yet."); +} + +void DataFlowAnalyzer::operator()(Continue&) +{ + yulAssert(false, "Not implemented yet."); +} + void DataFlowAnalyzer::operator()(Block& _block) { size_t numScopes = m_variableScopes.size(); diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 5fb5db958..7a569513e 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -53,6 +53,8 @@ public: void operator()(Switch& _switch) override; void operator()(FunctionDefinition&) override; void operator()(ForLoop&) override; + void operator()(Break& _continue) override; + void operator()(Continue& _continue) override; void operator()(Block& _block) override; protected: diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index eb79be713..36f63848f 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -149,6 +149,16 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) join(zeroRuns); } +void RedundantAssignEliminator::operator()(Break const&) +{ + yulAssert(false, "Not implemented yet."); +} + +void RedundantAssignEliminator::operator()(Continue const&) +{ + yulAssert(false, "Not implemented yet."); +} + void RedundantAssignEliminator::operator()(Block const& _block) { // This will set all variables that are declared in this diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index 2f4dd380f..e35c0143e 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -112,6 +112,8 @@ public: void operator()(Switch const& _switch) override; void operator()(FunctionDefinition const&) override; void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; void operator()(Block const& _block) override; static void run(Dialect const& _dialect, Block& _ast); diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index af240717b..c5e722c74 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -55,6 +55,8 @@ public: bool statementEqual(Switch const& _lhs, Switch const& _rhs); bool switchCaseEqual(Case const& _lhs, Case const& _rhs); bool statementEqual(ForLoop const& _lhs, ForLoop const& _rhs); + bool statementEqual(Break const&, Break const&) { return true; } + bool statementEqual(Continue const&, Continue const&) { return true; } bool statementEqual(Block const& _lhs, Block const& _rhs); private: bool statementEqual(Instruction const& _lhs, Instruction const& _rhs); diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index fa8e593d5..9d24ecd91 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -295,6 +295,74 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); } +BOOST_AUTO_TEST_CASE(for_statement) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {break} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0 break} iszero(eq(i, 10)) {i := add(i, 1)} {} }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_break_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) break} {} }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_nested_break) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {} { function f() { break } } }", + SyntaxError, + "Keyword break outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + BOOST_CHECK(successParse("{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1)} {continue} }", dialect)); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_fail_init) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0 continue} iszero(eq(i, 10)) {i := add(i, 1)} {} }", + SyntaxError, + "Keyword continue outside for-loop body is not allowed.", + dialect); +} + +BOOST_AUTO_TEST_CASE(for_statement_continue_fail_post) +{ + auto dialect = EVMDialect::strictAssemblyForEVMObjects(EVMVersion::constantinople()); + CHECK_ERROR_DIALECT( + "{ for {let i := 0} iszero(eq(i, 10)) {i := add(i, 1) continue} {} }", + SyntaxError, + "Keyword continue outside for-loop body is not allowed.", + dialect); +} + BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 0731d38a2..e9b4dbd94 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -105,12 +105,27 @@ void Interpreter::operator()(ForLoop const& _forLoop) visit(statement); while (evaluate(*_forLoop.condition) != 0) { + m_state.loopState = LoopState::Default; (*this)(_forLoop.body); + if (m_state.loopState == LoopState::Break) + break; + (*this)(_forLoop.post); } + m_state.loopState = LoopState::Default; closeScope(); } +void Interpreter::operator()(Break const&) +{ + m_state.loopState = LoopState::Break; +} + +void Interpreter::operator()(Continue const&) +{ + m_state.loopState = LoopState::Continue; +} + void Interpreter::operator()(Block const& _block) { openScope(); @@ -122,7 +137,14 @@ void Interpreter::operator()(Block const& _block) m_functions[funDef.name] = &funDef; m_scopes.back().insert(funDef.name); } - ASTWalker::operator()(_block); + + for (auto const& statement: _block.statements) + { + visit(statement); + if (m_state.loopState != LoopState::Default) + break; + } + closeScope(); } diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 267fe0a4d..06cfe939d 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -39,6 +39,13 @@ class InterpreterTerminated: dev::Exception { }; +enum class LoopState +{ + Default, + Continue, + Break, +}; + struct InterpreterState { dev::bytes calldata; @@ -65,6 +72,7 @@ struct InterpreterState std::vector trace; /// This is actually an input parameter that more or less limits the runtime. size_t maxTraceSize = 0; + LoopState loopState = LoopState::Default; }; /** @@ -90,6 +98,8 @@ public: void operator()(Switch const& _switch) override; void operator()(FunctionDefinition const&) override; void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; void operator()(Block const& _block) override; std::vector const& trace() const { return m_state.trace; }