Merge pull request #7765 from ethereum/fix-7626

yul proto fuzzer: permit variable declarations inside for-init block
This commit is contained in:
chriseth 2020-01-15 11:21:23 +01:00 committed by GitHub
commit 9d9a7ebe25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 274 additions and 60 deletions

View File

@ -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<string> varsVec = createVarDecls(
startIdx,
startIdx + numOutParams,
/*isAssignment=*/true
);
// Obtain variable name suffix
unsigned startIdx = counter();
vector<string> 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<string>{});
if (m_inForInitScope && m_forInitScopeExtEnabled)
{
yulAssert(
!m_funcForLoopInitVars.empty(),
"Proto fuzzer: Invalid data structure"
);
m_funcForLoopInitVars.back().push_back(vector<string>{});
}
}
else
m_globalVars.push_back(vector<string>{});
{
m_globalVars.push_back({});
if (m_inForInitScope && m_forInitScopeExtEnabled)
m_globalForLoopInitVars.push_back(vector<string>{});
}
}
void ProtoConverter::openFunctionScope(vector<string> const& _funcParams)
{
m_funcVars.push_back(vector<vector<string>>({_funcParams}));
m_funcForLoopInitVars.push_back(vector<vector<string>>({}));
}
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<string> 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

View File

@ -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<std::string const*> m_currentGlobalVars;
/// Functions in current scope
std::vector<std::vector<std::string>> m_scopeFuncs;
/// Variables
/// Global variables
std::vector<std::vector<std::string>> m_globalVars;
/// Functions
/// Variables declared in for loop init block that is in global scope
std::vector<std::vector<std::string>> m_globalForLoopInitVars;
/// Variables declared in for loop init block that is in function scope
std::vector<std::vector<std::vector<std::string>>> m_funcForLoopInitVars;
/// Vector of function names
std::vector<std::string> m_functions;
/// Maps FunctionDef object to its name
std::map<FunctionDef const*, std::string> 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;
};
}