diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index 54d3f5109..ce839f626 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -773,7 +773,7 @@ evmc::result EVMHost::resultWithGas( return result; } -void EVMHost::print_storage_at(evmc::address const& _addr, ostringstream& _os) +void EVMHost::printStorageAt(evmc::address const& _addr, ostringstream& _os) { for (auto const& [slot, value]: get_address_storage(_addr)) if (get_storage(_addr, slot)) @@ -786,9 +786,9 @@ StorageMap const& EVMHost::get_address_storage(evmc::address const& _addr) return accounts[_addr].storage; } -void EVMHost::print_call_records(std::ostringstream& _os) const noexcept +void EVMHost::printCallRecords(std::ostringstream& _os) const noexcept { - auto callKind = [](evmc_call_kind _kind) -> string + static const auto callKind = [](evmc_call_kind _kind) -> string { switch (_kind) { @@ -814,7 +814,7 @@ void EVMHost::print_call_records(std::ostringstream& _os) const noexcept << endl; } -void EVMHost::print_selfdestruct_records(ostringstream& _os) const noexcept +void EVMHost::printSelfdestructRecords(ostringstream& _os) const noexcept { for (auto const& record: recorded_selfdestructs) _os << "SELFDESTRUCT" @@ -825,7 +825,7 @@ void EVMHost::print_selfdestruct_records(ostringstream& _os) const noexcept << endl; } -void EVMHost::print_balance(evmc::address const& _addr, ostringstream& _os) const noexcept +void EVMHost::printBalance(evmc::address const& _addr, ostringstream& _os) const noexcept { _os << "BALANCE " << convertFromEVMC(get_balance(_addr)) << endl; } @@ -837,13 +837,13 @@ string EVMHost::dumpState(evmc::address _addr) // Print state and execution trace. if (account_exists(_addr)) { - print_storage_at(_addr, stateStream); - print_balance(_addr, stateStream); + printStorageAt(_addr, stateStream); + printBalance(_addr, stateStream); } else - print_selfdestruct_records(stateStream); + printSelfdestructRecords(stateStream); - print_call_records(stateStream); + printCallRecords(stateStream); return stateStream.str(); } diff --git a/test/EVMHost.h b/test/EVMHost.h index b6b162da7..163122e65 100644 --- a/test/EVMHost.h +++ b/test/EVMHost.h @@ -97,13 +97,13 @@ private: /// Records calls made via @param _message. void recordCalls(evmc_message const& _message) noexcept; /// Prints contents of storage at @param _addr to @param _os. - void print_storage_at(evmc::address const& _addr, std::ostringstream& _os); + void printStorageAt(evmc::address const& _addr, std::ostringstream& _os); /// Prints call summary to @param _os. - void print_call_records(std::ostringstream& _os) const noexcept; + void printCallRecords(std::ostringstream& _os) const noexcept; /// Print self destruct records to @param _os. - void print_selfdestruct_records(std::ostringstream& _os) const noexcept; + void printSelfdestructRecords(std::ostringstream& _os) const noexcept; /// Print balance of @param _addr to @param _os. - void print_balance(evmc::address const& _addr, std::ostringstream& _os) const noexcept; + void printBalance(evmc::address const& _addr, std::ostringstream& _os) const noexcept; static evmc::result precompileECRecover(evmc_message const& _message) noexcept; static evmc::result precompileSha256(evmc_message const& _message) noexcept; diff --git a/test/tools/ossfuzz/StackReuseCodegenFuzzer.cpp b/test/tools/ossfuzz/StackReuseCodegenFuzzer.cpp index 05dbc0e60..f99f25dae 100644 --- a/test/tools/ossfuzz/StackReuseCodegenFuzzer.cpp +++ b/test/tools/ossfuzz/StackReuseCodegenFuzzer.cpp @@ -49,6 +49,10 @@ static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; DEFINE_PROTO_FUZZER(Program const& _input) { + // Solidity creates an invalid instruction for subobjects, so we simply + // ignore them in this fuzzer. + if (_input.has_obj()) + return; bool filterStatefulInstructions = true; bool filterUnboundedLoops = true; ProtoConverter converter( @@ -95,17 +99,26 @@ DEFINE_PROTO_FUZZER(Program const& _input) // 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" ); - // Out of gas errors are problematic because it is possible that the - // optimizer makes them go away, making EVM state impossible to - // compare in general. - if (callResult.status_code == EVMC_OUT_OF_GAS) + // 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; unoptimizedState << hostContext.dumpState(deployResult.create_address); @@ -135,10 +148,34 @@ DEFINE_PROTO_FUZZER(Program const& _input) callResultOpt.status_code != EVMC_REVERT, "SolidityEvmoneInterface: EVM One reverted" ); + if (noInvalidInSource) + solAssert( + callResultOpt.status_code != EVMC_INVALID_INSTRUCTION, + "Invalid instruction." + ); + solAssert( + (callResultOpt.status_code == EVMC_SUCCESS || + (!noRevertInSource && callResultOpt.status_code == EVMC_REVERT) || + (!noInvalidInSource && callResultOpt.status_code == EVMC_INVALID_INSTRUCTION)), + "Optimised call failed." + ); ostringstream optimizedState; optimizedState << hostContext.dumpState(deployResultOpt.create_address); + + int64_t constexpr tolerance = 1000; + if (callResult.gas_left > callResultOpt.gas_left) + if (callResult.gas_left - callResultOpt.gas_left > tolerance) + { + cout << "Gas differential " << callResult.gas_left - callResultOpt.gas_left << endl; + cout << "Unoptimised bytecode" << endl; + cout << util::toHex(unoptimisedByteCode) << endl; + cout << "Optimised bytecode" << endl; + cout << util::toHex(optimisedByteCode) << endl; + solAssert(false, "Optimised code consumed more than +1000 gas."); + } + solAssert( unoptimizedState.str() == optimizedState.str(), - "Storage of unoptimised and optimised stack reused code do not match." + "State of unoptimised and optimised stack reused code do not match." ); } diff --git a/test/tools/ossfuzz/YulEvmoneInterface.cpp b/test/tools/ossfuzz/YulEvmoneInterface.cpp index 437585459..fe737993d 100644 --- a/test/tools/ossfuzz/YulEvmoneInterface.cpp +++ b/test/tools/ossfuzz/YulEvmoneInterface.cpp @@ -78,3 +78,21 @@ evmc_message YulEvmoneUtility::callMessage(evmc_address _address) call.kind = EVMC_CALL; return call; } + +bool YulEvmoneUtility::seriousCallError(evmc_status_code _code) +{ + if (_code == EVMC_OUT_OF_GAS) + return true; + else if (_code == EVMC_STACK_OVERFLOW) + return true; + else if (_code == EVMC_STACK_UNDERFLOW) + return true; + else if (_code == EVMC_INTERNAL_ERROR) + return true; + else if (_code == EVMC_UNDEFINED_INSTRUCTION) + return true; + else if (_code == EVMC_INVALID_MEMORY_ACCESS) + return true; + else + return false; +} diff --git a/test/tools/ossfuzz/YulEvmoneInterface.h b/test/tools/ossfuzz/YulEvmoneInterface.h index 780c50275..b7a430b60 100644 --- a/test/tools/ossfuzz/YulEvmoneInterface.h +++ b/test/tools/ossfuzz/YulEvmoneInterface.h @@ -54,5 +54,7 @@ struct YulEvmoneUtility static evmc::result deployCode(solidity::bytes const& _input, EVMHost& _host); /// @returns call message to be sent to @param _address. static evmc_message callMessage(evmc_address _address); + /// @returns true if call result indicates a serious error, false otherwise. + static bool seriousCallError(evmc_status_code _code); }; }