Add multi variable decl statement; use dummy expressions instead of dictionary tokens alone; dont visit blocks unless they contain at least one statement

This commit is contained in:
Bhargava Shastry 2020-01-13 07:00:44 +01:00
parent 31a5d3077e
commit 8aa7abaeda
3 changed files with 159 additions and 24 deletions

View File

@ -34,6 +34,41 @@ using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity;
string ProtoConverter::dummyExpression()
{
string expression{};
string location{};
unsigned pseudoRandomNum = m_inputSize / 13;
if (varDeclAvailable())
location = varRef(pseudoRandomNum);
switch (pseudoRandomNum % 4)
{
case 0:
if (location.empty())
expression = "mload(0)";
else
expression = Whiskers(R"(mload(<loc>))")("loc", location).render();
break;
case 1:
if (location.empty())
expression = "sload(0)";
else
expression = Whiskers(R"(sload(<loc>))")("loc", location).render();
break;
case 2:
if (location.empty())
expression = "calldataload(0)";
else
expression = Whiskers(R"(calldataload(<loc>))")("loc", location).render();
break;
case 3:
expression = dictionaryToken();
break;
}
yulAssert(!expression.empty(), "Proto fuzzer: Invalid dummy expression");
return expression;
}
string ProtoConverter::dictionaryToken(HexPrefix _p)
{
std::string token;
@ -176,22 +211,27 @@ bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type)
(_type == FunctionCall::MULTIASSIGN && !varDeclAvailable());
}
void ProtoConverter::visit(VarRef const& _x)
string ProtoConverter::varRef(unsigned _index)
{
if (m_inFunctionDef)
{
// Ensure that there is at least one variable declaration to reference in function scope.
yulAssert(m_currentFuncVars.size() > 0, "Proto fuzzer: No variables to reference.");
m_output << *m_currentFuncVars[_x.varnum() % m_currentFuncVars.size()];
return *m_currentFuncVars[_index % m_currentFuncVars.size()];
}
else
{
// Ensure that there is at least one variable declaration to reference in nested scopes.
yulAssert(m_currentGlobalVars.size() > 0, "Proto fuzzer: No global variables to reference.");
m_output << *m_currentGlobalVars[_x.varnum() % m_currentGlobalVars.size()];
return *m_currentGlobalVars[_index % m_currentGlobalVars.size()];
}
}
void ProtoConverter::visit(VarRef const& _x)
{
m_output << varRef(_x.varnum());
}
void ProtoConverter::visit(Expression const& _x)
{
switch (_x.expr_oneof_case())
@ -201,7 +241,7 @@ void ProtoConverter::visit(Expression const& _x)
// (because there are no variables in scope), we silently output a literal
// expression from the optimizer dictionary.
if (!varDeclAvailable())
m_output << dictionaryToken();
m_output << dummyExpression();
else
visit(_x.varref());
break;
@ -232,7 +272,7 @@ void ProtoConverter::visit(Expression const& _x)
if (_x.func_expr().ret() == FunctionCall::SINGLE)
visit(_x.func_expr());
else
m_output << dictionaryToken();
m_output << dummyExpression();
break;
case Expression::kLowcall:
visit(_x.lowcall());
@ -244,10 +284,10 @@ void ProtoConverter::visit(Expression const& _x)
if (m_isObject)
visit(_x.unopdata());
else
m_output << dictionaryToken();
m_output << dummyExpression();
break;
case Expression::EXPR_ONEOF_NOT_SET:
m_output << dictionaryToken();
m_output << dummyExpression();
break;
}
}
@ -259,7 +299,7 @@ void ProtoConverter::visit(BinaryOp const& _x)
if ((op == BinaryOp::SHL || op == BinaryOp::SHR || op == BinaryOp::SAR) &&
!m_evmVersion.hasBitwiseShifting())
{
m_output << dictionaryToken();
m_output << dummyExpression();
return;
}
@ -398,6 +438,81 @@ void ProtoConverter::visit(VarDecl const& _x)
}
}
void ProtoConverter::visit(MultiVarDecl const& _x)
{
m_output << "let ";
vector<string> varNames;
// We support up to 4 variables in a single
// declaration statement.
unsigned numVars = _x.num_vars() % 3 + 2;
string delimiter = "";
for (unsigned i = 0; i < numVars; i++)
{
string varName = newVarName();
varNames.push_back(varName);
m_output << delimiter << varName;
if (i == 0)
delimiter = ", ";
}
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)
{
// 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"
);
for (auto const& varName: varNames)
m_funcForLoopInitVars.back().back().push_back(varName);
}
else
{
yulAssert(
!m_funcVars.empty() && !m_funcVars.back().empty(),
"Proto fuzzer: Invalid operation"
);
for (auto const& varName: varNames)
m_funcVars.back().back().push_back(varName);
}
}
else
{
if (m_inForInitScope && m_forInitScopeExtEnabled)
{
yulAssert(
!m_globalForLoopInitVars.empty(),
"Proto fuzzer: Invalid operation"
);
for (auto const& varName: varNames)
m_globalForLoopInitVars.back().push_back(varName);
}
else
{
yulAssert(
!m_globalVars.empty(),
"Proto fuzzer: Invalid operation"
);
for (auto const& varName: varNames)
m_globalVars.back().push_back(varName);
}
}
}
void ProtoConverter::visit(TypedVarDecl const& _x)
{
string varName = newVarName();
@ -517,7 +632,7 @@ void ProtoConverter::visit(UnaryOp const& _x)
// token.
if (op == UnaryOp::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
{
m_output << dictionaryToken();
m_output << dummyExpression();
return;
}
@ -601,7 +716,7 @@ void ProtoConverter::visit(NullaryOp const& _x)
if (m_evmVersion.supportsReturndata())
m_output << "returndatasize()";
else
m_output << dictionaryToken();
m_output << dummyExpression();
break;
case NullaryOp::ADDRESS:
m_output << "address()";
@ -639,7 +754,7 @@ void ProtoConverter::visit(NullaryOp const& _x)
if (m_evmVersion.hasSelfBalance())
m_output << "selfbalance()";
else
m_output << dictionaryToken();
m_output << dummyExpression();
break;
case NullaryOp::CHAINID:
// Replace calls to chainid() on unsupported EVMs with a dictionary
@ -647,7 +762,7 @@ void ProtoConverter::visit(NullaryOp const& _x)
if (m_evmVersion.hasChainID())
m_output << "chainid()";
else
m_output << dictionaryToken();
m_output << dummyExpression();
break;
}
}
@ -866,7 +981,7 @@ void ProtoConverter::visit(FunctionCall const& _x)
// If there are no functions available, calls to functions that
// return a single value may be replaced by a dictionary token.
if (funcType == FunctionCall::SINGLE)
m_output << dictionaryToken();
m_output << dummyExpression();
return;
}
@ -878,7 +993,7 @@ void ProtoConverter::visit(FunctionCall const& _x)
if (!functionValid(funcType, numOutParams))
{
if (funcType == FunctionCall::SINGLE)
m_output << dictionaryToken();
m_output << dummyExpression();
return;
}
@ -1010,7 +1125,7 @@ void ProtoConverter::visit(Create const& _x)
// token.
if (type == Create::CREATE2 && !m_evmVersion.hasCreate2())
{
m_output << dictionaryToken();
m_output << dummyExpression();
return;
}
@ -1306,18 +1421,22 @@ void ProtoConverter::visit(Statement const& _x)
visit(_x.assignment());
break;
case Statement::kIfstmt:
if (_x.ifstmt().if_body().statements_size() > 0)
visit(_x.ifstmt());
break;
case Statement::kStorageFunc:
visit(_x.storage_func());
break;
case Statement::kBlockstmt:
if (_x.blockstmt().statements_size() > 0)
visit(_x.blockstmt());
break;
case Statement::kForstmt:
if (_x.forstmt().for_body().statements_size() > 0)
visit(_x.forstmt());
break;
case Statement::kBoundedforstmt:
if (_x.boundedforstmt().for_body().statements_size() > 0)
visit(_x.boundedforstmt());
break;
case Statement::kSwitchstmt:
@ -1350,6 +1469,7 @@ void ProtoConverter::visit(Statement const& _x)
visit(_x.functioncall());
break;
case Statement::kFuncdef:
if (_x.funcdef().block().statements_size() > 0)
if (!m_inForInitScope)
visit(_x.funcdef());
break;
@ -1360,6 +1480,9 @@ void ProtoConverter::visit(Statement const& _x)
if (m_inFunctionDef)
visit(_x.leave());
break;
case Statement::kMultidecl:
visit(_x.multidecl());
break;
case Statement::STMT_ONEOF_NOT_SET:
break;
}
@ -1528,7 +1651,7 @@ void ProtoConverter::visit(Block const& _x)
// scope belongs to for-init (in which function declarations
// are forbidden).
for (auto const& statement: _x.statements())
if (statement.has_funcdef() && !m_inForInitScope)
if (statement.has_funcdef() && statement.funcdef().block().statements_size() > 0 && !m_inForInitScope)
registerFunction(&statement.funcdef());
if (_x.statements_size() > 0)
@ -1622,9 +1745,9 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams)
m_output << "sload(" << slot << ")";
break;
case 3:
// Call to dictionaryToken() automatically picks a token
// Call to dummyExpression() automatically picks a token
// at a pseudo-random location.
m_output << dictionaryToken();
m_output << dummyExpression();
break;
}
if (i < _numInParams - 1)

