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", [&]() { | ||||
| 			disambiguate(); | ||||
| 			FunctionGrouper::run(*m_context, *m_ast); | ||||
| 			BlockFlattener::run(*m_context, *m_ast); | ||||
| 		}}, | ||||
| 		{"constantOptimiser", [&]() { | ||||
|  | ||||
| @ -27,6 +27,9 @@ | ||||
| #include <libyul/backends/evm/EVMCodeTransform.h> | ||||
| #include <libyul/backends/evm/EVMDialect.h> | ||||
| 
 | ||||
| #include <libyul/optimiser/CallGraphGenerator.h> | ||||
| #include <libyul/CompilabilityChecker.h> | ||||
| 
 | ||||
| #include <libevmasm/Instruction.h> | ||||
| 
 | ||||
| #include <liblangutil/EVMVersion.h> | ||||
| @ -47,6 +50,24 @@ using namespace std; | ||||
| 
 | ||||
| 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) | ||||
| { | ||||
| 	// Solidity creates an invalid instruction for subobjects, so we simply
 | ||||
| @ -78,15 +99,28 @@ DEFINE_PROTO_FUZZER(Program const& _input) | ||||
| 	settings.runYulOptimiser = false; | ||||
| 	settings.optimizeStackAllocation = false; | ||||
| 	bytes unoptimisedByteCode; | ||||
| 	bool recursiveFunction = false; | ||||
| 	bool unoptimizedStackTooDeep = false; | ||||
| 	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&) | ||||
| 	{ | ||||
| 		return; | ||||
| 		unoptimizedStackTooDeep = true; | ||||
| 	} | ||||
| 
 | ||||
| 	ostringstream unoptimizedState; | ||||
| 	bool noRevertInSource = true; | ||||
| 	bool noInvalidInSource = true; | ||||
| 	if (!unoptimizedStackTooDeep) | ||||
| 	{ | ||||
| 		evmc::Result deployResult = YulEvmoneUtility{}.deployCode(unoptimisedByteCode, hostContext); | ||||
| 		if (deployResult.status_code != EVMC_SUCCESS) | ||||
| 			return; | ||||
| @ -94,8 +128,8 @@ DEFINE_PROTO_FUZZER(Program const& _input) | ||||
| 		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; | ||||
| 		noRevertInSource = yul_source.find("revert") == string::npos; | ||||
| 		noInvalidInSource = yul_source.find("invalid") == string::npos; | ||||
| 		if (noInvalidInSource) | ||||
| 			solAssert( | ||||
| 				callResult.status_code != EVMC_INVALID_INSTRUCTION, | ||||
| @ -115,8 +149,8 @@ DEFINE_PROTO_FUZZER(Program const& _input) | ||||
| 			(!noInvalidInSource && callResult.status_code == EVMC_INVALID_INSTRUCTION)), | ||||
| 			"Unoptimised call failed." | ||||
| 		); | ||||
| 	ostringstream unoptimizedState; | ||||
| 		unoptimizedState << EVMHostPrinter{hostContext, deployResult.create_address}.state(); | ||||
| 	} | ||||
| 
 | ||||
