Update bytecode optimiser fuzzer

This commit is contained in:
Bhargava Shastry 2021-04-08 12:28:16 +02:00
parent 0c57fbb275
commit 68f5632ddd
2 changed files with 227 additions and 142 deletions

View File

@ -50,164 +50,244 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size);
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
static constexpr size_t abiCoderHeapSize = 1024 * 512;
namespace
{
string abiEncoding(
Json::Value const& _functionABI,
map<h160, vector<string>> _addressSelectors,
Json::Value const& _methodIdentifiers
)
{
string abiTypeString;
string abiValueString;
tie(abiTypeString, abiValueString) = ValueGenerator{
_functionABI["inputs"],
0,
std::move(_addressSelectors)
}.type();
string encodedData;
// A function with inputs must contain type names within
// parentheses.
bool functionWithInputs = abiTypeString != "()";
string functionSignature = _functionABI["name"].asString() + abiTypeString;
cout << functionSignature << endl;
string encoding = _methodIdentifiers[functionSignature].asString();
if (functionWithInputs)
{
abicoder::ABICoder coder(abiCoderHeapSize);
auto [status, data] = coder.encode(abiTypeString, abiValueString);
cout << abiTypeString << endl;
cout << abiValueString << endl;
solAssert(status, "Isabelle coder: failed.");
encoding += data.substr(2, data.size());
}
return encoding;
}
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size)
{
// if (_size <= 600)
string input(reinterpret_cast<char const*>(_data), _size);
regex re = regex("library\\s*(\\w+)\\s*\\{");
smatch matches;
std::string libraryName;
auto match = regex_search(input, matches, re);
if (match && matches[1].matched)
libraryName = matches[1].str();
map<string, string> sourceCode;
try
{
string input(reinterpret_cast<char const*>(_data), _size);
regex re = regex("library\\s*(\\w+)\\s*\\{");
smatch matches;
std::string libraryName;
auto match = regex_search(input, matches, re);
if (match && matches[1].matched)
libraryName = matches[1].str();
EVMVersion version;
EVMHost hostContext(version, evmone);
map<string, string> sourceCode;
try
TestCaseReader t = TestCaseReader(std::istringstream(input));
sourceCode = t.sources().sources;
string contractName;
string methodName;
auto compilerSetting = OptimiserSettings::none();
CompilerInput cInput = {
version,
sourceCode,
contractName,
compilerSetting,
{},
true,
false
};
EvmoneUtility evmoneUtil(
hostContext,
cInput,
contractName,
libraryName,
methodName
);
map<h160, vector<string>> addressSelector;
// Add pre-compiles
for (auto const& account: hostContext.accounts)
addressSelector[EVMHost::convertFromEVMC(account.first)] = {};
optional<EvmoneUtility::LibraryStatus> libraryInfo;
vector<string> libraryFunctionSelectors;
if (!libraryName.empty()) {
cout << "Deploying library" << endl;
libraryInfo = evmoneUtil.compileAndDeployLibrary();
if (!libraryInfo.has_value() || get<0>(libraryInfo.value()) != EVMC_SUCCESS)
return 0;
// Add library functions to address selector
libraryFunctionSelectors = get<2>(libraryInfo.value());
if (!libraryFunctionSelectors.empty())
addressSelector[get<1>(libraryInfo.value())] = libraryFunctionSelectors;
cout << "Deployed" << endl;
}
auto compilerOutput = evmoneUtil.compileContract();
if (!compilerOutput.has_value())
return 0;
auto r = evmoneUtil.randomFunction(_size);
if (!r.has_value())
return 0;
auto deployResult = evmoneUtil.deployContract(compilerOutput->byteCode);
if (deployResult.status_code != EVMC_SUCCESS)
return 0;
// Record function selectors in contract
vector<string> functionSelectors;
for (auto const& item: compilerOutput->methodIdentifiersInContract)
functionSelectors.push_back(item.asString());
// Add deployed contract to list of address literals.
addressSelector[EVMHost::convertFromEVMC(deployResult.create_address)] = functionSelectors;
string encodedData = abiEncoding(r.value(), addressSelector,
compilerOutput->methodIdentifiersInContract);
auto callResult = evmoneUtil.executeContract(
solidity::util::fromHex(encodedData),
deployResult.create_address
);
if (callResult.status_code != EVMC_SUCCESS) {
cout << "Unoptimised old code gen call failed with status code: "
<< callResult.status_code
<< endl;
return 0;
}
solidity::bytes result;
for (size_t i = 0; i < callResult.output_size; i++)
result.push_back(callResult.output_data[i]);
EVMHostPrinter p(hostContext, deployResult.create_address);
ostringstream oldCodeGen;
oldCodeGen << p.state();
compilerSetting = OptimiserSettings::standard();
// Reset call state post old code gen run
hostContext.reset();
addressSelector.clear();
bool libraryFunctions = !libraryFunctionSelectors.empty();
libraryFunctionSelectors.clear();
evmoneUtil.optSetting(compilerSetting);
// Add pre-compiles post reset
for (auto const& account: hostContext.accounts)
addressSelector[EVMHost::convertFromEVMC(account.first)] = {};
optional<EvmoneUtility::LibraryStatus> libraryInfoNew;
if (!libraryName.empty())
{
EVMVersion version;
EVMHost hostContext(version, evmone);
TestCaseReader t = TestCaseReader(std::istringstream(input));
sourceCode = t.sources().sources;
string contractName;
string methodName;
auto compilerSetting = OptimiserSettings::none();
CompilerInput cInput = {
version,
sourceCode,
contractName,
compilerSetting,
{},
true,
false
};
EvmoneUtility evmoneUtil(
hostContext,
cInput,
contractName,
libraryName,
methodName
libraryInfoNew = evmoneUtil.compileAndDeployLibrary();
solAssert(
libraryInfoNew.has_value() && get<0>(libraryInfoNew.value()) == EVMC_SUCCESS,
"Deploying library failed for the second time"
);
if (!libraryName.empty())
{
cout << "Deploying library" << endl;
auto l = evmoneUtil.compileAndDeployLibrary();
if (!l.has_value())
return 0;
cout << "Deployed" << endl;
}
hostContext.reset();
evmoneUtil.reset(true);
evmoneUtil.optSetting(compilerSetting);
auto compilerOutput = evmoneUtil.compileContract();
if (!compilerOutput.has_value())
return 0;
auto r = evmoneUtil.randomFunction(_size);
if (!r.has_value())
return 0;
auto x = ValueGenerator{r.value()["inputs"], 0}.type();
bool encodeStatus;
string encodedData;
bool functionWithInputs = x.first != "()";
auto sig = r.value()["name"].asString() + x.first;
cout << sig << endl;
if (functionWithInputs)
{
abicoder::ABICoder coder(abiCoderHeapSize);
auto s = coder.encode(x.first, x.second);
encodeStatus = s.first;
encodedData = s.second;
solAssert(encodeStatus, "Isabelle coder: failed.");
}
auto deployResult = evmoneUtil.deployContract(compilerOutput->byteCode);
if (deployResult.status_code != EVMC_SUCCESS)
return 0;
auto methodSig = compilerOutput->methodIdentifiersInContract[sig].asString();
if (functionWithInputs)
methodSig += encodedData.substr(2, encodedData.size());
auto callResult = evmoneUtil.executeContract(
solidity::util::fromHex(methodSig),
deployResult.create_address
libraryFunctionSelectors = get<2>(libraryInfoNew.value());
// Library functions in both runs should either be both empty or
// both non-empty.
solAssert(
(
(libraryFunctions && !libraryFunctionSelectors.empty()) ||
(!libraryFunctions && libraryFunctionSelectors.empty())
),
"Library function mismatch."
);
if (!libraryFunctionSelectors.empty())
addressSelector[get<1>(libraryInfoNew.value())] = libraryFunctionSelectors;
}
if (callResult.status_code != EVMC_SUCCESS)
{
cout << "Unoptimised call failed with status code: "
<< callResult.status_code
<< endl;
return 0;
}
auto compilerOutputOpt = evmoneUtil.compileContract();
solAssert(compilerOutputOpt.has_value(), "Contract could not be optimised.");
solidity::bytes result;
for (size_t i = 0; i < callResult.output_size; i++)
result.push_back(callResult.output_data[i]);
auto deployResultOpt = evmoneUtil.deployContract(compilerOutputOpt->byteCode);
solAssert(deployResultOpt.status_code == EVMC_SUCCESS,
"Contract optimised via old code gen could not be deployed.");
// Add address literal of contract compiled via Yul IR.
addressSelector[EVMHost::convertFromEVMC(deployResultOpt.create_address)] = functionSelectors;
string encodedDataIR = abiEncoding(r.value(), addressSelector,
compilerOutput->methodIdentifiersInContract);
auto callResultOpt = evmoneUtil.executeContract(
solidity::util::fromHex(encodedDataIR),
deployResultOpt.create_address
);
solAssert(
callResultOpt.status_code == EVMC_SUCCESS,
"Optimised code gen contract call failed."
);
solidity::bytes resultOpt;
for (size_t i = 0; i < callResultOpt.output_size; i++)
resultOpt.push_back(callResultOpt.output_data[i]);
if (result != resultOpt)
{
cout << solidity::util::toHex(result) << endl;
EVMHostPrinter p(hostContext, deployResult.create_address);
ostringstream oldCodeGen;
oldCodeGen << p.state();
compilerSetting = OptimiserSettings::standard();
hostContext.reset();
evmoneUtil.reset(true);
evmoneUtil.optSetting(compilerSetting);
auto compilerOutputOpt = evmoneUtil.compileContract();
solAssert(compilerOutputOpt.has_value(), "Contract could not be optimised.");
auto deployResultOpt = evmoneUtil.deployContract(compilerOutputOpt->byteCode);
solAssert(deployResultOpt.status_code == EVMC_SUCCESS, "Contract compiled via old optimiser could not be deployed.");
auto callResultOpt = evmoneUtil.executeContract(
solidity::util::fromHex(methodSig),
deployResultOpt.create_address
);
solAssert(callResultOpt.status_code == EVMC_SUCCESS, "Old code gen optimised contract call failed.");
solidity::bytes resultOpt;
for (size_t i = 0; i < callResultOpt.output_size; i++)
resultOpt.push_back(callResultOpt.output_data[i]);
cout << solidity::util::toHex(resultOpt) << endl;
solAssert(result == resultOpt, "Unoptimised and optimised call results do not match.");
}
solAssert(
result == resultOpt,
"Unoptimised and optimised code gen call results do not match."
);
EVMHostPrinter pOpt(hostContext, deployResultOpt.create_address);
ostringstream newCodeGen;
newCodeGen << pOpt.state();
EVMHostPrinter pOpt(hostContext, deployResultOpt.create_address);
ostringstream newCodeGen;
newCodeGen << pOpt.state();
if (oldCodeGen.str() != newCodeGen.str())
{
cout << oldCodeGen.str() << endl;
cout << newCodeGen.str() << endl;
solAssert(oldCodeGen.str() == newCodeGen.str(), "Old and new code gen state do not match.");
return 0;
}
catch (runtime_error const&)
{
cout << "Runtime error!" << endl;
return 0;
}
catch (solidity::langutil::UnimplementedFeatureError const&)
{
cout << "Unimplemented feature!" << endl;
return 0;
}
catch (solidity::langutil::CompilerError const& _e)
{
cout << "Compiler error!" << endl;
cout << _e.what() << endl;
return 0;
}
catch (solidity::yul::StackTooDeepError const&)
{
cout << "Stack too deep" << endl;
return 0;
}
solAssert(
oldCodeGen.str() == newCodeGen.str(),
"Unoptimised and optimised bytecode state do not match.");
return 0;
}
catch (runtime_error const&)
{
cout << "Runtime error!" << endl;
return 0;
}
catch (solidity::langutil::UnimplementedFeatureError const&)
{
cout << "Unimplemented feature!" << endl;
return 0;
}
catch (solidity::langutil::CompilerError const& _e)
{
cout << "Compiler error!" << endl;
cout << _e.what() << endl;
return 0;
}
catch (solidity::yul::StackTooDeepError const&)
{
cout << "Stack too deep" << endl;
return 0;
}
}

View File

@ -162,6 +162,11 @@ std::string ValueGenerator::functionLiteral()
{
std::string contractAddress;
std::string functionSelector;
// Selects a random function from the first address that contains non-zero
// number of function selectors. Usually this means that if a library and
// a contract are both present, then a library function is selected. If
// either only a contract or a library is present then a function in that
// unit is selected.
for (auto const& item: m_addressSelector)
if (!item.second.empty())
{