From 49a05b59356622196f63c87fde0e1c2c31a4b11d Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 18 Nov 2019 12:12:30 +0100 Subject: [PATCH] Permit variable declarations inside for init block --- test/tools/ossfuzz/protoToYul.cpp | 320 ++++++++++++++++++++++++------ test/tools/ossfuzz/protoToYul.h | 14 +- 2 files changed, 274 insertions(+), 60 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index bf152052b..cc7fde3ae 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -95,18 +95,32 @@ string ProtoConverter::visit(Literal const& _x) void ProtoConverter::consolidateVarDeclsInFunctionDef() { m_currentFuncVars.clear(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid operation"); + auto const& scopes = m_funcVars.back(); for (auto const& s: scopes) for (auto const& var: s) m_currentFuncVars.push_back(&var); + yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid operation"); + auto const& forinitscopes = m_funcForLoopInitVars.back(); + for (auto const& s: forinitscopes) + for (auto const& var: s) + m_currentFuncVars.push_back(&var); } void ProtoConverter::consolidateGlobalVarDecls() { m_currentGlobalVars.clear(); + // Place pointers to all global variables that are in scope + // into a single vector for (auto const& scope: m_globalVars) for (auto const& var: scope) m_currentGlobalVars.push_back(&var); + // Place pointers to all variables declared in for-init blocks + // that are still live into the same vector + for (auto const& init: m_globalForLoopInitVars) + for (auto const& var: init) + m_currentGlobalVars.push_back(&var); } bool ProtoConverter::varDeclAvailable() @@ -283,10 +297,54 @@ void ProtoConverter::visit(VarDecl const& _x) m_output << "let " << varName << " := "; visit(_x.expr()); m_output << "\n"; + // If we are inside a for-init block, there are two places + // where the visited vardecl may have been defined: + // - directly inside the for-init block + // - inside a block within the for-init block + // In the latter case, we don't scope extend. if (m_inFunctionDef) - m_funcVars.back().back().push_back(varName); + { + // Variables declared directly in for-init block + // are tracked separately because their scope + // extends beyond the block they are defined in + // to the rest of the for-loop statement. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcForLoopInitVars.back().back().push_back(varName); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcVars.back().back().push_back(varName); + } + + } else - m_globalVars.back().push_back(varName); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalForLoopInitVars.back().push_back(varName); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalVars.back().push_back(varName); + } + } } void ProtoConverter::visit(TypedVarDecl const& _x) @@ -351,10 +409,53 @@ void ProtoConverter::visit(TypedVarDecl const& _x) m_output << " : u256\n"; break; } + // If we are inside a for-init block, there are two places + // where the visited vardecl may have been defined: + // - directly inside the for-init block + // - inside a block within the for-init block + // In the latter case, we don't scope extend. if (m_inFunctionDef) - m_funcVars.back().back().push_back(varName); + { + // Variables declared directly in for-init block + // are tracked separately because their scope + // extends beyond the block they are defined in + // to the rest of the for-loop statement. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcForLoopInitVars.back().back().push_back(varName); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcVars.back().back().push_back(varName); + } + } else - m_globalVars.back().push_back(varName); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalForLoopInitVars.back().push_back(varName); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalVars.back().push_back(varName); + } + } } void ProtoConverter::visit(UnaryOp const& _x) @@ -713,30 +814,27 @@ void ProtoConverter::visit(FunctionCall const& _x) convertFunctionCall(_x, funcName, numInParams, /*newLine=*/false); break; case FunctionCall::MULTIDECL: - // Hack: Disallow (multi) variable declarations until scope extension - // is implemented for "for-init" - if (!m_inForInitScope) - { - // Ensure that the chosen function returns at most 4 values - yulAssert( - numOutParams <= 4, - "Proto fuzzer: Function call with too many output params encountered." - ); + { + // Ensure that the chosen function returns at most 4 values + yulAssert( + numOutParams <= 4, + "Proto fuzzer: Function call with too many output params encountered." + ); - // Obtain variable name suffix - unsigned startIdx = counter(); - vector varsVec = createVarDecls( - startIdx, - startIdx + numOutParams, - /*isAssignment=*/true - ); + // Obtain variable name suffix + unsigned startIdx = counter(); + vector varsVec = createVarDecls( + startIdx, + startIdx + numOutParams, + /*isAssignment=*/true + ); - // Create RHS of multi var decl - convertFunctionCall(_x, funcName, numInParams); - // Add newly minted vars in the multidecl statement to current scope - addVarsToScope(varsVec); - } + // Create RHS of multi var decl + convertFunctionCall(_x, funcName, numInParams); + // Add newly minted vars in the multidecl statement to current scope + addVarsToScope(varsVec); break; + } case FunctionCall::MULTIASSIGN: // Ensure that the chosen function returns at most 4 values yulAssert( @@ -749,22 +847,22 @@ void ProtoConverter::visit(FunctionCall const& _x) // This helps reduce the size of this switch statement. switch (numOutParams) { - case 4: - visit(_x.out_param4()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 3: - visit(_x.out_param3()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 2: - visit(_x.out_param2()); - m_output << ", "; - visit(_x.out_param1()); - break; - default: - yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); - break; + case 4: + visit(_x.out_param4()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 3: + visit(_x.out_param3()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 2: + visit(_x.out_param2()); + m_output << ", "; + visit(_x.out_param1()); + break; + default: + yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); + break; } m_output << " := "; @@ -868,17 +966,33 @@ void ProtoConverter::visit(ForStmt const& _x) { bool wasInForBody = m_inForBodyScope; bool wasInForInit = m_inForInitScope; + bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled; m_inForBodyScope = false; m_inForInitScope = true; + m_forInitScopeExtEnabled = true; m_output << "for "; visit(_x.for_init()); m_inForInitScope = false; + m_forInitScopeExtEnabled = wasForInitScopeExtEnabled; visit(_x.for_cond()); visit(_x.for_post()); m_inForBodyScope = true; visit(_x.for_body()); m_inForBodyScope = wasInForBody; m_inForInitScope = wasInForInit; + if (m_inFunctionDef) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid data structure"); + // Remove variables in for-init + m_funcForLoopInitVars.back().pop_back(); + } + else + { + yulAssert(!m_globalForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure"); + m_globalForLoopInitVars.pop_back(); + } } void ProtoConverter::visit(BoundedForStmt const& _x) @@ -1065,9 +1179,7 @@ void ProtoConverter::visit(Statement const& _x) switch (_x.stmt_oneof_case()) { case Statement::kDecl: - // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" - if (!m_inForInitScope) - visit(_x.decl()); + visit(_x.decl()); break; case Statement::kAssignment: // Create an assignment statement only if there is at least one variable @@ -1138,16 +1250,36 @@ void ProtoConverter::visit(Statement const& _x) void ProtoConverter::openBlockScope() { m_scopeFuncs.push_back({}); + // Create new block scope inside current function scope if (m_inFunctionDef) + { + yulAssert( + !m_funcVars.empty(), + "Proto fuzzer: Invalid data structure" + ); m_funcVars.back().push_back(vector{}); + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcForLoopInitVars.back().push_back(vector{}); + } + } else - m_globalVars.push_back(vector{}); + { + m_globalVars.push_back({}); + if (m_inForInitScope && m_forInitScopeExtEnabled) + m_globalForLoopInitVars.push_back(vector{}); + } } void ProtoConverter::openFunctionScope(vector const& _funcParams) { m_funcVars.push_back(vector>({_funcParams})); + m_funcForLoopInitVars.push_back(vector>({})); } void ProtoConverter::updateFunctionMaps(string const& _var) @@ -1166,6 +1298,8 @@ void ProtoConverter::updateFunctionMaps(string const& _var) void ProtoConverter::closeBlockScope() { + // Remove functions declared in the block that is going + // out of scope from the global function map. for (auto const& f: m_scopeFuncs.back()) { unsigned numFuncsRemoved = m_functions.size(); @@ -1177,35 +1311,95 @@ void ProtoConverter::closeBlockScope() ); updateFunctionMaps(f); } + // Pop back the vector of scoped functions. if (!m_scopeFuncs.empty()) m_scopeFuncs.pop_back(); - if (!m_inFunctionDef) + + // If block belongs to function body, then remove + // local variables in function body that are going out of scope. + if (m_inFunctionDef) { - if (!m_globalVars.empty()) - m_globalVars.pop_back(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure"); + if (!m_funcVars.back().empty()) + m_funcVars.back().pop_back(); } + // Remove variables declared in vanilla block from current + // global scope. else { - // Variables that have been declared in a - // function block, go out of scope - if (!m_funcVars.empty()) - if (!m_funcVars.back().empty()) - m_funcVars.back().pop_back(); + yulAssert(!m_globalVars.empty(), "Proto fuzzer: Invalid data structure"); + m_globalVars.pop_back(); } } void ProtoConverter::closeFunctionScope() { - if (!m_funcVars.empty()) - m_funcVars.pop_back(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure"); + m_funcVars.pop_back(); + yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure"); + m_funcForLoopInitVars.pop_back(); } void ProtoConverter::addVarsToScope(vector const& _vars) { + // If we are in function definition, add the new vars to current function scope if (m_inFunctionDef) - m_funcVars.back().back().insert(m_funcVars.back().back().end(), _vars.begin(), _vars.end()); + { + // If we are directly in for-init block, add the newly created vars to the + // stack of for-init variables. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcForLoopInitVars.back().back().insert( + m_funcForLoopInitVars.back().back().end(), + _vars.begin(), + _vars.end() + ); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcVars.back().back().insert( + m_funcVars.back().back().end(), + _vars.begin(), + _vars.end() + ); + } + } + // If we are in a vanilla block, add the new vars to current global scope else - m_globalVars.back().insert(m_globalVars.back().end(), _vars.begin(), _vars.end()); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_globalForLoopInitVars.back().insert( + m_globalForLoopInitVars.back().end(), + _vars.begin(), + _vars.end() + ); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_globalVars.back().insert( + m_globalVars.back().end(), + _vars.begin(), + _vars.end() + ); + } + } } void ProtoConverter::visit(Block const& _x) @@ -1222,8 +1416,20 @@ void ProtoConverter::visit(Block const& _x) if (_x.statements_size() > 0) { m_output << "{\n"; + bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled; for (auto const& st: _x.statements()) + { + // If statement is block or introduces one and we are in for-init block + // then temporarily disable scope extension if it is not already disabled. + if ( + (st.has_blockstmt() || st.has_switchstmt() || st.has_ifstmt()) && + m_inForInitScope && + m_forInitScopeExtEnabled + ) + m_forInitScopeExtEnabled = false; visit(st); + m_forInitScopeExtEnabled = wasForInitScopeExtEnabled; + } m_output << "}\n"; } else diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 43058f45c..4150e7c64 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -48,6 +48,7 @@ public: m_inFunctionDef = false; m_objectId = 0; m_isObject = false; + m_forInitScopeExtEnabled = true; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; @@ -314,9 +315,13 @@ private: std::vector m_currentGlobalVars; /// Functions in current scope std::vector> m_scopeFuncs; - /// Variables + /// Global variables std::vector> m_globalVars; - /// Functions + /// Variables declared in for loop init block that is in global scope + std::vector> m_globalForLoopInitVars; + /// Variables declared in for loop init block that is in function scope + std::vector>> m_funcForLoopInitVars; + /// Vector of function names std::vector m_functions; /// Maps FunctionDef object to its name std::map m_functionDefMap; @@ -331,7 +336,7 @@ private: static unsigned constexpr s_modOutputParams = 5; /// Hard-coded identifier for a Yul object's data block static auto constexpr s_dataIdentifier = "datablock"; - /// Predicate to keep track of for body scope. If true, break/continue + /// Predicate to keep track of for body scope. If false, break/continue /// statements can not be created. bool m_inForBodyScope; // Index used for naming loop variable of bounded for loops @@ -350,5 +355,8 @@ private: /// Flag to track whether program is an object (true) or a statement block /// (false: default value) bool m_isObject; + /// Flag to track whether scope extension of variables defined in for-init + /// block is enabled. + bool m_forInitScopeExtEnabled; }; }