| 	settings.runYulOptimiser = true; | ||||
| 	settings.optimizeStackAllocation = true; | ||||
| @ -127,9 +161,14 @@ DEFINE_PROTO_FUZZER(Program const& _input) | ||||
| 	} | ||||
| 	catch (solidity::yul::StackTooDeepError const&) | ||||
| 	{ | ||||
| 		if (!recursiveFunction) | ||||
| 			throw; | ||||
| 		else | ||||
| 			return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (unoptimizedStackTooDeep) | ||||
| 		return; | ||||
| 	// Reset host before running optimised code.
 | ||||
| 	hostContext.reset(); | ||||
| 	evmc::Result deployResultOpt = YulEvmoneUtility{}.deployCode(optimisedByteCode, hostContext); | ||||
| @ -158,8 +197,13 @@ DEFINE_PROTO_FUZZER(Program const& _input) | ||||
| 	ostringstream optimizedState; | ||||
| 	optimizedState << EVMHostPrinter{hostContext, deployResultOpt.create_address}.state(); | ||||
| 
 | ||||
| 	if (unoptimizedState.str() != optimizedState.str()) | ||||
| 	{ | ||||
| 		cout << unoptimizedState.str() << endl; | ||||
| 		cout << optimizedState.str() << endl; | ||||
| 		solAssert( | ||||
| 		unoptimizedState.str() == optimizedState.str(), | ||||
| 			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; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<yul::Object> YulAssembler::object() | ||||
| { | ||||
| 	return m_stack.parserResult(); | ||||
| } | ||||
| 
 | ||||
| evmc::Result YulEvmoneUtility::deployCode(bytes const& _input, EVMHost& _host) | ||||
| { | ||||
| 	// Zero initialize all message fields
 | ||||
|  | ||||
| @ -47,6 +47,7 @@ public: | ||||
| 		m_optimiseYul(_optSettings.runYulOptimiser) | ||||
| 	{} | ||||
| 	solidity::bytes assemble(); | ||||
| 	std::shared_ptr<yul::Object> object(); | ||||
| private: | ||||
| 	solidity::yul::YulStack m_stack; | ||||
| 	std::string m_yulProgram; | ||||
|  | ||||
| @ -640,7 +640,7 @@ void ProtoConverter::visit(UnaryOp const& _x) | ||||
| 	{ | ||||
| 		m_output << "mod("; | ||||
| 		visit(_x.operand()); | ||||
| 		m_output << ", " << to_string(s_maxMemory) << ")"; | ||||
| 		m_output << ", " << to_string(s_maxMemory - 32) << ")"; | ||||
| 	} | ||||
| 	else | ||||
| 		visit(_x.operand()); | ||||
| @ -1125,12 +1125,21 @@ void ProtoConverter::visit(StoreFunc const& _x) | ||||
| 	// Write to memory within bounds, storage is unbounded
 | ||||
| 	if (storeType == StoreFunc::SSTORE) | ||||
| 		visit(_x.loc()); | ||||
| 	else | ||||
| 	else if (storeType == StoreFunc::MSTORE8) | ||||
| 	{ | ||||
| 		m_output << "mod("; | ||||
| 		visit(_x.loc()); | ||||
| 		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 << ", "; | ||||
| 	visit(_x.val()); | ||||
| 	m_output << ")\n"; | ||||
| @ -1706,7 +1715,7 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams) | ||||
| 		case 1: | ||||
| 		{ | ||||
| 			// Access memory within stipulated bounds
 | ||||
| 			slot = "mod(" + dictionaryToken() + ", " + to_string(s_maxMemory) + ")"; | ||||
| 			slot = "mod(" + dictionaryToken() + ", " + to_string(s_maxMemory - 32) + ")"; | ||||
| 			m_output << "mload(" << slot << ")"; | ||||
| 			break; | ||||
| 		} | ||||
| @ -1940,6 +1949,8 @@ void ProtoConverter::visit(Program const& _x) | ||||
| 	{ | ||||
| 	case Program::kBlock: | ||||
| 		m_output << "{\n"; | ||||
| 		m_output << "mstore(memoryguard(0x10000), 1)\n"; | ||||
| 		m_output << "sstore(mload(calldataload(0)), 1)\n"; | ||||
| 		visit(_x.block()); | ||||
| 		m_output << "}\n"; | ||||
| 		break; | ||||
|  | ||||
| @ -344,11 +344,12 @@ private: | ||||
| 	static unsigned constexpr s_modOutputParams = 5; | ||||
| 	/// Hard-coded identifier for a Yul object's data block
 | ||||
| 	static auto constexpr s_dataIdentifier = "datablock"; | ||||
| 	/// Upper bound on memory writes = 2**32 - 1
 | ||||
| 	/// See: https://eips.ethereum.org/EIPS/eip-1985#memory-size
 | ||||
| 	static unsigned constexpr s_maxMemory = 4294967295; | ||||
| 	/// Upper bound on memory writes is 64KB in order to
 | ||||
| 	/// preserve semantic equivalence in the presence of
 | ||||
| 	/// memory guard
 | ||||
| 	static unsigned constexpr s_maxMemory = 65536; | ||||
| 	/// 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
 | ||||
| 	/// statements can not be created.
 | ||||
| 	bool m_inForBodyScope; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user