View File

@ -78,6 +78,7 @@ private:
void visit(VarRef const&);
void visit(Expression const&);
void visit(VarDecl const&);
void visit(MultiVarDecl const&);
void visit(TypedVarDecl const&);
void visit(UnaryOp const&);
void visit(AssignmentStatement const&);
@ -283,6 +284,10 @@ private:
/// enum of type Program_Version
solidity::langutil::EVMVersion evmVersionMapping(Program_Version const& _x);
/// Return variable reference.
/// @param _index: Index of variable to be referenced
std::string varRef(unsigned _index);
/// Returns a monotonically increasing counter that starts from zero.
unsigned counter()
{
@ -321,6 +326,8 @@ private:
return m_objectId - 1;
}
std::string dummyExpression();
std::ostringstream m_output;
/// Variables in all function definitions
std::vector<std::vector<std::vector<std::string>>> m_funcVars;

View File

@ -21,6 +21,10 @@ message VarDecl {
required Expression expr = 1;
}
message MultiVarDecl {
required uint32 num_vars = 1;
}
message LowLevelCall {
enum Type {
CALL = 0;
@ -384,6 +388,7 @@ message Statement {
FunctionDef funcdef = 16;
PopStmt pop = 17;
LeaveStmt leave = 18;
MultiVarDecl multidecl = 19;
}
}