diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt
index 265533397..8293cb57b 100644
--- a/test/tools/ossfuzz/CMakeLists.txt
+++ b/test/tools/ossfuzz/CMakeLists.txt
@@ -16,6 +16,8 @@ if (OSSFUZZ)
yul_proto_diff_ossfuzz
yul_proto_diff_custom_mutate_ossfuzz
stack_reuse_codegen_ossfuzz
+ yul_evm_diff_ossfuzz
+ yul_evm_mutator_diff_ossfuzz
)
add_custom_target(ossfuzz_abiv2)
@@ -133,6 +135,69 @@ if (OSSFUZZ)
-Wno-suggest-destructor-override
)
+ add_executable(yul_evm_diff_ossfuzz
+ ../../libyul/YulOptimizerTestCommon.cpp
+ YulToEvmDifferentialFuzzer.cpp
+ protoToYul.cpp
+ yulProto.pb.cc
+ ../../EVMHost.cpp
+ YulEvmoneInterface.cpp
+ yulFuzzerCommon.cpp
+ )
+ target_include_directories(yul_evm_diff_ossfuzz PRIVATE
+ /usr/include/libprotobuf-mutator
+ )
+ target_link_libraries(yul_evm_diff_ossfuzz PRIVATE
+ yul
+ yulInterpreter
+ evmc
+ evmone-standalone
+ protobuf-mutator-libfuzzer.a
+ protobuf-mutator.a
+ protobuf.a
+ )
+ set_target_properties(yul_evm_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
+ target_compile_options(yul_evm_diff_ossfuzz PUBLIC
+ ${COMPILE_OPTIONS}
+ -Wno-sign-conversion
+ -Wno-inconsistent-missing-destructor-override
+ -Wno-unused-parameter
+ -Wno-zero-length-array
+ -Wno-suggest-destructor-override
+ )
+
+ add_executable(yul_evm_mutator_diff_ossfuzz
+ ../../libyul/YulOptimizerTestCommon.cpp
+ YulToEvmDifferentialFuzzer.cpp
+ protoToYul.cpp
+ yulProto.pb.cc
+ ../../EVMHost.cpp
+ YulEvmoneInterface.cpp
+ yulFuzzerCommon.cpp
+ protomutators/YulProtoMutator.cpp
+ )
+ target_include_directories(yul_evm_mutator_diff_ossfuzz PRIVATE
+ /usr/include/libprotobuf-mutator
+ )
+ target_link_libraries(yul_evm_mutator_diff_ossfuzz PRIVATE
+ yul
+ yulInterpreter
+ evmc
+ evmone-standalone
+ protobuf-mutator-libfuzzer.a
+ protobuf-mutator.a
+ protobuf.a
+ )
+ set_target_properties(yul_evm_mutator_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
+ target_compile_options(yul_evm_mutator_diff_ossfuzz PUBLIC
+ ${COMPILE_OPTIONS}
+ -Wno-sign-conversion
+ -Wno-inconsistent-missing-destructor-override
+ -Wno-unused-parameter
+ -Wno-zero-length-array
+ -Wno-suggest-destructor-override
+ )
+
add_executable(abiv2_proto_ossfuzz
../../EVMHost.cpp
abiV2ProtoFuzzer.cpp
diff --git a/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp b/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp
new file mode 100644
index 000000000..8f195f120
--- /dev/null
+++ b/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp
@@ -0,0 +1,246 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see .
+*/
+
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+
+using namespace solidity;
+using namespace solidity::test;
+using namespace solidity::test::fuzzer;
+using namespace solidity::yul;
+using namespace solidity::yul::test;
+using namespace solidity::yul::test::yul_fuzzer;
+using namespace solidity::langutil;
+using namespace solidity::util;
+using namespace std;
+
+namespace
+{
+#if 0
+/// @returns true if there are no recursive functions, false otherwise.
+bool recursiveFunctionHasUnreachableVariables(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;
+}
+#endif
+
+string optimiseYulSubObject(
+ shared_ptr _subObject,
+ Dialect const& _dialect,
+ bool _forceOldPipeline
+)
+{
+ YulOptimizerTestCommon optimizerTest(
+ _subObject,
+ _dialect,
+ _forceOldPipeline
+ );
+ // Run circular references pruner and then stack limit evader.
+ string step = "fullSuite";
+ optimizerTest.setStep(step);
+ shared_ptr astBlock = optimizerTest.run();
+ string optimisedSubObject = AsmPrinter{}(*astBlock);
+ string optimisedProgram = Whiskers(R"(
+ object "main" {
+ code {
+ codecopy(0, dataoffset("deployed"), datasize("deployed"))
+ return(0, datasize("deployed"))
+ }
+ object "deployed" {
+ code {
+
+ }
+ }
+ })")
+ ("fuzzerInput", optimisedSubObject)
+ .render();
+ return optimisedProgram;
+}
+
+void runOnce(
+ EVMHost& _host,
+ bytes& _deployCode,
+ ostringstream& _state,
+ string _yulSubObject
+)
+{
+ // Reset host before running optimised code.
+ _host.reset();
+ evmc::result deployResultOpt = YulEvmoneUtility{}.deployCode(_deployCode, _host);
+ solAssert(
+ deployResultOpt.status_code == EVMC_SUCCESS,
+ "Evmone: Contract creation failed"
+ );
+ auto callMessageOpt = YulEvmoneUtility{}.callMessage(deployResultOpt.create_address);
+ evmc::result callResultOpt = _host.call(callMessageOpt);
+ // Bail out if we ran out of gas.
+ if (callResultOpt.status_code == EVMC_OUT_OF_GAS)
+ return;
+ bool noRevertInSource = _yulSubObject.find("revert") == string::npos;
+ bool noInvalidInSource = _yulSubObject.find("invalid") == string::npos;
+ if (noRevertInSource)
+ solAssert(
+ 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)),
+ "Call failed."
+ );
+ _state << EVMHostPrinter{_host, deployResultOpt.create_address}.state();
+}
+}
+
+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;
+ ProtoConverter converter;
+ string fuzzerInput = converter.programToString(_input);
+ langutil::EVMVersion version;
+ EVMHost hostContext(version, evmone);
+
+ YulStringRepository::reset();
+
+
+ if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH"))
+ {
+ // Package test case into a sub-object
+ Whiskers yulObjectFormat(R"(
+ object "main" {
+ code {
+ codecopy(0, dataoffset("deployed"), datasize("deployed"))
+ return(0, datasize("deployed"))
+ }
+ object "deployed" {
+ code {
+
+ }
+ }
+ })");
+ string yul_source = yulObjectFormat("fuzzerInput", fuzzerInput).render();
+ ofstream of(dump_path);
+ of.write(yul_source.data(), static_cast(yul_source.size()));
+ }
+
+ solidity::frontend::OptimiserSettings settings = solidity::frontend::OptimiserSettings::none();
+ AssemblyStack yulStack(version, AssemblyStack::Language::StrictAssembly, settings);
+ solAssert(
+ yulStack.parseAndAnalyze("source", fuzzerInput),
+ "Parsing fuzzer generated input failed."
+ );
+ std::shared_ptr fuzzedYulObject = yulStack.parserResult();
+ Dialect const& dialect = EVMDialect::strictAssemblyForEVMObjects(version);
+ // Run Yul optimizer with old pipeline
+ string controlOptYul = optimiseYulSubObject(fuzzedYulObject, dialect, true);
+ bytes controlByteCode;
+ settings.optimizeStackAllocation = true;
+ settings.forceOldPipeline = true;
+ bool controlThrowsStackTooDeep = false;
+ try
+ {
+ controlByteCode = YulAssembler{version, settings, controlOptYul}.assemble();
+ }
+ catch (yul::StackTooDeepError const&)
+ {
+ controlThrowsStackTooDeep = true;
+ }
+
+ ostringstream controlState;
+ runOnce(hostContext, controlByteCode, controlState, fuzzerInput);
+ // Run Yul optimizer with new pipeline
+ string experimentOptYul = optimiseYulSubObject(fuzzedYulObject, dialect, false);
+ settings.forceOldPipeline = false;
+ bytes experimentByteCode;
+#if 0
+ bool recursiveFunction = recursiveFunctionHasUnreachableVariables(dialect, *fuzzedYulObject);
+ if (recursiveFunction)
+ cout << "Fuzzer print: Recursive function" << endl;
+#endif
+ experimentByteCode = YulAssembler{version, settings, experimentOptYul}.assemble();
+#if 0
+ catch (yul::StackTooDeepError const&)
+ {
+ // If there are recursive functions, stack too deep errors are expected
+ // even post stack evasion optimisation and hence ignored. Otherwise,
+ // they are rethrown for further investigation.
+ if (recursiveFunction)
+ return;
+ throw;
+ }
+#endif
+
+ ostringstream experimentState;
+ runOnce(hostContext, experimentByteCode, experimentState, fuzzerInput);
+
+ if (
+ !controlThrowsStackTooDeep &&
+ (controlState.str() != experimentState.str())
+ )
+ {
+ cout << controlState.str() << endl;
+ cout << experimentState.str() << endl;
+ solAssert(false, "State of unoptimised and optimised code do not match.");
+ }
+}
diff --git a/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp b/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp
index f1f3fbc93..444977d88 100644
--- a/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp
+++ b/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp
@@ -46,6 +46,18 @@ static LPMPostProcessor addStoreToZero(
}
);
+static LPMPostProcessor addStackVariables(
+ [](Block* _message, unsigned _seed)
+ {
+ if (_seed % YPM::s_highIP == 0)
+ {
+ MutationInfo m{_message, "Added variable to block."};
+ _message->add_statements()->set_allocated_multidecl(new MultiVarDecl());
+ _message->add_statements()->set_allocated_decl(new VarDecl());
+ _message->add_statements()->set_allocated_assignment(new AssignmentStatement());
+ }
+ }
+);
namespace
{