yul proto fuzzer: Add EVM version field

This commit is contained in:
Bhargava Shastry 2019-11-18 12:12:30 +01:00
parent 45caaf5ad8
commit a335fed189
5 changed files with 129 additions and 10 deletions

View File

@ -30,6 +30,7 @@
using namespace std; using namespace std;
using namespace solidity::yul::test::yul_fuzzer; using namespace solidity::yul::test::yul_fuzzer;
using namespace solidity::yul::test; using namespace solidity::yul::test;
using namespace solidity::langutil;
using namespace solidity::util; using namespace solidity::util;
using namespace solidity; using namespace solidity;
@ -86,6 +87,29 @@ string ProtoConverter::createAlphaNum(string const& _strBytes)
return tmp; return tmp;
} }
EVMVersion ProtoConverter::evmVersionMapping(Program_Version const& _ver)
{
switch (_ver)
{
case Program::HOMESTEAD:
return EVMVersion::homestead();
case Program::TANGERINE:
return EVMVersion::tangerineWhistle();
case Program::SPURIOUS:
return EVMVersion::spuriousDragon();
case Program::BYZANTIUM:
return EVMVersion::byzantium();
case Program::CONSTANTINOPLE:
return EVMVersion::constantinople();
case Program::PETERSBURG:
return EVMVersion::petersburg();
case Program::ISTANBUL:
return EVMVersion::istanbul();
case Program::BERLIN:
return EVMVersion::berlin();
}
}
string ProtoConverter::visit(Literal const& _x) string ProtoConverter::visit(Literal const& _x)
{ {
switch (_x.literal_oneof_case()) switch (_x.literal_oneof_case())
@ -230,7 +254,16 @@ void ProtoConverter::visit(Expression const& _x)
void ProtoConverter::visit(BinaryOp const& _x) void ProtoConverter::visit(BinaryOp const& _x)
{ {
switch (_x.op()) BinaryOp_BOp op = _x.op();
if ((op == BinaryOp::SHL || op == BinaryOp::SHR || op == BinaryOp::SAR) &&
!m_evmVersion.hasBitwiseShifting())
{
m_output << dictionaryToken();
return;
}
switch (op)
{ {
case BinaryOp::ADD: case BinaryOp::ADD:
m_output << "add"; m_output << "add";
@ -266,12 +299,15 @@ void ProtoConverter::visit(BinaryOp const& _x)
m_output << "gt"; m_output << "gt";
break; break;
case BinaryOp::SHR: case BinaryOp::SHR:
yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
m_output << "shr"; m_output << "shr";
break; break;
case BinaryOp::SHL: case BinaryOp::SHL:
yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
m_output << "shl"; m_output << "shl";
break; break;
case BinaryOp::SAR: case BinaryOp::SAR:
yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version");
m_output << "sar"; m_output << "sar";
break; break;
case BinaryOp::SDIV: case BinaryOp::SDIV:
@ -475,7 +511,17 @@ void ProtoConverter::visit(TypedVarDecl const& _x)
void ProtoConverter::visit(UnaryOp const& _x) void ProtoConverter::visit(UnaryOp const& _x)
{ {
switch (_x.op()) UnaryOp_UOp op = _x.op();
// Replace calls to extcodehash on unsupported EVMs with a dictionary
// token.
if (op == UnaryOp::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
{
m_output << dictionaryToken();
return;
}
switch (op)
{ {
case UnaryOp::NOT: case UnaryOp::NOT:
m_output << "not"; m_output << "not";
@ -550,7 +596,12 @@ void ProtoConverter::visit(NullaryOp const& _x)
m_output << "codesize()"; m_output << "codesize()";
break; break;
case NullaryOp::RETURNDATASIZE: case NullaryOp::RETURNDATASIZE:
// If evm supports returndatasize, we generate it. Otherwise,
// we output a dictionary token.
if (m_evmVersion.supportsReturndata())
m_output << "returndatasize()"; m_output << "returndatasize()";
else
m_output << dictionaryToken();
break; break;
case NullaryOp::ADDRESS: case NullaryOp::ADDRESS:
m_output << "address()"; m_output << "address()";
@ -583,10 +634,20 @@ void ProtoConverter::visit(NullaryOp const& _x)
m_output << "gaslimit()"; m_output << "gaslimit()";
break; break;
case NullaryOp::SELFBALANCE: case NullaryOp::SELFBALANCE:
// Replace calls to selfbalance() on unsupported EVMs with a dictionary
// token.
if (m_evmVersion.hasSelfBalance())
m_output << "selfbalance()"; m_output << "selfbalance()";
else
m_output << dictionaryToken();
break; break;
case NullaryOp::CHAINID: case NullaryOp::CHAINID:
// Replace calls to chainid() on unsupported EVMs with a dictionary
// token.
if (m_evmVersion.hasChainID())
m_output << "chainid()"; m_output << "chainid()";
else
m_output << dictionaryToken();
break; break;
} }
} }
@ -600,6 +661,11 @@ void ProtoConverter::visit(CopyFunc const& _x)
if (type == CopyFunc::DATA && !m_isObject) if (type == CopyFunc::DATA && !m_isObject)
return; return;
// We don't generate code if the copy function is returndatacopy
// and the underlying evm does not support it.
if (type == CopyFunc::RETURNDATA && !m_evmVersion.supportsReturndata())
return;
switch (type) switch (type)
{ {
case CopyFunc::CALLDATA: case CopyFunc::CALLDATA:
@ -609,6 +675,7 @@ void ProtoConverter::visit(CopyFunc const& _x)
m_output << "codecopy"; m_output << "codecopy";
break; break;
case CopyFunc::RETURNDATA: case CopyFunc::RETURNDATA:
yulAssert(m_evmVersion.supportsReturndata(), "Proto fuzzer: Invalid evm version");
m_output << "returndatacopy"; m_output << "returndatacopy";
break; break;
case CopyFunc::DATA: case CopyFunc::DATA:
@ -890,6 +957,16 @@ void ProtoConverter::visit(FunctionCall const& _x)
void ProtoConverter::visit(LowLevelCall const& _x) void ProtoConverter::visit(LowLevelCall const& _x)
{ {
LowLevelCall_Type type = _x.callty(); LowLevelCall_Type type = _x.callty();
// Generate staticcall if it is supported by the underlying evm
if (type == LowLevelCall::STATICCALL && !m_evmVersion.hasStaticCall())
{
// Since staticcall is supposed to return 0 on success and 1 on
// failure, we can use counter value to emulate it
m_output << ((counter() % 2) ? "0" : "1");
return;
}
switch (type) switch (type)
{ {
case LowLevelCall::CALL: case LowLevelCall::CALL:
@ -902,6 +979,7 @@ void ProtoConverter::visit(LowLevelCall const& _x)
m_output << "delegatecall("; m_output << "delegatecall(";
break; break;
case LowLevelCall::STATICCALL: case LowLevelCall::STATICCALL:
yulAssert(m_evmVersion.hasStaticCall(), "Proto fuzzer: Invalid evm version");
m_output << "staticcall("; m_output << "staticcall(";
break; break;
} }
@ -927,6 +1005,15 @@ void ProtoConverter::visit(LowLevelCall const& _x)
void ProtoConverter::visit(Create const& _x) void ProtoConverter::visit(Create const& _x)
{ {
Create_Type type = _x.createty(); Create_Type type = _x.createty();
// Replace a call to create2 on unsupported EVMs with a dictionary
// token.
if (type == Create::CREATE2 && !m_evmVersion.hasCreate2())
{
m_output << dictionaryToken();
return;
}
switch (type) switch (type)
{ {
case Create::CREATE: case Create::CREATE:
@ -1737,6 +1824,9 @@ void ProtoConverter::visit(Program const& _x)
// Initialize input size // Initialize input size
m_inputSize = _x.ByteSizeLong(); m_inputSize = _x.ByteSizeLong();
// Record EVM Version
m_evmVersion = evmVersionMapping(_x.ver());
// Program is either a yul object or a block of // Program is either a yul object or a block of
// statements. // statements.
switch (_x.program_oneof_case()) switch (_x.program_oneof_case())

View File

@ -31,6 +31,8 @@
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <liblangutil/EVMVersion.h>
namespace solidity::yul::test::yul_fuzzer namespace solidity::yul::test::yul_fuzzer
{ {
class ProtoConverter class ProtoConverter
@ -56,6 +58,12 @@ public:
ProtoConverter(ProtoConverter&&) = delete; ProtoConverter(ProtoConverter&&) = delete;
std::string programToString(Program const& _input); std::string programToString(Program const& _input);
/// Returns evm version
solidity::langutil::EVMVersion version()
{
return m_evmVersion;
}
private: private:
void visit(BinaryOp const&); void visit(BinaryOp const&);
@ -270,6 +278,10 @@ private:
/// dictionarySize is the total number of entries in the dictionary. /// dictionarySize is the total number of entries in the dictionary.
std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add); std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add);
/// Returns an EVMVersion object corresponding to the protobuf
/// enum of type Program_Version
solidity::langutil::EVMVersion evmVersionMapping(Program_Version const& _x);
/// Returns a monotonically increasing counter that starts from zero. /// Returns a monotonically increasing counter that starts from zero.
unsigned counter() unsigned counter()
{ {
@ -367,5 +379,7 @@ private:
/// Flag to track whether scope extension of variables defined in for-init /// Flag to track whether scope extension of variables defined in for-init
/// block is enabled. /// block is enabled.
bool m_forInitScopeExtEnabled; bool m_forInitScopeExtEnabled;
/// Object that holds the targeted evm version specified by protobuf input
solidity::langutil::EVMVersion m_evmVersion;
}; };
} }

View File

@ -405,10 +405,21 @@ message Data {
} }
message Program { message Program {
enum Version {
HOMESTEAD = 0;
TANGERINE = 1;
SPURIOUS = 2;
BYZANTIUM = 3;
CONSTANTINOPLE = 4;
PETERSBURG = 5;
ISTANBUL = 6;
BERLIN = 7;
}
oneof program_oneof { oneof program_oneof {
Block block = 1; Block block = 1;
Object obj = 2; Object obj = 2;
} }
required Version ver = 3;
} }
package solidity.yul.test.yul_fuzzer; package solidity.yul.test.yul_fuzzer;

View File

@ -29,12 +29,14 @@
using namespace solidity; using namespace solidity;
using namespace solidity::yul; using namespace solidity::yul;
using namespace solidity::yul::test::yul_fuzzer; using namespace solidity::yul::test::yul_fuzzer;
using namespace solidity::langutil;
using namespace std; using namespace std;
DEFINE_PROTO_FUZZER(Program const& _input) DEFINE_PROTO_FUZZER(Program const& _input)
{ {
ProtoConverter converter; ProtoConverter converter;
string yul_source = converter.programToString(_input); string yul_source = converter.programToString(_input);
EVMVersion version = converter.version();
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{ {
@ -51,7 +53,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
// AssemblyStack entry point // AssemblyStack entry point
AssemblyStack stack( AssemblyStack stack(
langutil::EVMVersion(), version,
AssemblyStack::Language::StrictAssembly, AssemblyStack::Language::StrictAssembly,
solidity::frontend::OptimiserSettings::full() solidity::frontend::OptimiserSettings::full()
); );

View File

@ -55,7 +55,9 @@ void printErrors(ostream& _stream, ErrorList const& _errors)
DEFINE_PROTO_FUZZER(Program const& _input) DEFINE_PROTO_FUZZER(Program const& _input)
{ {
string yul_source = ProtoConverter().programToString(_input); ProtoConverter converter;
string yul_source = converter.programToString(_input);
EVMVersion version = converter.version();
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
{ {
@ -69,7 +71,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
// AssemblyStack entry point // AssemblyStack entry point
AssemblyStack stack( AssemblyStack stack(
langutil::EVMVersion::berlin(), version,
AssemblyStack::Language::StrictAssembly, AssemblyStack::Language::StrictAssembly,
solidity::frontend::OptimiserSettings::full() solidity::frontend::OptimiserSettings::full()
); );
@ -87,7 +89,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret( yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret(
os1, os1,
stack.parserResult()->code, stack.parserResult()->code,
EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::berlin()) EVMDialect::strictAssemblyForEVMObjects(version)
); );
if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached) if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached)
@ -97,7 +99,7 @@ DEFINE_PROTO_FUZZER(Program const& _input)
termReason = yulFuzzerUtil::interpret( termReason = yulFuzzerUtil::interpret(
os2, os2,
stack.parserResult()->code, stack.parserResult()->code,
EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::berlin()), EVMDialect::strictAssemblyForEVMObjects(version),
(yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4) (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4)
); );