mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #14227 from ethereum/update-codegen-fuzzer
Stack optimizer fuzzer: Detect stack-too-deep during optimization
This commit is contained in:
commit
5d7533b540
@ -96,6 +96,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(
|
|||||||
}},
|
}},
|
||||||
{"blockFlattener", [&]() {
|
{"blockFlattener", [&]() {
|
||||||
disambiguate();
|
disambiguate();
|
||||||
|
FunctionGrouper::run(*m_context, *m_ast);
|
||||||
BlockFlattener::run(*m_context, *m_ast);
|
BlockFlattener::run(*m_context, *m_ast);
|
||||||
}},
|
}},
|
||||||
{"constantOptimiser", [&]() {
|
{"constantOptimiser", [&]() {
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
#include <libyul/backends/evm/EVMCodeTransform.h>
|
#include <libyul/backends/evm/EVMCodeTransform.h>
|
||||||
#include <libyul/backends/evm/EVMDialect.h>
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
|
||||||
|
#include <libyul/optimiser/CallGraphGenerator.h>
|
||||||
|
#include <libyul/CompilabilityChecker.h>
|
||||||
|
|
||||||
#include <libevmasm/Instruction.h>
|
#include <libevmasm/Instruction.h>
|
||||||
|
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
@ -47,6 +50,24 @@ using namespace std;
|
|||||||
|
|
||||||
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
|
static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// @returns true if there are recursive functions, false otherwise.
|
||||||
|
bool recursiveFunctionExists(Dialect const& _dialect, yul::Object& _object)
|
||||||
|
{
|
||||||
|
auto recursiveFunctions = CallGraphGenerator::callGraph(*_object.code).recursiveFunctions();
|
||||||
|
for(auto&& [function, variables]: CompilabilityChecker{
|
||||||
|
_dialect,
|
||||||
|
_object,
|
||||||
|
true
|
||||||
|
}.unreachableVariables
|
||||||
|
)
|
||||||
|
if(recursiveFunctions.count(function))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_PROTO_FUZZER(Program const& _input)
|
DEFINE_PROTO_FUZZER(Program const& _input)
|
||||||
{
|
{
|
||||||
// Solidity creates an invalid instruction for subobjects, so we simply
|
// Solidity creates an invalid instruction for subobjects, so we simply
|
||||||
@ -78,45 +99,58 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
|||||||
settings.runYulOptimiser = false;
|
settings.runYulOptimiser = false;
|
||||||
settings.optimizeStackAllocation = false;
|
settings.optimizeStackAllocation = false;
|
||||||
bytes unoptimisedByteCode;
|
bytes unoptimisedByteCode;
|
||||||
|
bool recursiveFunction = false;
|
||||||
|
bool unoptimizedStackTooDeep = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
unoptimisedByteCode = YulAssembler{version, nullopt, settings, yul_source}.assemble();
|
YulAssembler assembler{version, nullopt, settings, yul_source};
|
||||||
|
unoptimisedByteCode = assembler.assemble();
|
||||||
|
auto yulObject = assembler.object();
|
||||||
|
recursiveFunction = recursiveFunctionExists(
|
||||||
|
EVMDialect::strictAssemblyForEVMObjects(version),
|
||||||
|
*yulObject
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (solidity::yul::StackTooDeepError const&)
|
catch (solidity::yul::StackTooDeepError const&)
|
||||||
{
|
{
|
||||||
return;
|
unoptimizedStackTooDeep = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
evmc::Result deployResult = YulEvmoneUtility{}.deployCode(unoptimisedByteCode, hostContext);
|
|
||||||
if (deployResult.status_code != EVMC_SUCCESS)
|
|
||||||
return;
|
|
||||||
auto callMessage = YulEvmoneUtility{}.callMessage(deployResult.create_address);
|
|
||||||
evmc::Result callResult = hostContext.call(callMessage);
|
|
||||||
// If the fuzzer synthesized input does not contain the revert opcode which
|
|
||||||
// we lazily check by string find, the EVM call should not revert.
|
|
||||||
bool noRevertInSource = yul_source.find("revert") == string::npos;
|
|
||||||
bool noInvalidInSource = yul_source.find("invalid") == string::npos;
|
|
||||||
if (noInvalidInSource)
|
|
||||||
solAssert(
|
|
||||||
callResult.status_code != EVMC_INVALID_INSTRUCTION,
|
|
||||||
"Invalid instruction."
|
|
||||||
);
|
|
||||||
if (noRevertInSource)
|
|
||||||
solAssert(
|
|
||||||
callResult.status_code != EVMC_REVERT,
|
|
||||||
"SolidityEvmoneInterface: EVM One reverted"
|
|
||||||
);
|
|
||||||
// Bail out on serious errors encountered during a call.
|
|
||||||
if (YulEvmoneUtility{}.seriousCallError(callResult.status_code))
|
|
||||||
return;
|
|
||||||
solAssert(
|
|
||||||
(callResult.status_code == EVMC_SUCCESS ||
|
|
||||||
(!noRevertInSource && callResult.status_code == EVMC_REVERT) ||
|
|
||||||
(!noInvalidInSource && callResult.status_code == EVMC_INVALID_INSTRUCTION)),
|
|
||||||
"Unoptimised call failed."
|
|
||||||
);
|
|
||||||
ostringstream unoptimizedState;
|
ostringstream unoptimizedState;
|
||||||
unoptimizedState << EVMHostPrinter{hostContext, deployResult.create_address}.state();
|
bool noRevertInSource = true;
|
||||||
|
bool noInvalidInSource = true;
|
||||||
|
if (!unoptimizedStackTooDeep)
|
||||||
|
{
|
||||||
|
evmc::Result deployResult = YulEvmoneUtility{}.deployCode(unoptimisedByteCode, hostContext);
|
||||||
|
if (deployResult.status_code != EVMC_SUCCESS)
|
||||||
|
return;
|
||||||
|
auto callMessage = YulEvmoneUtility{}.callMessage(deployResult.create_address);
|
||||||
|
evmc::Result callResult = hostContext.call(callMessage);
|
||||||
|
// If the fuzzer synthesized input does not contain the revert opcode which
|
||||||
|
// we lazily check by string find, the EVM call should not revert.
|
||||||
|
noRevertInSource = yul_source.find("revert") == string::npos;
|
||||||
|
noInvalidInSource = yul_source.find("invalid") == string::npos;
|
||||||
|
if (noInvalidInSource)
|
||||||
|
solAssert(
|
||||||
|
callResult.status_code != EVMC_INVALID_INSTRUCTION,
|
||||||
|
"Invalid instruction."
|
||||||
|
);
|
||||||
|
if (noRevertInSource)
|
||||||
|
solAssert(
|
||||||
|
callResult.status_code != EVMC_REVERT,
|
||||||
|
"SolidityEvmoneInterface: EVM One reverted"
|
||||||
|
);
|
||||||
|
// Bail out on serious errors encountered during a call.
|
||||||
|
if (YulEvmoneUtility{}.seriousCallError(callResult.status_code))
|
||||||
|
return;
|
||||||
|
solAssert(
|
||||||
|
(callResult.status_code == EVMC_SUCCESS ||
|
||||||
|
(!noRevertInSource && callResult.status_code == EVMC_REVERT) ||
|
||||||
|
(!noInvalidInSource && callResult.status_code == EVMC_INVALID_INSTRUCTION)),
|
||||||
|
"Unoptimised call failed."
|
||||||
|
);
|
||||||
|
unoptimizedState << EVMHostPrinter{hostContext, deployResult.create_address}.state();
|
||||||
|
}
|
||||||
|
|
||||||
settings.runYulOptimiser = true;
|
settings.runYulOptimiser = true;
|
||||||
settings.optimizeStackAllocation = true;
|
settings.optimizeStackAllocation = true;
|
||||||
@ -127,9 +161,14 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
|||||||
}
|
}
|
||||||
catch (solidity::yul::StackTooDeepError const&)
|
catch (solidity::yul::StackTooDeepError const&)
|
||||||
{
|
{
|
||||||
return;
|
if (!recursiveFunction)
|
||||||
|
throw;
|
||||||
|
else
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unoptimizedStackTooDeep)
|
||||||
|
return;
|
||||||
// Reset host before running optimised code.
|
// Reset host before running optimised code.
|
||||||
hostContext.reset();
|
hostContext.reset();
|
||||||
evmc::Result deployResultOpt = YulEvmoneUtility{}.deployCode(optimisedByteCode, hostContext);
|
evmc::Result deployResultOpt = YulEvmoneUtility{}.deployCode(optimisedByteCode, hostContext);
|
||||||
@ -158,8 +197,13 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
|||||||
ostringstream optimizedState;
|
ostringstream optimizedState;
|
||||||
optimizedState << EVMHostPrinter{hostContext, deployResultOpt.create_address}.state();
|
optimizedState << EVMHostPrinter{hostContext, deployResultOpt.create_address}.state();
|
||||||
|
|
||||||
solAssert(
|
if (unoptimizedState.str() != optimizedState.str())
|
||||||
unoptimizedState.str() == optimizedState.str(),
|
{
|
||||||
"State of unoptimised and optimised stack reused code do not match."
|
cout << unoptimizedState.str() << endl;
|
||||||
);
|
cout << optimizedState.str() << endl;
|
||||||
|
solAssert(
|
||||||
|
false,
|
||||||
|
"State of unoptimised and optimised stack reused code do not match."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,11 @@ bytes YulAssembler::assemble()
|
|||||||
return m_stack.assemble(YulStack::Machine::EVM).bytecode->bytecode;
|
return m_stack.assemble(YulStack::Machine::EVM).bytecode->bytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<yul::Object> YulAssembler::object()
|
||||||
|
{
|
||||||
|
return m_stack.parserResult();
|
||||||
|
}
|
||||||
|
|
||||||
evmc::Result YulEvmoneUtility::deployCode(bytes const& _input, EVMHost& _host)
|
evmc::Result YulEvmoneUtility::deployCode(bytes const& _input, EVMHost& _host)
|
||||||
{
|
{
|
||||||
// Zero initialize all message fields
|
// Zero initialize all message fields
|
||||||
|
@ -47,6 +47,7 @@ public:
|
|||||||
m_optimiseYul(_optSettings.runYulOptimiser)
|
m_optimiseYul(_optSettings.runYulOptimiser)
|
||||||
{}
|
{}
|
||||||
solidity::bytes assemble();
|
solidity::bytes assemble();
|
||||||
|
std::shared_ptr<yul::Object> object();
|
||||||
private:
|
private:
|
||||||
solidity::yul::YulStack m_stack;
|
solidity::yul::YulStack m_stack;
|
||||||
std::string m_yulProgram;
|
std::string m_yulProgram;
|
||||||
|
@ -640,7 +640,7 @@ void ProtoConverter::visit(UnaryOp const& _x)
|
|||||||
{
|
{
|
||||||
m_output << "mod(";
|
m_output << "mod(";
|
||||||
visit(_x.operand());
|
visit(_x.operand());
|
||||||
m_output << ", " << to_string(s_maxMemory) << ")";
|
m_output << ", " << to_string(s_maxMemory - 32) << ")";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visit(_x.operand());
|
visit(_x.operand());
|
||||||
@ -1125,12 +1125,21 @@ void ProtoConverter::visit(StoreFunc const& _x)
|
|||||||
// Write to memory within bounds, storage is unbounded
|
// Write to memory within bounds, storage is unbounded
|
||||||
if (storeType == StoreFunc::SSTORE)
|
if (storeType == StoreFunc::SSTORE)
|
||||||
visit(_x.loc());
|
visit(_x.loc());
|
||||||
else
|
else if (storeType == StoreFunc::MSTORE8)
|
||||||
{
|
{
|
||||||
m_output << "mod(";
|
m_output << "mod(";
|
||||||
visit(_x.loc());
|
visit(_x.loc());
|
||||||
m_output << ", " << to_string(s_maxMemory) << ")";
|
m_output << ", " << to_string(s_maxMemory) << ")";
|
||||||
}
|
}
|
||||||
|
else if (storeType == StoreFunc::MSTORE)
|
||||||
|
{
|
||||||
|
// Since we write 32 bytes, ensure it does not exceed
|
||||||
|
// upper bound on memory.
|
||||||
|
m_output << "mod(";
|
||||||
|
visit(_x.loc());
|
||||||
|
m_output << ", " << to_string(s_maxMemory - 32) << ")";
|
||||||
|
|
||||||
|
}
|
||||||
m_output << ", ";
|
m_output << ", ";
|
||||||
visit(_x.val());
|
visit(_x.val());
|
||||||
m_output << ")\n";
|
m_output << ")\n";
|
||||||
@ -1706,7 +1715,7 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams)
|
|||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
// Access memory within stipulated bounds
|
// Access memory within stipulated bounds
|
||||||
slot = "mod(" + dictionaryToken() + ", " + to_string(s_maxMemory) + ")";
|
slot = "mod(" + dictionaryToken() + ", " + to_string(s_maxMemory - 32) + ")";
|
||||||
m_output << "mload(" << slot << ")";
|
m_output << "mload(" << slot << ")";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1940,6 +1949,8 @@ void ProtoConverter::visit(Program const& _x)
|
|||||||
{
|
{
|
||||||
case Program::kBlock:
|
case Program::kBlock:
|
||||||
m_output << "{\n";
|
m_output << "{\n";
|
||||||
|
m_output << "mstore(memoryguard(0x10000), 1)\n";
|
||||||
|
m_output << "sstore(mload(calldataload(0)), 1)\n";
|
||||||
visit(_x.block());
|
visit(_x.block());
|
||||||
m_output << "}\n";
|
m_output << "}\n";
|
||||||
break;
|
break;
|
||||||
|
@ -344,11 +344,12 @@ private:
|
|||||||
static unsigned constexpr s_modOutputParams = 5;
|
static unsigned constexpr s_modOutputParams = 5;
|
||||||
/// Hard-coded identifier for a Yul object's data block
|
/// Hard-coded identifier for a Yul object's data block
|
||||||
static auto constexpr s_dataIdentifier = "datablock";
|
static auto constexpr s_dataIdentifier = "datablock";
|
||||||
/// Upper bound on memory writes = 2**32 - 1
|
/// Upper bound on memory writes is 64KB in order to
|
||||||
/// See: https://eips.ethereum.org/EIPS/eip-1985#memory-size
|
/// preserve semantic equivalence in the presence of
|
||||||
static unsigned constexpr s_maxMemory = 4294967295;
|
/// memory guard
|
||||||
|
static unsigned constexpr s_maxMemory = 65536;
|
||||||
/// Upper bound on size for range copy functions
|
/// Upper bound on size for range copy functions
|
||||||
static unsigned constexpr s_maxSize = 65536;
|
static unsigned constexpr s_maxSize = 32768;
|
||||||
/// Predicate to keep track of for body scope. If false, break/continue
|
/// Predicate to keep track of for body scope. If false, break/continue
|
||||||
/// statements can not be created.
|
/// statements can not be created.
|
||||||
bool m_inForBodyScope;
|
bool m_inForBodyScope;
|
||||||
|
Loading…
Reference in New Issue
Block a user