From 7c91a31cce040fb826631456c20d894a89c90905 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Sat, 9 May 2020 12:46:05 +0200 Subject: [PATCH] Add fuzzer for testing clean up of dirty bits via inline assembly --- .circleci/config.yml | 1 + liblangutil/Exceptions.h | 2 + libsolidity/codegen/ArrayUtils.cpp | 3 +- libsolidity/codegen/CompilerContext.cpp | 2 +- libsolidity/codegen/CompilerUtils.cpp | 29 +- libsolidity/codegen/ContractCompiler.cpp | 6 +- libsolidity/codegen/ExpressionCompiler.cpp | 4 +- libsolidity/codegen/LValue.cpp | 4 +- libyul/backends/evm/AsmCodeGen.cpp | 6 +- test/tools/ossfuzz/CMakeLists.txt | 69 +- test/tools/ossfuzz/protoToYul.cpp | 1049 ++++-------------- test/tools/ossfuzz/protoToYul.h | 124 +-- test/tools/ossfuzz/solArith.proto | 49 + test/tools/ossfuzz/solArithProtoFuzzer.cpp | 268 +++++ test/tools/ossfuzz/solarithprotoToSol.cpp | 180 +++ test/tools/ossfuzz/solarithprotoToSol.h | 121 ++ test/tools/ossfuzz/yulFuzzerCommon.cpp | 33 +- test/tools/ossfuzz/yulFuzzerCommon.h | 4 +- test/tools/ossfuzz/yulProto.proto | 243 +--- test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp | 2 +- test/tools/yulInterpreter/Interpreter.cpp | 12 + test/tools/yulInterpreter/Interpreter.h | 1 + 22 files changed, 1023 insertions(+), 1189 deletions(-) create mode 100644 test/tools/ossfuzz/solArith.proto create mode 100644 test/tools/ossfuzz/solArithProtoFuzzer.cpp create mode 100644 test/tools/ossfuzz/solarithprotoToSol.cpp create mode 100644 test/tools/ossfuzz/solarithprotoToSol.h diff --git a/.circleci/config.yml b/.circleci/config.yml index e1161e432..af0fe3cc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -96,6 +96,7 @@ defaults: - test/tools/ossfuzz/strictasm_diff_ossfuzz - test/tools/ossfuzz/strictasm_opt_ossfuzz - test/tools/ossfuzz/yul_proto_diff_ossfuzz + - test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz - test/tools/ossfuzz/yul_proto_ossfuzz # test result output directory diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index b8688afa6..b12400ab0 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -37,6 +37,8 @@ namespace solidity::langutil class Error; using ErrorList = std::vector>; +struct FuzzerError: virtual util::Exception {}; +struct StackTooDeepError: virtual util::Exception {}; struct CompilerError: virtual util::Exception {}; struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 089d0f5cf..4fb786073 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -219,8 +219,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons else solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... - solAssert( + assertThrow( 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + StackTooDeepError, "Stack too deep, try removing local variables." ); // fetch target storage reference diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 9431ecec6..738511203 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -405,7 +405,7 @@ void CompilerContext::appendInlineAssembly( stackDiff -= 1; if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_identifier.location) << util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") ); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 156cee565..9675e3a78 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory( // leave end_of_mem as dyn head pointer m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; dynPointers++; - solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables."); + assertThrow( + (argSize + dynPointers) < 16, + StackTooDeepError, + "Stack too deep, try using fewer variables." + ); } else { @@ -497,8 +501,9 @@ void CompilerUtils::encodeToMemory( if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) { // copy tail pointer (=mem_end - mem_start) to memory - solAssert( + assertThrow( (2 + dynPointers) <= 16, + StackTooDeepError, "Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables." ); m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; @@ -1241,7 +1246,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) // move variable starting from its top end in the stack if (stackPosition - size + 1 > 16) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_variable.location()) << util::errinfo_comment("Stack too deep, try removing local variables.") ); @@ -1251,7 +1256,11 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) { - solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); + assertThrow( + _stackDepth <= 16, + StackTooDeepError, + "Stack too deep, try removing local variables." + ); for (unsigned i = 0; i < _itemSize; ++i) m_context << dupInstruction(_stackDepth); } @@ -1273,14 +1282,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::rotateStackUp(unsigned _items) { - solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); + assertThrow( + _items - 1 <= 16, + StackTooDeepError, + "Stack too deep, try removing local variables." + ); for (unsigned i = 1; i < _items; ++i) m_context << swapInstruction(_items - i); } void CompilerUtils::rotateStackDown(unsigned _items) { - solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); + assertThrow( + _items - 1 <= 16, + StackTooDeepError, + "Stack too deep, try removing local variables." + ); for (unsigned i = 1; i < _items; ++i) m_context << swapInstruction(i); } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 319d7a7ad..c05011cf5 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -631,7 +631,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) if (stackLayout.size() > 17) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_function.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); @@ -795,7 +795,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) solAssert(variable->type()->sizeOnStack() == 1, ""); if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); @@ -828,7 +828,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1; if (stackDiff > 16 || stackDiff < 1) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") ); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a7ddb4b87..b4b574d20 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), ""); if (retSizeOnStack > 15) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_varDecl.location()) << errinfo_comment("Stack too deep.") ); @@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) { if (itemSize + lvalueSize > 16) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_assignment.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 3a04d6f92..798428098 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep, try removing local variables.") ); @@ -61,7 +61,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; if (stackDiff > 16) BOOST_THROW_EXCEPTION( - CompilerError() << + StackTooDeepError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep, try removing local variables.") ); diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index eefff5b18..89f3e59e6 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -218,10 +218,10 @@ void CodeGenerator::assemble( } catch (StackTooDeepError const& _e) { - yulAssert( + assertThrow( false, - "Stack too deep when compiling inline assembly" + - (_e.comment() ? ": " + *_e.comment() : ".") + langutil::StackTooDeepError, + "" ); } yulAssert(transform.stackErrors().empty(), "Stack errors present but not thrown."); diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 83543b9f0..8f4cf5f49 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -10,7 +10,12 @@ add_dependencies(ossfuzz if (OSSFUZZ) add_custom_target(ossfuzz_proto) - add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz) + add_dependencies(ossfuzz_proto + yul_proto_ossfuzz + yul_proto_diff_ossfuzz + yul_proto_diff_custom_mutate_ossfuzz + sol_arith_proto_ossfuzz + ) add_custom_target(ossfuzz_abiv2) add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz) @@ -60,6 +65,22 @@ if (OSSFUZZ) ) set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + add_executable(yul_proto_diff_custom_mutate_ossfuzz + yulProto_diff_ossfuzz.cpp + yulFuzzerCommon.cpp + protoToYul.cpp + yulProto.pb.cc + protomutators/YulProtoMutator.cpp + ) + target_include_directories(yul_proto_diff_custom_mutate_ossfuzz PRIVATE /usr/include/libprotobuf-mutator) + target_link_libraries(yul_proto_diff_custom_mutate_ossfuzz PRIVATE yul + yulInterpreter + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + ) + set_target_properties(yul_proto_diff_custom_mutate_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + add_executable(abiv2_proto_ossfuzz ../../EVMHost.cpp abiV2ProtoFuzzer.cpp @@ -78,6 +99,52 @@ if (OSSFUZZ) protobuf.a ) set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + + add_executable(sol_arith_proto_ossfuzz + solArithProtoFuzzer.cpp + solarithprotoToSol.cpp + solArith.pb.cc + abiV2FuzzerCommon.cpp + ../../EVMHost.cpp + protoToYul.cpp + yulProto.pb.cc + yulFuzzerCommon.cpp + ) + target_include_directories(sol_arith_proto_ossfuzz PRIVATE + /usr/include/libprotobuf-mutator + ) + target_link_libraries(sol_arith_proto_ossfuzz PRIVATE solidity libsolc + yulInterpreter + evmc + evmone-standalone + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + ) + set_target_properties(sol_arith_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + +# add_executable(sol_proto_ossfuzz +# solProtoFuzzer.cpp +# protoToSol.cpp +# solProto.pb.cc +# abiV2FuzzerCommon.cpp +# ../../EVMHost.cpp +# SolProtoAdaptor.cpp +# ) +# target_include_directories(sol_proto_ossfuzz PRIVATE +# /usr/include/libprotobuf-mutator +# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include +# /home/bhargava/work/github/solidity/deps/evmone/include +# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator +# ) +# target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc +# evmc +# evmone-standalone +# protobuf-mutator-libfuzzer.a +# protobuf-mutator.a +# protobuf.a +# ) +# set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) else() add_library(solc_opt_ossfuzz solc_opt_ossfuzz.cpp diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 8d5ced0a8..6df707b71 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -34,20 +34,78 @@ using namespace solidity::langutil; using namespace solidity::util; using namespace solidity; +vector ProtoConverter::createVars(unsigned _startIdx, unsigned _endIdx) +{ + yulAssert(_endIdx > _startIdx, "Proto fuzzer: Variable indices not in range"); + string varsStr = suffixedVariableNameList("x_", _startIdx, _endIdx); + m_output << varsStr; + vector varsVec; + boost::split( + varsVec, + varsStr, + boost::algorithm::is_any_of(", "), + boost::algorithm::token_compress_on + ); + + yulAssert( + varsVec.size() == (_endIdx - _startIdx), + "Proto fuzzer: Variable count mismatch during function definition" + ); + m_counter += varsVec.size(); + return varsVec; +} + + +string ProtoConverter::dummyExpression() +{ + string expression{}; + string location{}; + unsigned pseudoRandomNum = m_inputSize / 13; + if (varDeclAvailable()) + location = varRef(pseudoRandomNum); + switch (pseudoRandomNum % 4) + { + case 0: + if (location.empty()) + expression = "mload(0)"; + else + expression = Whiskers(R"(mload(mod(, 256)))")("loc", location).render(); + break; + case 1: + if (location.empty()) + expression = "sload(0)"; + else + expression = Whiskers(R"(sload(mod(, 256)))")("loc", location).render(); + break; + case 2: + if (location.empty()) + expression = "calldataload(0)"; + else + expression = Whiskers(R"(calldataload(mod(, 32)))")("loc", location).render(); + break; + case 3: + expression = dictionaryToken(); + break; + } + yulAssert(!expression.empty(), "Proto fuzzer: Invalid dummy expression"); + return expression; +} + string ProtoConverter::dictionaryToken(HexPrefix _p) { std::string token; - // If dictionary constant is requested while converting - // for loop condition, then return zero so that we don't - // generate infinite for loops. - if (m_inForCond) - token = "0"; - else - { - unsigned indexVar = m_inputSize * m_inputSize + counter(); - token = hexDictionary[indexVar % hexDictionary.size()]; - yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large"); - } + + // If dictionary constant is requested while converting + // for loop condition, then return zero so that we don't + // generate infinite for loops. + if (m_inForCond) + token = "0"; + else + { + unsigned indexVar = m_inputSize * m_inputSize + counter(); + token = hexDictionary[indexVar % hexDictionary.size()]; + yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large"); + } return _p == HexPrefix::Add ? "0x" + token : token; } @@ -87,29 +145,6 @@ string ProtoConverter::createAlphaNum(string const& _strBytes) return tmp; } -EVMVersion ProtoConverter::evmVersionMapping(Program_Version const& _ver) -{ - switch (_ver) - { - case Program::HOMESTEAD: - return EVMVersion::homestead(); - case Program::TANGERINE: - return EVMVersion::tangerineWhistle(); - case Program::SPURIOUS: - return EVMVersion::spuriousDragon(); - case Program::BYZANTIUM: - return EVMVersion::byzantium(); - case Program::CONSTANTINOPLE: - return EVMVersion::constantinople(); - case Program::PETERSBURG: - return EVMVersion::petersburg(); - case Program::ISTANBUL: - return EVMVersion::istanbul(); - case Program::BERLIN: - return EVMVersion::berlin(); - } -} - string ProtoConverter::visit(Literal const& _x) { switch (_x.literal_oneof_case()) @@ -172,25 +207,41 @@ bool ProtoConverter::varDeclAvailable() } } -bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type) -{ - return _type == FunctionCall::SINGLE || - (_type == FunctionCall::MULTIASSIGN && !varDeclAvailable()); -} - -void ProtoConverter::visit(VarRef const& _x) +string ProtoConverter::varRef(unsigned _index) { if (m_inFunctionDef) { // Ensure that there is at least one variable declaration to reference in function scope. yulAssert(m_currentFuncVars.size() > 0, "Proto fuzzer: No variables to reference."); - m_output << *m_currentFuncVars[_x.varnum() % m_currentFuncVars.size()]; + return *m_currentFuncVars[_index % m_currentFuncVars.size()]; } else { // Ensure that there is at least one variable declaration to reference in nested scopes. yulAssert(m_currentGlobalVars.size() > 0, "Proto fuzzer: No global variables to reference."); - m_output << *m_currentGlobalVars[_x.varnum() % m_currentGlobalVars.size()]; + return *m_currentGlobalVars[_index % m_currentGlobalVars.size()]; + } +} + +void ProtoConverter::visit(VarRef const& _x) +{ + m_output << varRef(_x.varnum()); +} + +void ProtoConverter::visit(FunctionExpr const& _x) +{ + vector> functionSet; + for (auto const& f: m_functionSigMap) + if (f.second.second == 1) + functionSet.emplace_back(f.first, f.second.first); + if (functionSet.size() > 0) + { + pair chosenFunction = functionSet[_x.index() % functionSet.size()]; + convertFunctionCall(_x, chosenFunction.first, chosenFunction.second); + } + else + { + m_output << dummyExpression(); } } @@ -203,18 +254,18 @@ void ProtoConverter::visit(Expression const& _x) // (because there are no variables in scope), we silently output a literal // expression from the optimizer dictionary. if (!varDeclAvailable()) - m_output << dictionaryToken(); + m_output << dummyExpression(); else visit(_x.varref()); break; case Expression::kCons: - // If literal expression describes for-loop condition - // then force it to zero, so we don't generate infinite - // for loops - if (m_inForCond) - m_output << "0"; - else - m_output << visit(_x.cons()); + // If literal expression describes for-loop condition + // then force it to zero, so we don't generate infinite + // for loops + if (m_inForCond) + m_output << "0"; + else + m_output << visit(_x.cons()); break; case Expression::kBinop: visit(_x.binop()); @@ -228,28 +279,11 @@ void ProtoConverter::visit(Expression const& _x) case Expression::kNop: visit(_x.nop()); break; - case Expression::kFuncExpr: - // FunctionCall must return a single value, otherwise - // we output a trivial expression "1". - if (_x.func_expr().ret() == FunctionCall::SINGLE) - visit(_x.func_expr()); - else - m_output << dictionaryToken(); - break; - case Expression::kLowcall: - visit(_x.lowcall()); - break; - case Expression::kCreate: - visit(_x.create()); - break; - case Expression::kUnopdata: - if (m_isObject) - visit(_x.unopdata()); - else - m_output << dictionaryToken(); + case Expression::kFuncexpr: + visit(_x.funcexpr()); break; case Expression::EXPR_ONEOF_NOT_SET: - m_output << dictionaryToken(); + m_output << dummyExpression(); break; } } @@ -258,13 +292,6 @@ void ProtoConverter::visit(BinaryOp const& _x) { BinaryOp_BOp op = _x.op(); - if ((op == BinaryOp::SHL || op == BinaryOp::SHR || op == BinaryOp::SAR) && - !m_evmVersion.hasBitwiseShifting()) - { - m_output << dictionaryToken(); - return; - } - switch (op) { case BinaryOp::ADD: @@ -301,15 +328,12 @@ void ProtoConverter::visit(BinaryOp const& _x) m_output << "gt"; break; case BinaryOp::SHR: - yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "shr"; break; case BinaryOp::SHL: - yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "shl"; break; case BinaryOp::SAR: - yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "sar"; break; case BinaryOp::SDIV: @@ -411,190 +435,32 @@ void ProtoConverter::scopeVariables(vector const& _varNames) } } -void ProtoConverter::visit(VarDecl const& _x) -{ - string varName = newVarName(); - m_output << "let " << varName << " := "; - visit(_x.expr()); - m_output << "\n"; - scopeVariables({varName}); -} - -void ProtoConverter::visit(MultiVarDecl const& _x) -{ - m_output << "let "; - vector varNames; - // We support up to 4 variables in a single - // declaration statement. - unsigned numVars = _x.num_vars() % 3 + 2; - string delimiter = ""; - for (unsigned i = 0; i < numVars; i++) - { - string varName = newVarName(); - varNames.push_back(varName); - m_output << delimiter << varName; - if (i == 0) - delimiter = ", "; - } - m_output << "\n"; - scopeVariables(varNames); -} - -void ProtoConverter::visit(TypedVarDecl const& _x) -{ - string varName = newVarName(); - m_output << "let " << varName; - switch (_x.type()) - { - case TypedVarDecl::BOOL: - m_output << ": bool := "; - visit(_x.expr()); - m_output << " : bool\n"; - break; - case TypedVarDecl::S8: - m_output << ": s8 := "; - visit(_x.expr()); - m_output << " : s8\n"; - break; - case TypedVarDecl::S32: - m_output << ": s32 := "; - visit(_x.expr()); - m_output << " : s32\n"; - break; - case TypedVarDecl::S64: - m_output << ": s64 := "; - visit(_x.expr()); - m_output << " : s64\n"; - break; - case TypedVarDecl::S128: - m_output << ": s128 := "; - visit(_x.expr()); - m_output << " : s128\n"; - break; - case TypedVarDecl::S256: - m_output << ": s256 := "; - visit(_x.expr()); - m_output << " : s256\n"; - break; - case TypedVarDecl::U8: - m_output << ": u8 := "; - visit(_x.expr()); - m_output << " : u8\n"; - break; - case TypedVarDecl::U32: - m_output << ": u32 := "; - visit(_x.expr()); - m_output << " : u32\n"; - break; - case TypedVarDecl::U64: - m_output << ": u64 := "; - visit(_x.expr()); - m_output << " : u64\n"; - break; - case TypedVarDecl::U128: - m_output << ": u128 := "; - visit(_x.expr()); - m_output << " : u128\n"; - break; - case TypedVarDecl::U256: - m_output << ": u256 := "; - visit(_x.expr()); - m_output << " : u256\n"; - break; - } - // If we are inside a for-init block, there are two places - // where the visited vardecl may have been defined: - // - directly inside the for-init block - // - inside a block within the for-init block - // In the latter case, we don't scope extend. - if (m_inFunctionDef) - { - // Variables declared directly in for-init block - // are tracked separately because their scope - // extends beyond the block they are defined in - // to the rest of the for-loop statement. - if (m_inForInitScope && m_forInitScopeExtEnabled) - { - yulAssert( - !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), - "Proto fuzzer: Invalid operation" - ); - m_funcForLoopInitVars.back().back().push_back(varName); - } - else - { - yulAssert( - !m_funcVars.empty() && !m_funcVars.back().empty(), - "Proto fuzzer: Invalid operation" - ); - m_funcVars.back().back().push_back(varName); - } - } - else - { - if (m_inForInitScope && m_forInitScopeExtEnabled) - { - yulAssert( - !m_globalForLoopInitVars.empty(), - "Proto fuzzer: Invalid operation" - ); - m_globalForLoopInitVars.back().push_back(varName); - } - else - { - yulAssert( - !m_globalVars.empty(), - "Proto fuzzer: Invalid operation" - ); - m_globalVars.back().push_back(varName); - } - } -} - void ProtoConverter::visit(UnaryOp const& _x) { UnaryOp_UOp op = _x.op(); - // Replace calls to extcodehash on unsupported EVMs with a dictionary - // token. - if (op == UnaryOp::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) - { - m_output << dictionaryToken(); - return; - } - switch (op) { case UnaryOp::NOT: m_output << "not"; break; case UnaryOp::MLOAD: - m_output << "mload"; + m_output << "mload(mod"; break; case UnaryOp::SLOAD: - m_output << "sload"; + m_output << "sload(mod"; break; case UnaryOp::ISZERO: m_output << "iszero"; break; case UnaryOp::CALLDATALOAD: - m_output << "calldataload"; - break; - case UnaryOp::EXTCODESIZE: - m_output << "extcodesize"; - break; - case UnaryOp::EXTCODEHASH: - m_output << "extcodehash"; - break; - case UnaryOp::BALANCE: - m_output << "balance"; - break; - case UnaryOp::BLOCKHASH: - m_output << "blockhash"; + m_output << "calldataload(mod"; break; } m_output << "("; visit(_x.operand()); + if (op == UnaryOp::MLOAD || op == UnaryOp::SLOAD || op == UnaryOp::CALLDATALOAD) + m_output << ", 256)"; m_output << ")"; } @@ -622,28 +488,11 @@ void ProtoConverter::visit(NullaryOp const& _x) { switch (_x.op()) { - case NullaryOp::PC: - m_output << "pc()"; - break; - case NullaryOp::MSIZE: - m_output << "msize()"; - break; - case NullaryOp::GAS: - m_output << "gas()"; - break; case NullaryOp::CALLDATASIZE: m_output << "calldatasize()"; break; - case NullaryOp::CODESIZE: - m_output << "codesize()"; - break; case NullaryOp::RETURNDATASIZE: - // If evm supports returndatasize, we generate it. Otherwise, - // we output a dictionary token. - if (m_evmVersion.supportsReturndata()) - m_output << "returndatasize()"; - else - m_output << dictionaryToken(); + m_output << "returndatasize()"; break; case NullaryOp::ADDRESS: m_output << "address()"; @@ -663,9 +512,6 @@ void ProtoConverter::visit(NullaryOp const& _x) case NullaryOp::COINBASE: m_output << "coinbase()"; break; - case NullaryOp::TIMESTAMP: - m_output << "timestamp()"; - break; case NullaryOp::NUMBER: m_output << "number()"; break; @@ -676,140 +522,10 @@ void ProtoConverter::visit(NullaryOp const& _x) m_output << "gaslimit()"; break; case NullaryOp::SELFBALANCE: - // Replace calls to selfbalance() on unsupported EVMs with a dictionary - // token. - if (m_evmVersion.hasSelfBalance()) - m_output << "selfbalance()"; - else - m_output << dictionaryToken(); + m_output << "selfbalance()"; break; case NullaryOp::CHAINID: - // Replace calls to chainid() on unsupported EVMs with a dictionary - // token. - if (m_evmVersion.hasChainID()) - m_output << "chainid()"; - else - m_output << dictionaryToken(); - break; - } -} - -void ProtoConverter::visit(CopyFunc const& _x) -{ - CopyFunc_CopyType type = _x.ct(); - - // datacopy() is valid only if we are inside - // a Yul object. - if (type == CopyFunc::DATA && !m_isObject) - return; - - // We don't generate code if the copy function is returndatacopy - // and the underlying evm does not support it. - if (type == CopyFunc::RETURNDATA && !m_evmVersion.supportsReturndata()) - return; - - switch (type) - { - case CopyFunc::CALLDATA: - m_output << "calldatacopy"; - break; - case CopyFunc::CODE: - m_output << "codecopy"; - break; - case CopyFunc::RETURNDATA: - yulAssert(m_evmVersion.supportsReturndata(), "Proto fuzzer: Invalid evm version"); - m_output << "returndatacopy"; - break; - case CopyFunc::DATA: - m_output << "datacopy"; - break; - } - m_output << "("; - visit(_x.target()); - m_output << ", "; - visit(_x.source()); - m_output << ", "; - visit(_x.size()); - m_output << ")\n"; -} - -void ProtoConverter::visit(ExtCodeCopy const& _x) -{ - m_output << "extcodecopy"; - m_output << "("; - visit(_x.addr()); - m_output << ", "; - visit(_x.target()); - m_output << ", "; - visit(_x.source()); - m_output << ", "; - visit(_x.size()); - m_output << ")\n"; -} - -void ProtoConverter::visit(LogFunc const& _x) -{ - switch (_x.num_topics()) - { - case LogFunc::ZERO: - m_output << "log0"; - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ")\n"; - break; - case LogFunc::ONE: - m_output << "log1"; - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ", "; - visit(_x.t1()); - m_output << ")\n"; - break; - case LogFunc::TWO: - m_output << "log2"; - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ", "; - visit(_x.t1()); - m_output << ", "; - visit(_x.t2()); - m_output << ")\n"; - break; - case LogFunc::THREE: - m_output << "log3"; - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ", "; - visit(_x.t1()); - m_output << ", "; - visit(_x.t2()); - m_output << ", "; - visit(_x.t3()); - m_output << ")\n"; - break; - case LogFunc::FOUR: - m_output << "log4"; - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ", "; - visit(_x.t1()); - m_output << ", "; - visit(_x.t2()); - m_output << ", "; - visit(_x.t3()); - m_output << ", "; - visit(_x.t4()); - m_output << ")\n"; + m_output << "chainid()"; break; } } @@ -822,7 +538,8 @@ void ProtoConverter::visit(AssignmentStatement const& _x) m_output << "\n"; } -void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _numInputParams) +template +void ProtoConverter::visitFunctionInputParams(T const& _x, unsigned _numInputParams) { // We reverse the order of function input visits since it helps keep this switch case concise. switch (_numInputParams) @@ -830,18 +547,18 @@ void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _ case 4: visit(_x.in_param4()); m_output << ", "; - BOOST_FALLTHROUGH; + [[fallthrough]];; case 3: visit(_x.in_param3()); m_output << ", "; - BOOST_FALLTHROUGH; + [[fallthrough]];; case 2: visit(_x.in_param2()); m_output << ", "; - BOOST_FALLTHROUGH; + [[fallthrough]];; case 1: visit(_x.in_param1()); - BOOST_FALLTHROUGH; + [[fallthrough]];; case 0: break; default: @@ -850,51 +567,26 @@ void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _ } } -bool ProtoConverter::functionValid(FunctionCall_Returns _type, unsigned _numOutParams) -{ - switch (_type) - { - case FunctionCall::ZERO: - return _numOutParams == 0; - case FunctionCall::SINGLE: - return _numOutParams == 1; - case FunctionCall::MULTIDECL: - case FunctionCall::MULTIASSIGN: - return _numOutParams > 1; - } -} - +template void ProtoConverter::convertFunctionCall( - FunctionCall const& _x, + T const& _x, std::string _name, unsigned _numInParams, - bool _newLine + bool _newline ) { m_output << _name << "("; visitFunctionInputParams(_x, _numInParams); m_output << ")"; - if (_newLine) + if (_newline) m_output << "\n"; } -vector ProtoConverter::createVarDecls(unsigned _start, unsigned _end, bool _isAssignment) -{ - m_output << "let "; - vector varsVec = createVars(_start, _end); - if (_isAssignment) - m_output << " := "; - else - m_output << "\n"; - return varsVec; -} - void ProtoConverter::visit(FunctionCall const& _x) { bool functionAvailable = m_functionSigMap.size() > 0; unsigned numInParams, numOutParams; string funcName; - FunctionCall_Returns funcType = _x.ret(); if (functionAvailable) { yulAssert(m_functions.size() > 0, "Proto fuzzer: No function in scope"); @@ -905,177 +597,71 @@ void ProtoConverter::visit(FunctionCall const& _x) } else { - // If there are no functions available, calls to functions that - // return a single value may be replaced by a dictionary token. - if (funcType == FunctionCall::SINGLE) - m_output << dictionaryToken(); - return; - } - - // If function selected for function call does not meet interface - // requirements (num output values) for the function type - // specified, then we return early unless it is a function call - // that returns a single value (which may be replaced by a - // dictionary token. - if (!functionValid(funcType, numOutParams)) - { - if (funcType == FunctionCall::SINGLE) - m_output << dictionaryToken(); return; } // If we are here, it means that we have at least one valid // function for making the function call - switch (funcType) + switch (numOutParams) { - case FunctionCall::ZERO: - convertFunctionCall(_x, funcName, numInParams); + case 0: + convertFunctionCall(_x, funcName, numInParams, /*newline=*/true); break; - case FunctionCall::SINGLE: - // Since functions that return a single value are used as expressions - // we do not print a newline because it is done by the expression - // visitor. - convertFunctionCall(_x, funcName, numInParams, /*newLine=*/false); - break; - case FunctionCall::MULTIDECL: - { - // Ensure that the chosen function returns at most 4 values - yulAssert( - numOutParams <= 4, - "Proto fuzzer: Function call with too many output params encountered." - ); - - // Obtain variable name suffix - unsigned startIdx = counter(); - vector varsVec = createVarDecls( - startIdx, - startIdx + numOutParams, - /*isAssignment=*/true - ); - - // Create RHS of multi var decl - convertFunctionCall(_x, funcName, numInParams); - // Add newly minted vars in the multidecl statement to current scope - addVarsToScope(varsVec); - break; - } - case FunctionCall::MULTIASSIGN: - // Ensure that the chosen function returns at most 4 values - yulAssert( - numOutParams <= 4, - "Proto fuzzer: Function call with too many output params encountered." - ); - - // Convert LHS of multi assignment - // We reverse the order of out param visits since the order does not matter. - // This helps reduce the size of this switch statement. - switch (numOutParams) + case 1: + // assignment + if (varDeclAvailable()) { - case 4: - visit(_x.out_param4()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 3: - visit(_x.out_param3()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 2: - visit(_x.out_param2()); - m_output << ", "; visit(_x.out_param1()); - break; - default: - yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); - break; + m_output << " := "; + convertFunctionCall(_x, funcName, numInParams, /*newline=*/true); } - m_output << " := "; - - // Convert RHS of multi assignment - convertFunctionCall(_x, funcName, numInParams); break; - } -} - -void ProtoConverter::visit(LowLevelCall const& _x) -{ - LowLevelCall_Type type = _x.callty(); - - // Generate staticcall if it is supported by the underlying evm - if (type == LowLevelCall::STATICCALL && !m_evmVersion.hasStaticCall()) + case 2: + [[fallthrough]]; + case 3: + [[fallthrough]]; + case 4: { - // Since staticcall is supposed to return 0 on success and 1 on - // failure, we can use counter value to emulate it - m_output << ((counter() % 2) ? "0" : "1"); - return; - } + // Ensure that the chosen function returns at most 4 values + yulAssert( + numOutParams <= 4, + "Proto fuzzer: Function call with too many output params encountered." + ); + { + // If we do not have variables to assign to, bail out + if (!varDeclAvailable()) + return; - switch (type) - { - case LowLevelCall::CALL: - m_output << "call("; - break; - case LowLevelCall::CALLCODE: - m_output << "callcode("; - break; - case LowLevelCall::DELEGATECALL: - m_output << "delegatecall("; - break; - case LowLevelCall::STATICCALL: - yulAssert(m_evmVersion.hasStaticCall(), "Proto fuzzer: Invalid evm version"); - m_output << "staticcall("; - break; - } - visit(_x.gas()); - m_output << ", "; - visit(_x.addr()); - m_output << ", "; - if (type == LowLevelCall::CALL || type == LowLevelCall::CALLCODE) - { - visit(_x.wei()); - m_output << ", "; - } - visit(_x.in()); - m_output << ", "; - visit(_x.insize()); - m_output << ", "; - visit(_x.out()); - m_output << ", "; - visit(_x.outsize()); - m_output << ")"; -} + // Convert LHS of multi assignment + // We reverse the order of out param visits since the order does not matter. + // This helps reduce the size of this switch statement. + switch (numOutParams) + { + case 4: + visit(_x.out_param4()); + m_output << ", "; + [[fallthrough]]; + case 3: + visit(_x.out_param3()); + m_output << ", "; + [[fallthrough]]; + case 2: + visit(_x.out_param2()); + m_output << ", "; + visit(_x.out_param1()); + break; + default: + yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); + break; + } + m_output << " := "; -void ProtoConverter::visit(Create const& _x) -{ - Create_Type type = _x.createty(); - - // Replace a call to create2 on unsupported EVMs with a dictionary - // token. - if (type == Create::CREATE2 && !m_evmVersion.hasCreate2()) - { - m_output << dictionaryToken(); - return; - } - - switch (type) - { - case Create::CREATE: - m_output << "create("; - break; - case Create::CREATE2: - m_output << "create2("; + // Convert RHS of multi assignment + convertFunctionCall(_x, funcName, numInParams, /*newline=*/true); + } break; } - visit(_x.wei()); - m_output << ", "; - visit(_x.position()); - m_output << ", "; - visit(_x.size()); - if (type == Create::CREATE2) - { - m_output << ", "; - visit(_x.value()); } - m_output << ")"; } void ProtoConverter::visit(IfStmt const& _x) @@ -1083,7 +669,7 @@ void ProtoConverter::visit(IfStmt const& _x) m_output << "if "; visit(_x.cond()); m_output << " "; - visit(_x.if_body()); + visit(_x.block()); } void ProtoConverter::visit(StoreFunc const& _x) @@ -1091,17 +677,17 @@ void ProtoConverter::visit(StoreFunc const& _x) switch (_x.st()) { case StoreFunc::MSTORE: - m_output << "mstore("; + m_output << "mstore(mod("; break; case StoreFunc::SSTORE: - m_output << "sstore("; + m_output << "sstore(mod("; break; case StoreFunc::MSTORE8: - m_output << "mstore8("; + m_output << "mstore8(mod("; break; } visit(_x.loc()); - m_output << ", "; + m_output << ", 256), "; visit(_x.val()); m_output << ")\n"; } @@ -1126,7 +712,7 @@ void ProtoConverter::visit(ForStmt const& _x) m_inForCond = false; visit(_x.for_post()); m_inForBodyScope = true; - visit(_x.for_body()); + visit(_x.block()); m_inForBodyScope = wasInForBody; m_inForInitScope = wasInForInit; if (m_inFunctionDef) @@ -1160,7 +746,7 @@ void ProtoConverter::visit(BoundedForStmt const& _x) bool wasInForInit = m_inForInitScope; m_inForBodyScope = true; m_inForInitScope = false; - visit(_x.for_body()); + visit(_x.block()); // Restore previous for body scope and init m_inForBodyScope = wasInForBody; m_inForInitScope = wasInForInit; @@ -1228,13 +814,13 @@ void ProtoConverter::visit(CaseStmt const& _x) if (isUnique) { m_output << "case " << literal << " "; - visit(_x.case_block()); + visit(_x.block()); } } void ProtoConverter::visit(SwitchStmt const& _x) { - if (_x.case_stmt_size() > 0 || _x.has_default_block()) + if (_x.case_stmt_size() > 0 || _x.has_block()) { std::set s; m_switchLiteralSetPerScope.push(s); @@ -1247,95 +833,18 @@ void ProtoConverter::visit(SwitchStmt const& _x) m_switchLiteralSetPerScope.pop(); - if (_x.has_default_block()) + if (_x.has_block()) { m_output << "default "; - visit(_x.default_block()); + visit(_x.block()); } } } -void ProtoConverter::visit(StopInvalidStmt const& _x) -{ - switch (_x.stmt()) - { - case StopInvalidStmt::STOP: - m_output << "stop()\n"; - break; - case StopInvalidStmt::INVALID: - m_output << "invalid()\n"; - break; - } -} - -void ProtoConverter::visit(RetRevStmt const& _x) -{ - switch (_x.stmt()) - { - case RetRevStmt::RETURN: - m_output << "return"; - break; - case RetRevStmt::REVERT: - m_output << "revert"; - break; - } - m_output << "("; - visit(_x.pos()); - m_output << ", "; - visit(_x.size()); - m_output << ")\n"; -} - -void ProtoConverter::visit(SelfDestructStmt const& _x) -{ - m_output << "selfdestruct"; - m_output << "("; - visit(_x.addr()); - m_output << ")\n"; -} - -void ProtoConverter::visit(TerminatingStmt const& _x) -{ - switch (_x.term_oneof_case()) - { - case TerminatingStmt::kStopInvalid: - visit(_x.stop_invalid()); - break; - case TerminatingStmt::kRetRev: - visit(_x.ret_rev()); - break; - case TerminatingStmt::kSelfDes: - visit(_x.self_des()); - break; - case TerminatingStmt::TERM_ONEOF_NOT_SET: - break; - } -} - -void ProtoConverter::visit(UnaryOpData const& _x) -{ - switch (_x.op()) - { - case UnaryOpData::SIZE: - m_output << Whiskers(R"(datasize(""))") - ("id", getObjectIdentifier(_x.identifier())) - .render(); - break; - case UnaryOpData::OFFSET: - m_output << Whiskers(R"(dataoffset(""))") - ("id", getObjectIdentifier(_x.identifier())) - .render(); - break; - } -} - void ProtoConverter::visit(Statement const& _x) { switch (_x.stmt_oneof_case()) { - case Statement::kDecl: - visit(_x.decl()); - break; case Statement::kAssignment: // Create an assignment statement only if there is at least one variable // declaration that is in scope. @@ -1343,7 +852,7 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.assignment()); break; case Statement::kIfstmt: - if (_x.ifstmt().if_body().statements_size() > 0) + if (_x.ifstmt().block().statements_size() > 0) visit(_x.ifstmt()); break; case Statement::kStorageFunc: @@ -1354,11 +863,11 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.blockstmt()); break; case Statement::kForstmt: - if (_x.forstmt().for_body().statements_size() > 0) + if (_x.forstmt().block().statements_size() > 0) visit(_x.forstmt()); break; case Statement::kBoundedforstmt: - if (_x.boundedforstmt().for_body().statements_size() > 0) + if (_x.boundedforstmt().block().statements_size() > 0) visit(_x.boundedforstmt()); break; case Statement::kSwitchstmt: @@ -1372,22 +881,7 @@ void ProtoConverter::visit(Statement const& _x) if (m_inForBodyScope) m_output << "continue\n"; break; - case Statement::kLogFunc: - visit(_x.log_func()); - break; - case Statement::kCopyFunc: - visit(_x.copy_func()); - break; - case Statement::kExtcodeCopy: - visit(_x.extcode_copy()); - break; - case Statement::kTerminatestmt: - visit(_x.terminatestmt()); - break; case Statement::kFunctioncall: - // Return early if a function call cannot be created - if (functionCallNotPossible(_x.functioncall().ret())) - return; visit(_x.functioncall()); break; case Statement::kFuncdef: @@ -1402,9 +896,6 @@ void ProtoConverter::visit(Statement const& _x) if (m_inFunctionDef) visit(_x.leave()); break; - case Statement::kMultidecl: - visit(_x.multidecl()); - break; case Statement::STMT_ONEOF_NOT_SET: break; } @@ -1565,6 +1056,24 @@ void ProtoConverter::addVarsToScope(vector const& _vars) } } +void ProtoConverter::storeGlobals() +{ + if (m_globalVars.size() == 1) + { + unsigned i = 0; + for (auto const& v: m_globalVars.back()) + { + string loc = to_string(i * 32); + m_output << Whiskers(R"(mstore(, ))") + ("loc", loc) + ("var", v) + ("endl", "\n") + .render(); + i++; + } + } +} + void ProtoConverter::visit(Block const& _x) { openBlockScope(); @@ -1600,27 +1109,6 @@ void ProtoConverter::visit(Block const& _x) closeBlockScope(); } -vector ProtoConverter::createVars(unsigned _startIdx, unsigned _endIdx) -{ - yulAssert(_endIdx > _startIdx, "Proto fuzzer: Variable indices not in range"); - string varsStr = suffixedVariableNameList("x_", _startIdx, _endIdx); - m_output << varsStr; - vector varsVec; - boost::split( - varsVec, - varsStr, - boost::algorithm::is_any_of(", "), - boost::algorithm::token_compress_on - ); - - yulAssert( - varsVec.size() == (_endIdx - _startIdx), - "Proto fuzzer: Variable count mismatch during function definition" - ); - m_counter += varsVec.size(); - return varsVec; -} - void ProtoConverter::registerFunction(FunctionDef const* _x) { unsigned numInParams = _x->num_input_params() % s_modInputParams; @@ -1654,7 +1142,7 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams) unsigned diceValue = counter() % 4; // Pseudo-randomly choose one of the first ten 32-byte // aligned slots. - string slot = to_string((counter() % 10) * 32); + string slot = to_string((counter() % 8) * 32); switch (diceValue) { case 0: @@ -1667,9 +1155,9 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams) m_output << "sload(" << slot << ")"; break; case 3: - // Call to dictionaryToken() automatically picks a token + // Call to dummyExpression() automatically picks a token // at a pseudo-random location. - m_output << dictionaryToken(); + m_output << dummyExpression(); break; } if (i < _numInParams - 1) @@ -1694,42 +1182,6 @@ void ProtoConverter::saveFunctionCallOutput(vector const& _varsVec) } } -void ProtoConverter::createFunctionCall( - string _funcName, - unsigned _numInParams, - unsigned _numOutParams -) -{ - vector varsVec{}; - if (_numOutParams > 0) - { - unsigned startIdx = counter(); - // Prints the following to output stream "let x_i,...,x_n := " - varsVec = createVarDecls( - startIdx, - startIdx + _numOutParams, - /*isAssignment=*/true - ); - } - - // Call the function with the correct number of input parameters - m_output << _funcName << "("; - if (_numInParams > 0) - fillFunctionCallInput(_numInParams); - m_output << ")\n"; - - if (!varsVec.empty()) - { - // Save values returned by function so that they are reflected - // in the interpreter trace. - saveFunctionCallOutput(varsVec); - // Add newly minted vars to current scope - addVarsToScope(varsVec); - } - else - yulAssert(_numOutParams == 0, "Proto fuzzer: Function return value not saved"); -} - void ProtoConverter::createFunctionDefAndCall( FunctionDef const& _x, unsigned _numInParams, @@ -1793,8 +1245,6 @@ void ProtoConverter::createFunctionDefAndCall( !m_inForInitScope, "Proto fuzzer: Trying to create function call inside a for-init block" ); - if (_x.force_call()) - createFunctionCall(funcName, _numInParams, _numOutParams); } void ProtoConverter::visit(FunctionDef const& _x) @@ -1816,92 +1266,15 @@ void ProtoConverter::visit(LeaveStmt const&) m_output << "leave\n"; } -string ProtoConverter::getObjectIdentifier(unsigned _x) -{ - unsigned currentId = currentObjectId(); - yulAssert(m_objectScopeTree.size() > currentId, "Proto fuzzer: Error referencing object"); - std::vector objectIdsInScope = m_objectScopeTree[currentId]; - return objectIdsInScope[_x % objectIdsInScope.size()]; -} - -void ProtoConverter::visit(Code const& _x) -{ - m_output << "code {\n"; - visit(_x.block()); - m_output << "}\n"; -} - -void ProtoConverter::visit(Data const& _x) -{ - // TODO: Generate random data block identifier - m_output << "data \"" << s_dataIdentifier << "\" hex\"" << createHex(_x.hex()) << "\"\n"; -} - -void ProtoConverter::visit(Object const& _x) -{ - // object "object" { - // ... - // } - m_output << "object " << newObjectId() << " {\n"; - visit(_x.code()); - if (_x.has_data()) - visit(_x.data()); - if (_x.has_sub_obj()) - visit(_x.sub_obj()); - m_output << "}\n"; -} - -void ProtoConverter::buildObjectScopeTree(Object const& _x) -{ - // Identifies object being visited - string objectId = newObjectId(false); - vector node{objectId}; - if (_x.has_data()) - node.push_back(s_dataIdentifier); - if (_x.has_sub_obj()) - { - // Identifies sub object whose numeric suffix is - // m_objectId - string subObjectId = "object" + to_string(m_objectId); - node.push_back(subObjectId); - // TODO: Add sub-object to object's ancestors once - // nested access is implemented. - m_objectScopeTree.push_back(node); - buildObjectScopeTree(_x.sub_obj()); - } - else - m_objectScopeTree.push_back(node); -} - void ProtoConverter::visit(Program const& _x) { // Initialize input size m_inputSize = _x.ByteSizeLong(); - - // Record EVM Version - m_evmVersion = evmVersionMapping(_x.ver()); - - // Program is either a Yul object or a block of - // statements. - switch (_x.program_oneof_case()) - { - case Program::kBlock: - m_output << "{\n"; - visit(_x.block()); - m_output << "}\n"; - break; - case Program::kObj: - m_isObject = true; - buildObjectScopeTree(_x.obj()); - // Reset object id counter - m_objectId = 0; - visit(_x.obj()); - break; - case Program::PROGRAM_ONEOF_NOT_SET: - // {} is a trivial Yul program - m_output << "{}"; - break; - } + m_output << "{\n"; + visit(_x.block()); + yulAssert(m_globalVars.size() <= 1, "Yul proto converter: Invalid global scope size"); + storeGlobals(); + m_output << "}\n"; } string ProtoConverter::programToString(Program const& _input) diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 9f7240f5e..817e7a771 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -38,10 +38,14 @@ namespace solidity::yul::test::yul_fuzzer class ProtoConverter { public: - ProtoConverter() + ProtoConverter(unsigned _numGlobalVars) { m_funcVars = std::vector>>{}; m_globalVars = std::vector>{}; + if (_numGlobalVars > 0) + m_globalVars.push_back({}); + for (unsigned i = 0; i < _numGlobalVars; i++) + m_globalVars.back().push_back("v" + std::to_string(i)); m_inForBodyScope = false; m_inForInitScope = false; m_inForCond = false; @@ -50,20 +54,12 @@ public: m_counter = 0; m_inputSize = 0; m_inFunctionDef = false; - m_objectId = 0; - m_isObject = false; m_forInitScopeExtEnabled = true; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; std::string programToString(Program const& _input); - /// Returns evm version - solidity::langutil::EVMVersion version() - { - return m_evmVersion; - } - private: void visit(BinaryOp const&); @@ -75,10 +71,8 @@ private: std::string visit(Literal const&); void visit(VarRef const&); + void visit(FunctionExpr const& _x); void visit(Expression const&); - void visit(VarDecl const&); - void visit(MultiVarDecl const&); - void visit(TypedVarDecl const&); void visit(UnaryOp const&); void visit(AssignmentStatement const&); void visit(IfStmt const&); @@ -90,23 +84,10 @@ private: void visit(SwitchStmt const&); void visit(TernaryOp const&); void visit(NullaryOp const&); - void visit(LogFunc const&); - void visit(CopyFunc const&); - void visit(ExtCodeCopy const&); - void visit(StopInvalidStmt const&); - void visit(RetRevStmt const&); - void visit(SelfDestructStmt const&); - void visit(TerminatingStmt const&); void visit(FunctionCall const&); void visit(FunctionDef const&); void visit(PopStmt const&); void visit(LeaveStmt const&); - void visit(LowLevelCall const&); - void visit(Create const&); - void visit(UnaryOpData const&); - void visit(Object const&); - void visit(Data const&); - void visit(Code const&); void visit(Program const&); /// Creates a new block scope. @@ -140,8 +121,10 @@ private: Multiple }; - void visitFunctionInputParams(FunctionCall const&, unsigned); + template + void visitFunctionInputParams(T const&, unsigned); void createFunctionDefAndCall(FunctionDef const&, unsigned, unsigned); + void storeGlobals(); /// Convert function type to a string to be used while naming a /// function that is created by a function declaration statement. @@ -169,46 +152,19 @@ private: /// in scope bool varDeclAvailable(); - /// Return true if a function call cannot be made, false otherwise. - /// @param _type is an enum denoting the type of function call. It - /// can be one of NONE, SINGLE, MULTIDECL, MULTIASSIGN. - /// NONE -> Function call does not return a value - /// SINGLE -> Function call returns a single value - /// MULTIDECL -> Function call returns more than one value - /// and it is used to create a multi declaration - /// statement - /// MULTIASSIGN -> Function call returns more than one value - /// and it is used to create a multi assignment - /// statement - /// @return True if the function call cannot be created for one of the - /// following reasons - // - It is a SINGLE function call (we reserve SINGLE functions for - // expressions) - // - It is a MULTIASSIGN function call and we do not have any - // variables available for assignment. - bool functionCallNotPossible(FunctionCall_Returns _type); - - /// Checks if function call of type @a _type returns the correct number - /// of values. - /// @param _type Function call type of the function being checked - /// @param _numOutParams Number of values returned by the function - /// being checked - /// @return true if the function returns the correct number of values, - /// false otherwise - bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams); - - /// Converts protobuf function call to a Yul function call and appends + /// Converts protobuf function call to a yul function call and appends /// it to output stream. /// @param _x Protobuf function call /// @param _name Function name /// @param _numInParams Number of input arguments accepted by function - /// @param _newLine Flag that prints a new line to the output stream if - /// true. Default value for the flag is true. + /// @param _newline Boolean flag that is true if new line to be printed after + /// function call, false otherwise + template void convertFunctionCall( - FunctionCall const& _x, + T const& _x, std::string _name, unsigned _numInParams, - bool _newLine = true + bool _newline = false ); /// Prints a Yul formatted variable declaration statement to the output @@ -270,11 +226,6 @@ private: /// Removes entry from m_functionMap and m_functionName void updateFunctionMaps(std::string const& _x); - /// Build a tree of objects that contains the object/data - /// identifiers that are in scope in a given object. - /// @param _x root object of the Yul protobuf specification. - void buildObjectScopeTree(Object const& _x); - /// Returns a pseudo-random dictionary token. /// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not /// @return Dictionary token at the index computed using a @@ -284,9 +235,9 @@ private: /// dictionarySize is the total number of entries in the dictionary. std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add); - /// Returns an EVMVersion object corresponding to the protobuf - /// enum of type Program_Version - solidity::langutil::EVMVersion evmVersionMapping(Program_Version const& _x); + /// Return variable reference. + /// @param _index: Index of variable to be referenced + std::string varRef(unsigned _index); /// Returns a monotonically increasing counter that starts from zero. unsigned counter() @@ -302,29 +253,7 @@ private: return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter()); } - /// Returns a pseudo-randomly chosen object identifier that is in the - /// scope of the Yul object being visited. - std::string getObjectIdentifier(unsigned _x); - - /// Return new object identifier as string. Identifier string - /// is a template of the form "\"object\"" where is - /// a monotonically increasing object ID counter. - /// @param _decorate If true (default value), object ID is - /// enclosed within double quotes. - std::string newObjectId(bool _decorate = true) - { - return util::Whiskers(R"("object")") - ("decorate", _decorate) - ("id", std::to_string(m_objectId++)) - .render(); - } - - /// Returns the object counter value corresponding to the object - /// being visited. - unsigned currentObjectId() - { - return m_objectId - 1; - } + std::string dummyExpression(); std::ostringstream m_output; /// Variables in all function definitions @@ -349,13 +278,9 @@ private: std::stack> m_switchLiteralSetPerScope; // Look-up table per function type that holds the number of input (output) function parameters std::map> m_functionSigMap; - /// Tree of objects and their scopes - std::vector> m_objectScopeTree; // mod input/output parameters impose an upper bound on the number of input/output parameters a function may have. static unsigned constexpr s_modInputParams = 5; static unsigned constexpr s_modOutputParams = 5; - /// Hard-coded identifier for a Yul object's data block - static auto constexpr s_dataIdentifier = "datablock"; /// Predicate to keep track of for body scope. If false, break/continue /// statements can not be created. bool m_inForBodyScope; @@ -368,24 +293,15 @@ private: /// Predicate to keep track of for loop init scope. If true, variable /// or function declarations can not be created. bool m_inForInitScope; - /// Flag that is true while converting for loop condition, - /// false otherwise. - bool m_inForCond; /// Monotonically increasing counter unsigned m_counter; /// Size of protobuf input unsigned m_inputSize; /// Predicate that is true if inside function definition, false otherwise bool m_inFunctionDef; - /// Index used for naming objects - unsigned m_objectId; - /// Flag to track whether program is an object (true) or a statement block - /// (false: default value) - bool m_isObject; /// Flag to track whether scope extension of variables defined in for-init /// block is enabled. bool m_forInitScopeExtEnabled; - /// Object that holds the targeted evm version specified by protobuf input - solidity::langutil::EVMVersion m_evmVersion; + bool m_inForCond; }; } diff --git a/test/tools/ossfuzz/solArith.proto b/test/tools/ossfuzz/solArith.proto new file mode 100644 index 000000000..a21940633 --- /dev/null +++ b/test/tools/ossfuzz/solArith.proto @@ -0,0 +1,49 @@ +/* + 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 . +*/ + +syntax = "proto2"; + +import "yulProto.proto"; + +message Type { + enum Sign { + SIGNED = 0; + UNSIGNED = 1; + } + required Sign s = 1; + required uint32 bytewidth = 2; +} + +message VarDecl { + required Type t = 1; +} + +message Assembly { + required solidity.yul.test.yul_fuzzer.Program p = 1; +} + +message Block { + repeated VarDecl v = 1; + required Assembly a = 2; +} + +message Program { + required Block b = 1; + required uint64 seed = 2; +} + +package solidity.test.solarithfuzzer; diff --git a/test/tools/ossfuzz/solArithProtoFuzzer.cpp b/test/tools/ossfuzz/solArithProtoFuzzer.cpp new file mode 100644 index 000000000..5874a5112 --- /dev/null +++ b/test/tools/ossfuzz/solArithProtoFuzzer.cpp @@ -0,0 +1,268 @@ +/* + 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 + +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; + +using namespace solidity::test::abiv2fuzzer; +using namespace solidity::test::solarithfuzzer; +using namespace solidity; +using namespace solidity::test; +using namespace solidity::util; +using namespace std; + +namespace +{ +/// Test function returns a uint256 value +//static size_t const expectedOutputLength = 32; +///// Expected output value is decimal 0 +//static uint8_t const expectedOutput[expectedOutputLength] = { +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +//}; + +/// Compares the contents of the memory address pointed to +/// by `_result` of `_length` bytes to the expected output. +/// Returns true if `_result` matches expected output, false +/// otherwise. +//bool isOutputExpected(evmc::result const& _run) +//{ +// if (_run.output_size != expectedOutputLength) +// return false; +// +// return (memcmp(_run.output_data, expectedOutput, expectedOutputLength) == 0); +//} + +bool compareRuns(evmc::result const& _run1, evmc::result const& _run2) +{ + if (_run1.output_size != _run2.output_size) + return false; + return memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0; +} + +/// Accepts a reference to a user-specified input and returns an +/// evmc_message with all of its fields zero initialized except +/// gas and input fields. +/// The gas field is set to the maximum permissible value so that we +/// don't run into out of gas errors. The input field is copied from +/// user input. +evmc_message initializeMessage(bytes const& _input) +{ + // Zero initialize all message fields + evmc_message msg = {}; + // Gas available (value of type int64_t) is set to its maximum + // value. + msg.gas = std::numeric_limits::max(); + msg.input_data = _input.data(); + msg.input_size = _input.size(); + return msg; +} + +/// Accepts host context implementation, and keccak256 hash of the function +/// to be called at a specified address in the simulated blockchain as +/// input and returns the result of the execution of the called function. +evmc::result executeContract( + EVMHost& _hostContext, + bytes const& _functionHash, + evmc_address _deployedAddress +) +{ + evmc_message message = initializeMessage(_functionHash); + message.destination = _deployedAddress; + message.kind = EVMC_CALL; + return _hostContext.call(message); +} + +/// Accepts a reference to host context implementation and byte code +/// as input and deploys it on the simulated blockchain. Returns the +/// result of deployment. +evmc::result deployContract(EVMHost& _hostContext, bytes const& _code) +{ + evmc_message message = initializeMessage(_code); + message.kind = EVMC_CREATE; + return _hostContext.call(message); +} + +std::pair compileContract( + std::string _sourceCode, + std::string _contractName, + std::map const& _libraryAddresses = {}, + frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal() +) +{ + try + { + // Compile contract generated by the proto fuzzer + SolidityCompilationFramework solCompilationFramework; + return std::make_pair( + solCompilationFramework.compileContract(_sourceCode, _contractName, _libraryAddresses, _optimization), + solCompilationFramework.getMethodIdentifiers() + ); + } + // Ignore stack too deep errors during compilation + catch (langutil::StackTooDeepError const&) + { + throw langutil::FuzzerError(); + } +} + +evmc::result deployAndExecute(EVMHost& _hostContext, bytes _byteCode, std::string _hexEncodedInput) +{ + // Deploy contract and signal failure if deploy failed + evmc::result createResult = deployContract(_hostContext, _byteCode); + solAssert( + createResult.status_code == EVMC_SUCCESS, + "Proto solc fuzzer: Contract creation failed" + ); + + // Execute test function and signal failure if EVM reverted or + // did not return expected output on successful execution. + evmc::result callResult = executeContract( + _hostContext, + fromHex(_hexEncodedInput), + createResult.create_address + ); + + // We don't care about EVM One failures other than EVMC_REVERT + solAssert(callResult.status_code != EVMC_REVERT, "Proto solc fuzzer: EVM One reverted"); + return callResult; +} + +evmc::result compileDeployAndExecute( + std::string _sourceCode, + std::string _contractName, + std::string _methodName, + frontend::OptimiserSettings _optimization, + std::string _libraryName = {} +) +{ + bytes libraryBytecode; + Json::Value libIds; + // We target the default EVM which is the latest + langutil::EVMVersion version = {}; + EVMHost hostContext(version, evmone); + std::map _libraryAddressMap; + + // First deploy library + if (!_libraryName.empty()) + { + tie(libraryBytecode, libIds) = compileContract( + _sourceCode, + _libraryName, + {}, + _optimization + ); + // Deploy contract and signal failure if deploy failed + evmc::result createResult = deployContract(hostContext, libraryBytecode); + solAssert( + createResult.status_code == EVMC_SUCCESS, + "Proto solc fuzzer: Library deployment failed" + ); + _libraryAddressMap[_libraryName] = EVMHost::convertFromEVMC(createResult.create_address); + } + + auto [bytecode, ids] = compileContract( + _sourceCode, + _contractName, + _libraryAddressMap, + _optimization + ); + + return deployAndExecute( + hostContext, + bytecode, + ids[_methodName].asString() + ); +} +} + +DEFINE_PROTO_FUZZER(Program const& _input) +{ + ProtoConverter converter; + string sol_source{}; + try { + sol_source = converter.programToString(_input); + } + catch (langutil::FuzzerError const&) + { + return; + } + + if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) + { + // With libFuzzer binary run this to generate a YUL source file x.yul: + // PROTO_FUZZER_DUMP_PATH=x.yul ./a.out proto-input + ofstream of(dump_path); + of.write(sol_source.data(), sol_source.size()); + } + + if (const char* dump_path = getenv("SOL_DEBUG_FILE")) + { + sol_source.clear(); + // With libFuzzer binary run this to generate a YUL source file x.yul: + // PROTO_FUZZER_LOAD_PATH=x.yul ./a.out proto-input + ifstream ifstr(dump_path); + sol_source = { + std::istreambuf_iterator(ifstr), + std::istreambuf_iterator() + }; + std::cout << sol_source << std::endl; + } + + try { + auto minimalResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::minimal(), + "" + ); + bool successState = minimalResult.status_code == EVMC_SUCCESS; + + auto optResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::standard(), + "" + ); + if (successState) + { + solAssert( + optResult.status_code == EVMC_SUCCESS, + "Sol arith fuzzer: Optimal code failed" + ); + solAssert( + compareRuns(minimalResult, optResult), + "Sol arith fuzzer: Runs produced different result" + ); + } + } + catch (langutil::FuzzerError const&) + { + } + +} diff --git a/test/tools/ossfuzz/solarithprotoToSol.cpp b/test/tools/ossfuzz/solarithprotoToSol.cpp new file mode 100644 index 000000000..2900b0b82 --- /dev/null +++ b/test/tools/ossfuzz/solarithprotoToSol.cpp @@ -0,0 +1,180 @@ +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace solidity::test::solarithfuzzer; +using namespace solidity; +using namespace solidity::util; +using namespace std; + +string ProtoConverter::programToString(Program const& _program) +{ + m_rand = make_unique( + SolRandomNumGenerator(_program.seed()) + ); + return visit(_program); +} + +string ProtoConverter::visit(Program const& _program) +{ + Whiskers p(R"(pragma solidity >= 0.0.0;)"); + Whiskers c(R"(contract C {)"); + Whiskers t(R"(function test() public returns (uint))"); + t("body", visit(_program.b())); + return p.render() + + '\n' + + c.render() + + '\n' + + '\t' + + t.render() + + '\n' + + '}'; +} + +string ProtoConverter::visit(Block const& _block) +{ + ostringstream blockStr; + blockStr << '\n' + << '\t' + << '{' + << '\n'; + for (auto const& v: _block.v()) + blockStr << visit(v); + blockStr << visit(_block.a()); + ostringstream trace; + if (m_yulAssembly.empty()) + blockStr << "\t\treturn 0;"; + else + blockStr << addChecks(m_yulProgram, langutil::EVMVersion::berlin(), trace); + blockStr << '\n' + << '\t' + << '}'; + return blockStr.str(); +} + +string ProtoConverter::addChecks( + string const& _yulSource, + langutil::EVMVersion _version, + ostringstream& _os +) +{ + ostringstream out; + unsigned error = 1; + auto memoryDump = interpretYul(_yulSource, _version, _os); + unsigned index = 0; + for (auto const& v: m_varTypeMap) + { + Whiskers check(R"(if ( != (0x)) return ;)"); + check("ind", "\t\t"); + check("var", v.first); + check("type", get<1>(v.second)); + u256 memIdx = index * 0x20; + string val{}; + if (memoryDump.count(memIdx)) + { + unsigned byteWidth = get<2>(v.second); + val = extractBytes(memoryDump.at(memIdx), byteWidth); + // Avoid interpretation of 20 byte literals as address literals + // by prepending 00. + if (byteWidth == 20) + val = "00" + val; + } + else + val = "0"; + check("value", val); + check("error", to_string(error++)); + check("endl", "\n"); + out << check.render(); + index++; + } + out << "\t\treturn 0;\n"; + return out.str(); +} + +map ProtoConverter::interpretYul( + string const& _yulSource, + langutil::EVMVersion _version, + ostringstream& _os +) +{ + using namespace yul; + using namespace solidity::yul::test::yul_fuzzer; + YulStringRepository::reset(); + + // AssemblyStack entry point + AssemblyStack stack( + _version, + AssemblyStack::Language::StrictAssembly, + solidity::frontend::OptimiserSettings::full() + ); + + // Parse protobuf mutated YUL code + if (!stack.parseAndAnalyze("source", _yulSource) || !stack.parserResult()->code || + !stack.parserResult()->analysisInfo) + { + std::cout << _yulSource << std::endl; + yulAssert(false, "Proto fuzzer generated malformed program"); + } + stack.optimize(); + yulFuzzerUtil i; + + auto r = i.interpret( + _os, + stack.parserResult()->code, + EVMDialect::strictAssemblyForEVMObjects(_version) + ); + if (r != yulFuzzerUtil::TerminationReason::StepLimitReached && r != yulFuzzerUtil::TerminationReason::TraceLimitReached) + return i.memoryDump(); + else + throw langutil::FuzzerError(); +} + +string ProtoConverter::visit(Assembly const& _as) +{ + using namespace solidity::yul::test; + ostringstream assemblyStr; + assemblyStr << "\t\t" << "assembly {\n"; + // Yul converter + auto c = yul_fuzzer::ProtoConverter{m_varCounter}; + m_yulAssembly = c.programToString(_as.p()); + m_yulProgram = Whiskers("{}") + ("endl", "\n") + ("init", m_yulInitCode.str()) + ("assembly", m_yulAssembly) + .render(); + assemblyStr << m_yulAssembly; + assemblyStr << "\t\t}\n"; + return assemblyStr.str(); +} + +string ProtoConverter::visit(VarDecl const& _vardecl) +{ + Whiskers v(R"( = ();)"); + string type = visit(_vardecl.t()); + string varName = newVarName(); + unsigned byteWidth = widthUnsigned(_vardecl.t().bytewidth()); + m_varTypeMap.emplace(varName, tuple(typeSign(_vardecl.t()), type, byteWidth)); + v("type", type); + v("varName", varName); + string value = maskUnsignedToHex(64); + v("value", value); + Whiskers i(R"(let := )"); + i("varName", varName); + i("value", "0x" + extractBytes(value, byteWidth)); + i("endl", "\n"); + m_yulInitCode << i.render(); + incrementVarCounter(); + return "\t\t" + v.render() + '\n'; +} + +string ProtoConverter::visit(Type const& _type) +{ + return signString(_type.s()) + widthString(_type.bytewidth()); +} \ No newline at end of file diff --git a/test/tools/ossfuzz/solarithprotoToSol.h b/test/tools/ossfuzz/solarithprotoToSol.h new file mode 100644 index 000000000..84420cc07 --- /dev/null +++ b/test/tools/ossfuzz/solarithprotoToSol.h @@ -0,0 +1,121 @@ +#include + +#include "yulFuzzerCommon.h" +#include +#include +#include +#include +#include + +namespace solidity::test::solarithfuzzer +{ +/// Random number generator that is seeded with a fuzzer +/// supplied unsigned integer. +struct SolRandomNumGenerator +{ + using RandomEngine = std::mt19937_64; + + explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {} + + /// @returns a pseudo random unsigned integer + unsigned operator()() + { + return m_random(); + } + + RandomEngine m_random; +}; + +enum class Sign +{ + Signed, + Unsigned +}; + +class ProtoConverter +{ +public: + ProtoConverter() + { + + } + ProtoConverter(ProtoConverter const&) = delete; + ProtoConverter(ProtoConverter&&) = delete; + std::string programToString(Program const& _input); +private: + std::string visit(Type const& _type); + std::string visit(Assembly const& _a); + std::string visit(VarDecl const& _decl); + std::string visit(Block const& _block); + std::string visit(Program const& _program); + + std::map interpretYul( + std::string const& _yulSource, + langutil::EVMVersion _version, + std::ostringstream& _os + ); + std::string addChecks( + std::string const& _yulSource, + langutil::EVMVersion _version, + std::ostringstream& _os + ); + + std::string newVarName() + { + return "v" + std::to_string(m_varCounter); + } + void incrementVarCounter() + { + m_varCounter++; + } + static std::string signString(Type::Sign _sign) + { + return _sign == Type::Sign::Type_Sign_SIGNED ? "int" : "uint"; + } + static std::string signString(Sign _sign) + { + return _sign == Sign::Signed ? "int" : "uint"; + } + static std::string widthString(unsigned _width) + { + return std::to_string((_width % 32 + 1) * 8); + } + static unsigned widthUnsigned(unsigned _width) + { + return _width % 32 + 1; + } + bool varAvailable() + { + return m_varCounter > 0; + } + Sign typeSign(Type const& _ts) + { + return _ts.s() == Type::SIGNED ? Sign::Signed : Sign::Unsigned; + } + std::string maskUnsignedToHex(unsigned _numMaskNibbles) + { + return toHex(maskUnsignedInt(_numMaskNibbles), util::HexPrefix::Add); + } + + // Convert _counter to string and return its keccak256 hash + solidity::u256 hashUnsignedInt() + { + return util::keccak256(util::h256((*m_rand)())); + } + u256 maskUnsignedInt(unsigned _numMaskNibbles) + { + return hashUnsignedInt() & u256("0x" + std::string(_numMaskNibbles, 'f')); + } + static std::string extractBytes(std::string _value, unsigned _numBytes) + { + return _value.substr(_value.size() - (_numBytes * 2)); + } + + unsigned m_varCounter = 0; + std::shared_ptr m_rand; + std::map> m_varTypeMap; + std::string m_yulAssembly; + std::string m_yulProgram; + std::ostringstream m_yulInitCode; +}; +} diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index 214caf3f8..63c8b08c9 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -29,12 +29,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( size_t _maxTraceSize ) { - InterpreterState state; - state.maxTraceSize = _maxTraceSize; - state.maxSteps = _maxSteps; + m_state.maxTraceSize = _maxTraceSize; + m_state.maxSteps = _maxSteps; // Add 64 bytes of pseudo-randomly generated calldata so that // calldata opcodes perform non trivial work. - state.calldata = { + m_state.calldata = { 0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d, 0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8, 0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e, @@ -44,7 +43,24 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( 0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd, 0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde }; - Interpreter interpreter(state, _dialect); + m_state.returndata = { + 0xe9, 0x96, 0x40, 0x7d, 0xa5, 0xda, 0xb0, 0x2d, + 0x97, 0xf5, 0xc3, 0x44, 0xd7, 0x65, 0x0a, 0xd8, + 0x2c, 0x14, 0x3a, 0xf3, 0xe7, 0x40, 0x0f, 0x1e, + 0x67, 0xce, 0x90, 0x44, 0x2e, 0x92, 0xdb, 0x88, + 0xb8, 0x43, 0x9c, 0x41, 0x42, 0x08, 0xf1, 0xd7, + 0x65, 0xe9, 0x7f, 0xeb, 0x7b, 0xb9, 0x56, 0x9f, + 0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd, + 0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde + }; +// for (unsigned i = 0; i < 64; i++) +// { +// m_state.memory[i] = m_state.calldata[i]; +// if (i % 32 == 0) +// m_state.storage[util::h256(i / 32)] = util::h256(m_state.calldata[i]); +// } + + Interpreter interpreter(m_state, _dialect); TerminationReason reason = TerminationReason::None; try @@ -64,6 +80,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( reason = TerminationReason::ExplicitlyTerminated; } - state.dumpTraceAndState(_os); + m_state.dumpTraceAndState(_os); return reason; } + +std::map yulFuzzerUtil::memoryDump() +{ + return m_state.dumpMemory(); +} diff --git a/test/tools/ossfuzz/yulFuzzerCommon.h b/test/tools/ossfuzz/yulFuzzerCommon.h index 08e17e381..6492784cd 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.h +++ b/test/tools/ossfuzz/yulFuzzerCommon.h @@ -30,13 +30,15 @@ struct yulFuzzerUtil None }; - static TerminationReason interpret( + TerminationReason interpret( std::ostream& _os, std::shared_ptr _ast, Dialect const& _dialect, size_t _maxSteps = maxSteps, size_t _maxTraceSize = maxTraceSize ); + std::map memoryDump(); + InterpreterState m_state; static size_t constexpr maxSteps = 100; static size_t constexpr maxTraceSize = 75; }; diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 3180b421a..86fe65a3b 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -17,82 +17,17 @@ syntax = "proto2"; -message VarDecl { - required Expression expr = 1; -} - -message MultiVarDecl { - required uint32 num_vars = 1; -} - -message LowLevelCall { - enum Type { - CALL = 0; - CALLCODE = 1; - DELEGATECALL = 2; - STATICCALL = 3; - } - required Type callty = 1; - required Expression gas = 2; - required Expression addr = 3; - // Valid for call and callcode only - required Expression wei = 4; - required Expression in = 5; - required Expression insize = 6; - required Expression out = 7; - required Expression outsize = 8; -} - -message Create { - enum Type { - CREATE = 0; - CREATE2 = 1; - } - required Type createty = 1; - required Expression wei = 2; - required Expression position = 3; - required Expression size = 4; - // Valid for create2 only - required Expression value = 5; -} - message FunctionCall { - enum Returns { - ZERO = 1; - SINGLE = 2; - MULTIDECL = 3; - MULTIASSIGN = 4; - } - required Returns ret = 1; // Indexes an existing function - required uint32 func_index = 2; - required Expression in_param1 = 3; - required Expression in_param2 = 4; - required Expression in_param3 = 5; - required Expression in_param4 = 6; - required VarRef out_param1 = 7; - required VarRef out_param2 = 8; - required VarRef out_param3 = 9; - required VarRef out_param4 = 10; -} - -message TypedVarDecl { - enum TypeName { - BOOL = 1; - U8 = 2; - U32 = 3; - U64 = 4; - U128 = 5; - U256 = 6; - S8 = 7; - S32 = 8; - S64 = 9; - S128 = 10; - S256 = 11; - }; - required int32 id = 1; - required TypeName type = 2; - required Expression expr = 3; + required uint32 func_index = 1; + required Expression in_param1 = 2; + required Expression in_param2 = 3; + required Expression in_param3 = 4; + required Expression in_param4 = 5; + required VarRef out_param1 = 6; + required VarRef out_param2 = 7; + required VarRef out_param3 = 8; + required VarRef out_param4 = 9; } message VarRef { @@ -108,24 +43,6 @@ message Literal { } } -message TypedLiteral { - enum TypeName { - BOOL = 1; - U8 = 2; - U32 = 3; - U64 = 4; - U128 = 5; - U256 = 6; - S8 = 7; - S32 = 8; - S64 = 9; - S128 = 10; - S256 = 11; - }; - required int32 val = 1; - required TypeName type = 2; -} - message BinaryOp { enum BOp { ADD = 0; @@ -163,24 +80,11 @@ message UnaryOp { SLOAD = 2; ISZERO = 3; CALLDATALOAD = 4; - EXTCODESIZE = 5; - EXTCODEHASH = 6; - BALANCE = 7; - BLOCKHASH = 8; } required UOp op = 1; required Expression operand = 2; } -message UnaryOpData { - enum UOpData { - SIZE = 1; - OFFSET = 2; - } - required UOpData op = 1; - required uint64 identifier = 2; -} - message TernaryOp { enum TOp { ADDM = 0; @@ -192,33 +96,9 @@ message TernaryOp { required Expression arg3 = 4; } -message CopyFunc { - enum CopyType { - CALLDATA = 0; - CODE = 1; - RETURNDATA = 2; - DATA = 3; - } - required CopyType ct = 1; - required Expression target = 2; - required Expression source = 3; - required Expression size = 4; -} - -message ExtCodeCopy { - required Expression addr = 1; - required Expression target = 2; - required Expression source = 3; - required Expression size = 4; -} - message NullaryOp { enum NOp { - PC = 1; - MSIZE = 2; - GAS = 3; CALLDATASIZE = 4; - CODESIZE = 5; RETURNDATASIZE = 6; ADDRESS = 7; ORIGIN = 8; @@ -226,7 +106,6 @@ message NullaryOp { CALLVALUE = 10; GASPRICE = 11; COINBASE = 12; - TIMESTAMP = 13; NUMBER = 14; DIFFICULTY = 15; GASLIMIT = 16; @@ -247,21 +126,12 @@ message StoreFunc { required Storage st = 3; } -message LogFunc { - enum NumTopics { - ZERO = 0; - ONE = 1; - TWO = 2; - THREE = 3; - FOUR = 4; - } - required Expression pos = 1; - required Expression size = 2; - required NumTopics num_topics = 3; - required Expression t1 = 4; - required Expression t2 = 5; - required Expression t3 = 6; - required Expression t4 = 7; +message FunctionExpr { + required uint64 index = 1; + required Expression in_param1 = 2; + required Expression in_param2 = 3; + required Expression in_param3 = 4; + required Expression in_param4 = 5; } message Expression { @@ -272,10 +142,7 @@ message Expression { UnaryOp unop = 4; TernaryOp top = 5; NullaryOp nop = 6; - FunctionCall func_expr = 7; - LowLevelCall lowcall = 8; - Create create = 9; - UnaryOpData unopdata = 10; + FunctionExpr funcexpr = 10; } } @@ -286,15 +153,15 @@ message AssignmentStatement { message IfStmt { required Expression cond = 1; - required Block if_body = 2; + required Block block = 2; } message BoundedForStmt { - required Block for_body = 1; + required Block block = 1; } message ForStmt { - required Block for_body = 1; + required Block block = 1; required Block for_init = 2; required Block for_post = 3; required Expression for_cond = 4; @@ -302,48 +169,18 @@ message ForStmt { message CaseStmt { required Literal case_lit = 1; - required Block case_block = 2; + required Block block = 2; } message SwitchStmt { required Expression switch_expr = 1; repeated CaseStmt case_stmt = 2; - optional Block default_block = 3; + optional Block block = 3; } message BreakStmt {} message ContinueStmt {} -message StopInvalidStmt { - enum Type { - STOP = 0; - INVALID = 1; - } - required Type stmt = 1; -} - -message RetRevStmt { - enum Type { - RETURN = 0; - REVERT = 1; - } - required Type stmt = 1; - required Expression pos = 2; - required Expression size = 3; -} - -message SelfDestructStmt { - required Expression addr = 1; -} - -message TerminatingStmt { - oneof term_oneof { - StopInvalidStmt stop_invalid = 1; - RetRevStmt ret_rev = 2; - SelfDestructStmt self_des = 3; - } -} - message FunctionDef { required uint32 num_input_params = 1; required uint32 num_output_params = 2; @@ -359,7 +196,6 @@ message LeaveStmt {} message Statement { oneof stmt_oneof { - VarDecl decl = 1; AssignmentStatement assignment = 2; IfStmt ifstmt = 3; StoreFunc storage_func = 4; @@ -368,16 +204,11 @@ message Statement { SwitchStmt switchstmt = 7; BreakStmt breakstmt = 8; ContinueStmt contstmt = 9; - LogFunc log_func = 10; - CopyFunc copy_func = 11; - ExtCodeCopy extcode_copy = 12; - TerminatingStmt terminatestmt = 13; FunctionCall functioncall = 14; BoundedForStmt boundedforstmt = 15; FunctionDef funcdef = 16; PopStmt pop = 17; LeaveStmt leave = 18; - MultiVarDecl multidecl = 19; } } @@ -385,36 +216,8 @@ message Block { repeated Statement statements = 1; } -message Object { - required Code code = 1; - optional Data data = 2; - optional Object sub_obj = 3; -} - -message Code { +message Program { required Block block = 1; } -message Data { - required string hex = 1; -} - -message Program { - enum Version { - HOMESTEAD = 0; - TANGERINE = 1; - SPURIOUS = 2; - BYZANTIUM = 3; - CONSTANTINOPLE = 4; - PETERSBURG = 5; - ISTANBUL = 6; - BERLIN = 7; - } - oneof program_oneof { - Block block = 1; - Object obj = 2; - } - required Version ver = 3; -} - -package solidity.yul.test.yul_fuzzer; +package solidity.yul.test.yul_fuzzer; \ No newline at end of file diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 9dbc518be..56244aa16 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -96,7 +96,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) return; stack.optimize(); - termReason = yulFuzzerUtil::interpret( + yulFuzzerUtil::interpret( os2, stack.parserResult()->code, EVMDialect::strictAssemblyForEVMObjects(version), diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 6f989b873..c9309d04a 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -46,6 +46,18 @@ using namespace solidity::yul::test; using solidity::util::h256; +std::map InterpreterState::dumpMemory() const +{ + std::map out; + map words; + for (auto const& [offset, value]: memory) + words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * size_t(offset % 0x20)); + for (auto const& [offset, value]: words) + if (value != 0) + out.emplace(offset, h256(value).hex()); + return out; +} + void InterpreterState::dumpTraceAndState(ostream& _out) const { _out << "Trace:" << endl; diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index f50cbe6c4..ed38ded12 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -94,6 +94,7 @@ struct InterpreterState ControlFlowState controlFlowState = ControlFlowState::Default; void dumpTraceAndState(std::ostream& _out) const; + std::map dumpMemory() const; }; /**