diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 83543b9f0..2b5a05795 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -10,7 +10,7 @@ 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 sol_proto_ossfuzz) add_custom_target(ossfuzz_abiv2) add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz) @@ -60,6 +60,25 @@ 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 +# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator +# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include +# ) +# 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 +97,24 @@ if (OSSFUZZ) protobuf.a ) set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + + add_executable(sol_proto_ossfuzz + solProtoFuzzer.cpp + ../fuzzer_common.cpp + protoToSol.cpp + solProto.pb.cc + abiV2Proto.pb.cc + protoToAbiV2.cpp + ) + target_include_directories(sol_proto_ossfuzz PRIVATE + /usr/include/libprotobuf-mutator + ) + target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc + 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/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index 1c422ef8c..96d31119c 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -704,7 +704,7 @@ void TypeVisitor::structDefinition(StructType const& _type) // Commence struct declaration string structDef = lineString( "struct " + - string(s_structNamePrefix) + + m_structPrefix + to_string(m_structCounter) + " {" ); @@ -718,7 +718,7 @@ void TypeVisitor::structDefinition(StructType const& _type) if (!ValidityVisitor().visit(t)) continue; - TypeVisitor tVisitor(m_structCounter + 1); + TypeVisitor tVisitor(m_structCounter + 1, m_indentation - 1, m_structPrefix); type = tVisitor.visit(t); m_structCounter += tVisitor.numStructs(); m_structDef << tVisitor.structDef(); @@ -749,7 +749,7 @@ string TypeVisitor::visit(StructType const& _type) // Set last dyn param if struct contains a dyn param e.g., bytes, array etc. m_isLastDynParamRightPadded = DynParamVisitor().visit(_type); // If top-level struct is a non-emtpy struct, assign the name S - m_baseType = s_structTypeName + to_string(m_structStartCounter); + m_baseType = m_structPrefix + to_string(m_structStartCounter); } else m_baseType = {}; diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index f75f5d7e4..f638e9743 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -152,7 +152,7 @@ public: ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; std::string contractToString(Contract const& _input); -private: + enum class Delimiter { ADD, @@ -205,7 +205,7 @@ private: /// Solidity code to be placed inside test function scope. template std::pair processType(T const& _type, bool _isValueType); - +private: /// Convert a protobuf type @a _T into Solidity variable assignment and check /// statements to be placed inside contract and test function scopes. /// @param: _varName is the name of the Solidity variable @@ -348,6 +348,8 @@ private: return s_paramNamePrefix; case Contract_Test::Contract_Test_RETURNDATA_CODER: return s_localVarNamePrefix; + default: + return s_localVarNamePrefix; } } @@ -375,6 +377,7 @@ private: std::ostringstream m_typedReturn; /// Argument names to be passed to coder functions std::ostringstream m_argsCoder; +public: /// Predicate that is true if we are in contract scope bool m_isStateVar; unsigned m_counter; @@ -588,12 +591,13 @@ private: class TypeVisitor: public AbiV2ProtoVisitor { public: - TypeVisitor(unsigned _structSuffix = 0): - m_indentation(1), + TypeVisitor(unsigned _structSuffix = 0, unsigned _indentation = 1, std::string _structPrefix = "S"): + m_indentation(_indentation), m_structCounter(_structSuffix), m_structStartCounter(_structSuffix), m_structFieldCounter(0), - m_isLastDynParamRightPadded(false) + m_isLastDynParamRightPadded(false), + m_structPrefix(_structPrefix) {} std::string visit(BoolType const&) override; @@ -649,8 +653,7 @@ private: unsigned m_structStartCounter; unsigned m_structFieldCounter; bool m_isLastDynParamRightPadded; - - static auto constexpr s_structTypeName = "S"; + std::string m_structPrefix; }; /// Returns a pair of strings, first of which contains assignment statements diff --git a/test/tools/ossfuzz/protoToSol.cpp b/test/tools/ossfuzz/protoToSol.cpp new file mode 100644 index 000000000..3713685c1 --- /dev/null +++ b/test/tools/ossfuzz/protoToSol.cpp @@ -0,0 +1,803 @@ +/* + 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 "protoToSol.h" +#include "protoToAbiV2.h" + +#include + +using namespace solidity::test::solprotofuzzer; +using namespace std; +using namespace solidity::util; + +string ProtoConverter::protoToSolidity(Program const& _p) +{ + return visit(_p); +} + +string ProtoConverter::visit(Program const& _p) +{ + ostringstream program; + ostringstream contracts; + + for (auto &contract: _p.contracts()) + contracts << visit(contract); + + program << Whiskers(R"( +pragma solidity >=0.0; +pragma experimental ABIEncoderV2; +pragma experimental SMTChecker; + + +)") + ("contracts", contracts.str()) + .render(); + return program.str(); +} + +string ProtoConverter::visit(ContractType const& _contractType) +{ + switch (_contractType.contract_type_oneof_case()) + { + case ContractType::kC: + return visit(_contractType.c()); + case ContractType::kL: + return visit(_contractType.l()); + case ContractType::kI: + return visit(_contractType.i()); + case ContractType::CONTRACT_TYPE_ONEOF_NOT_SET: + return ""; + } +} + +string ProtoConverter::visit(ContractOrInterface const& _contractOrInterface) +{ + switch (_contractOrInterface.contract_or_interface_oneof_case()) + { + case ContractOrInterface::kC: + return visit(_contractOrInterface.c()); + case ContractOrInterface::kI: + return visit(_contractOrInterface.i()); + case ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET: + return ""; + } +} + +string ProtoConverter::traverseOverrides(Interface const& _interface, bool _isOverride, bool _implement, bool _inheritedByContract, bool _isVirtual) +{ + ostringstream funcs; + + for (auto ancestor = _interface.ancestors().rbegin(); ancestor != _interface.ancestors().rend(); ancestor++) + { + unsigned index = 0; + m_numContracts++; + + m_numStructs = 0; + + for (auto& f: ancestor->funcdef()) + { + string funcStr = visit( + f, + index++, + true, + m_interfaceNameMap[&*ancestor], + _implement, + _inheritedByContract, + _isVirtual + ); + + if (f.override() || _isOverride) + funcs << funcStr; + } + funcs << traverseOverrides(*ancestor, _isOverride, _implement, _inheritedByContract, _isVirtual); + } + return funcs.str(); +} + +string ProtoConverter::traverseOverrides(Contract const& _contract, bool _isAbstract) +{ + ostringstream funcs; + bool isImplemented = m_isImplemented; + + for (auto ancestor = _contract.ancestors().rbegin(); ancestor != _contract.ancestors().rend(); ancestor++) + { + if (ancestor->contract_or_interface_oneof_case() == ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET) + continue; + + m_numContracts++; + m_numStructs = 0; + + if (ancestor->has_c()) + { + unsigned index = 0; + if (_contract.abstract() && !ancestor->c().abstract()) + m_isImplemented = true; + + for (auto& f: ancestor->c().funcdef()) + { + string funcStr = visit( + f, + index++, + true, + _isAbstract, + f.virtualfunc() || !f.implemented(), + m_isImplemented, + m_contractNameMap[&ancestor->c()] + ); + if ((f.virtualfunc() && !f.implemented() && !_isAbstract) || + (f.virtualfunc() && f.override()) || + (ancestor->c().abstract() && !f.implemented())) + funcs << funcStr; + } + funcs << traverseOverrides(ancestor->c(), _isAbstract); + } + else if (ancestor->has_i()) + { + unsigned index = 0; + for (auto& f: ancestor->i().funcdef()) + funcs << visit( + f, + index++, + true, + m_interfaceNameMap[&ancestor->i()], + true, + true, + true + ); + funcs << traverseOverrides(ancestor->i(), true, true, true, true); + } + } + m_isImplemented = isImplemented; + return funcs.str(); +} + +tuple ProtoConverter::visitContractHelper(CI _cOrI, string _programName) +{ + ostringstream ancestors; + ostringstream funcs; + ostringstream ancestorNames; + + string separator{}; + if (holds_alternative(_cOrI)) + { + auto interface = get(_cOrI); + for (auto &ancestor: interface->ancestors()) + { + string ancestorStr = visit(ancestor); + if (ancestorStr.empty()) + continue; + ancestors << ancestorStr; + ancestorNames << separator + << m_interfaceNameMap[&ancestor]; + if (separator.empty()) + separator = ", "; + } + + unsigned wasNumContract = m_numContracts; + + // First define overridden functions + bool overrides = interface->ancestors_size() > 0 && !ancestorNames.str().empty(); + if (overrides) + { + funcs << traverseOverrides(*interface, false, false, false, false); + } + + m_numContracts = wasNumContract; + m_numStructs = 0; + + unsigned index = 0; + // Define non-overridden functions + for (auto &f: interface->funcdef()) + funcs << visit(f, index++, false, _programName); + } + else + { + auto contract = get(_cOrI); + + for (auto &ancestor: contract->ancestors()) + { + string ancestorStr = visit(ancestor); + if (ancestorStr.empty()) + continue; + ancestors << ancestorStr; + ancestorNames << separator + << (ancestor.has_c() ? m_contractNameMap[&ancestor.c()] : m_interfaceNameMap[&ancestor.i()]); + if (separator.empty()) + separator = ", "; + } + + unsigned wasNumContract = m_numContracts; + + // First define overridden functions + bool overrides = contract->ancestors_size() > 0 && !ancestorNames.str().empty(); + if (overrides) + { + funcs << traverseOverrides(*contract, contract->abstract()); + } + + m_numContracts = wasNumContract; + m_numStructs = 0; + + // Define non-overridden functions + unsigned index = 0; + for (auto &f: contract->funcdef()) + funcs << visit( + f, + index++, + false, + contract->abstract(), + (f.virtualfunc() || (contract->abstract() && !f.implemented())), + false, + _programName + ); + } + return make_tuple(ancestors.str(), ancestorNames.str(), funcs.str()); +} + +string ProtoConverter::visit(Interface const& _interface) +{ + string programName{"I" + to_string(m_numContracts++)}; + m_interfaceNameMap.insert(pair(&_interface, programName)); + m_numStructs = 0; + auto [ancestors, ancestorNames, funcs] = visitContractHelper(&_interface, programName); + return Whiskers(R"( + + is { + +})") + ("ancestors", ancestors) + ("programType", "interface") + ("programName", programName) + ("inheritance", _interface.ancestors_size() > 0 && !ancestorNames.empty()) + ("ancestorNames", ancestorNames) + ("functionDefs", funcs) + .render(); +} + +string ProtoConverter::visit(Library const& _library) +{ + ostringstream funcs; + string programName{"L" + to_string(m_numContracts++)}; + + unsigned index = 0; + m_numStructs = 0; + for (auto &f: _library.funcdef()) + funcs << visit(f, index++, programName); + + return Whiskers(R"( +library { + +})") + ("programName", programName) + ("functionDefs", funcs.str()) + .render(); +} + +string ProtoConverter::visit(Contract const& _contract) +{ + string programName{"C" + to_string(m_numContracts++)}; + m_contractNameMap.insert(pair(&_contract, programName)); + m_numStructs = 0; + auto [ancestors, ancestorNames, funcs] = visitContractHelper(&_contract, programName); + return Whiskers(R"( + +abstract is { + +})") + ("ancestors", ancestors) + ("isAbstract", _contract.abstract()) + ("programType", "contract") + ("programName", programName) + ("inheritance", _contract.ancestors_size() > 0 && !ancestorNames.empty()) + ("ancestorNames", ancestorNames) + ("functionDefs", funcs) + .render(); +} + +string ProtoConverter::visit(Modifier const& _mod) +{ + return Whiskers(R"( + modifier m() { + + _; + })") + ("i", to_string(m_numMods++)) + ("isParams", _mod.params_size() > 0) + ("params", "") + ("body", "") + .render(); +} + +tuple ProtoConverter::visit( + FunctionParamsAndReturns const& _pr, + bool _isExternal, + string _programName +) +{ + string paramsString; + string typeDefsParamsString; + unsigned structsAdded = 0; + unsigned index = 0; + string separator{}; + string structPrefix = _programName + "S"; + + for (auto ¶m: _pr.params()) + { + solidity::test::abiv2fuzzer::TypeVisitor typeVisitor(m_numStructs, 2, structPrefix); + typeVisitor.visit(param); + if (!typeVisitor.baseType().empty()) + { + paramsString += Whiskers( + R"( calldatamemory p)") + ("type", typeVisitor.baseType()) + ("isNonValue", param.type_oneof_case() == param.kNvtype) + ("isExternal", _isExternal) + ("i", to_string(index++)) + ("sep", separator) + .render(); + typeDefsParamsString += typeVisitor.structDef(); + m_numStructs += typeVisitor.numStructs(); + structsAdded += typeVisitor.numStructs(); + if (separator.empty()) + separator = ", "; + } + } + + separator = ""; + string returnString; + string typeDefsReturnsString; + for (auto &ret: _pr.returns()) + { + solidity::test::abiv2fuzzer::TypeVisitor typeVisitor(m_numStructs, 2, structPrefix); + typeVisitor.visit(ret); + if (!typeVisitor.baseType().empty()) + { + returnString += Whiskers(R"( memory)") + ("type", typeVisitor.baseType()) + ("isNonValue", ret.type_oneof_case() == ret.kNvtype) + ("sep", separator) + .render(); + typeDefsReturnsString += typeVisitor.structDef(); + m_numStructs += typeVisitor.numStructs(); + structsAdded += typeVisitor.numStructs(); + if (separator.empty()) + separator = ", "; + } + } + return make_tuple(typeDefsParamsString + typeDefsReturnsString, paramsString, returnString); +} + +bool ProtoConverter::disallowedContractFunction(ContractFunction const& _contractFunction, bool _isVirtual) +{ + // Private virtual functions are disallowed + if (functionVisibility(_contractFunction.vis()) == "private" && _isVirtual) + return true; + // Private payable functions are disallowed + else if (functionVisibility(_contractFunction.vis()) == "private" && stateMutability(_contractFunction.mut()) == "payable") + return true; + // Internal payable functions are disallowed + else if (functionVisibility(_contractFunction.vis()) == "internal" && stateMutability(_contractFunction.mut()) == "payable") + return true; + return false; +} + +string ProtoConverter::visit( + ContractFunction const& _contractFunction, + unsigned _index, + bool _isOverride, + bool _isAbstractContract, + bool _isVirtual, + bool _isImplemented, + string _programName +) +{ + if (disallowedContractFunction(_contractFunction, _isVirtual || _contractFunction.virtualfunc())) + return ""; + + auto [structDefString, paramString, returnString] = visit( + _contractFunction.paramandret(), + _contractFunction.vis() == ContractFunction_Visibility_EXTERNAL, + _programName + ); + + bool isUnimplemented = _isAbstractContract && !_contractFunction.implemented() && !_isImplemented; + + return Whiskers(R"( + + + function () override virtual returns (); + { + + } + + + +)") + ("isTypeDefs", !_isOverride) + ("typeDefs", structDefString) + ("functionPrefix", boost::algorithm::to_lower_copy(_programName) + s_functionPrefix) + ("i", to_string(_index)) + ("isParams", _contractFunction.paramandret().params_size() > 0 && !paramString.empty()) + ("params", paramString) + ("isVirtual", _isVirtual) + ("isOverride", _isOverride) + ("visibility", functionVisibility(_contractFunction.vis())) + ("stateMutability", stateMutability(_contractFunction.mut())) + ("isMod", _contractFunction.has_m() && !isUnimplemented) + ("modifier", "m" + to_string(m_numMods)) + ("isReturn", _contractFunction.paramandret().returns_size() > 0 && !returnString.empty()) + ("types", returnString) + ("body", "") + ("isUnimplemented", isUnimplemented) + ("modifierDef", visit(_contractFunction.m())) + .render(); +} + +string ProtoConverter::visit(LibraryFunction const& _libraryFunction, unsigned _index, string _programName) +{ + auto [typeDefs, params, returns] = visit( + _libraryFunction.paramandret(), + _libraryFunction.vis() == LibraryFunction_Visibility_EXTERNAL, + _programName + ); + + return Whiskers(R"( + + + function () returns () + { + + } + + + +)") + ("typeDefs", typeDefs) + ("functionPrefix", s_libraryFunctionPrefix) + ("functionSuffix", to_string(_index)) + ("isParams", _libraryFunction.paramandret().params_size() > 0 && !params.empty()) + ("params", params) + ("visibility", functionVisibility(_libraryFunction.vis())) + ("stateMutability", stateMutability(_libraryFunction.mut())) + ("isMod", _libraryFunction.has_m()) + ("modifier", "m" + to_string(m_numMods)) + ("isReturn", _libraryFunction.paramandret().returns_size() > 0 && !returns.empty()) + ("types", returns) + ("body", "") + ("modifierDef", visit(_libraryFunction.m())) + .render(); +} + +string ProtoConverter::visit( + InterfaceFunction const& _interfaceFunction, + unsigned _index, + bool _isOverride, + string _programName, + bool _implement, + bool _inheritedByContract, + bool _isVirtual +) +{ + auto [typeDefs, params, returns] = visit( + _interfaceFunction.paramandret(), + true, + _programName + ); + + return Whiskers(R"( + + + function () external override virtual returns () virtual;; + )") + ("isTypeDefs", !_isOverride) + ("typeDefs", typeDefs) + ("functionPrefix", boost::algorithm::to_lower_copy(_programName) + s_functionPrefix) + ("functionSuffix", to_string(_index)) + ("isParams", _interfaceFunction.paramandret().params_size() > 0 && !params.empty()) + ("params", params) + ("stateMutability", stateMutability(_interfaceFunction.mut())) + ("isOverride", _isOverride) + ("isVirtual", _isVirtual) + ("isReturn", _interfaceFunction.paramandret().returns_size() > 0 && !returns.empty()) + ("types", returns) + ("isImplemented", _implement) + ("inheritedByContract", _inheritedByContract) + ("block", "{}") + .render(); +} + +pair ProtoConverter::visit(Block const& _block) +{ + pair block; + for (auto &statement: _block.statements()) + { + block.first += visit(statement).first; + block.second += visit(statement).second; + } + return block; +} + +pair ProtoConverter::visit(Statement const& _stmt) +{ + switch (_stmt.stmt_oneof_case()) + { + case Statement::kVar: + return visit(_stmt.var(), false); + case Statement::kIfstmt: + return visit(_stmt.ifstmt()); + case Statement::kForstmt: + return make_pair("", visit(_stmt.forstmt())); + case Statement::kSwitchstmt: + return make_pair("", visit(_stmt.switchstmt())); + case Statement::kBreakstmt: + return make_pair("", visit(_stmt.breakstmt())); + case Statement::kContinuestmt: + return make_pair("", visit(_stmt.continuestmt())); + case Statement::kReturnstmt: + return make_pair("", visit(_stmt.returnstmt())); + case Statement::kDostmt: + return visit(_stmt.dostmt()); + case Statement::kWhilestmt: + return visit(_stmt.whilestmt()); + case Statement::STMT_ONEOF_NOT_SET: + return make_pair("", ""); + } +} + +pair ProtoConverter::visit(solidity::test::abiv2fuzzer::VarDecl const& _varDecl, bool _stateVar) +{ + solidity::test::abiv2fuzzer::ProtoConverter converter; + converter.m_isStateVar = _stateVar; + converter.m_varCounter = m_numVars; + converter.m_structCounter = m_numStructs; + auto decl = converter.visit(_varDecl); + if (!decl.first.empty()) + { + m_numVars++; + m_numStructs += converter.m_numStructsAdded; + decl.second += "\n" + + solidity::test::abiv2fuzzer::AssignCheckVisitor{ + solidity::test::abiv2fuzzer::ProtoConverter::s_stateVarNamePrefix + + to_string(m_numVars - 1), + "", + 0, + "true", + 0, + m_numStructs - 1 + }.visit(_varDecl.type()).second; + } + return decl; +} + +pair ProtoConverter::visit(IfStmt const& _ifstmt) +{ + string ifCond = visit(_ifstmt.condition()); + pair buffer = visit(_ifstmt.statements()); + + return make_pair(buffer.first, Whiskers(R"(if () { + + } + )") + ("cond", ifCond) + ("statements", buffer.second) + .render()); +} + +string ProtoConverter::visit(ForStmt const&) +{ + return ""; +} + +string ProtoConverter::visit(SwitchStmt const&) +{ + return ""; +} + +string ProtoConverter::visit(BreakStmt const&) +{ + return "break;\n"; +} + +string ProtoConverter::visit(ContinueStmt const&) +{ + return "continue;\n"; +} + +string ProtoConverter::visit(ReturnStmt const& _returnstmt) +{ + return Whiskers(R"(return ; +)") + ("expr", visit(_returnstmt.value())) + .render(); +} + +pair ProtoConverter::visit(DoStmt const& _dostmt) +{ + string doCond = visit(_dostmt.condition()); + pair buffer = visit(_dostmt.statements()); + + return make_pair(buffer.first, Whiskers(R"(do { + + } while () + )") + ("cond", doCond) + ("statements", buffer.second) + .render()); +} + +pair ProtoConverter::visit(WhileStmt const& _whilestmt) +{ + string whileCond = visit(_whilestmt.condition()); + pair buffer = visit(_whilestmt.statements()); + + return make_pair(buffer.first, Whiskers(R"(while () { + + } + )") + ("cond", whileCond) + ("statements", buffer.second) + .render()); +} + +string ProtoConverter::visit(Expression const& _expr) +{ + switch (_expr.expr_oneof_case()) + { + case Expression::kLit: + return visit(_expr.lit()); + case Expression::kBop: + return visit(_expr.bop()); + case Expression::kUop: + return visit(_expr.uop()); + case Expression::kRef: + return visit(_expr.ref()); + case Expression::EXPR_ONEOF_NOT_SET: + return "\"\""; + } +} + +string ProtoConverter::visit(Literal const& _literal) +{ + switch (_literal.literal_oneof_case()) + { + case Literal::kBlit: + return _literal.blit() ? "true" : "false"; + case Literal::kSlit: + return "\"" + _literal.slit() + "\""; + case Literal::LITERAL_ONEOF_NOT_SET: + return "\"\""; + } +} + +string ProtoConverter::visit(BinaryOp const& _bop) +{ + string op; + switch (_bop.op()) + { + case BinaryOp_Operation_ADD: + op = "+"; + break; + case BinaryOp_Operation_SUB: + op = "-"; + break; + case BinaryOp_Operation_MUL: + op = "*"; + break; + case BinaryOp_Operation_DIV: + op = "/"; + break; + } + return Whiskers(R"( )") + ("arg1", visit(_bop.arg1())) + ("op", op) + ("arg2", visit(_bop.arg2())) + .render(); +} + +string ProtoConverter::visit(UnaryOp const& _uop) +{ + string op; + switch (_uop.op()) + { + case UnaryOp_Operation_INC: + op = "++"; + break; + case UnaryOp_Operation_DEC: + op = "--"; + break; + } + return Whiskers(R"()") + ("arg", visit(_uop.arg())) + ("op", op) + .render(); +} + +string ProtoConverter::visit(VarRef const& _varref) +{ + if (m_numVars > 0) + return solidity::test::abiv2fuzzer::ProtoConverter::s_stateVarNamePrefix + to_string(_varref.varnum() % m_numVars); + else + return "\"\""; +} + +string ProtoConverter::functionVisibility(ContractFunction::Visibility _vis) +{ + switch (_vis) + { + case ContractFunction_Visibility_PUBLIC: + return "public"; + case ContractFunction_Visibility_PRIVATE: + return "private"; + case ContractFunction_Visibility_EXTERNAL: + return "external"; + case ContractFunction_Visibility_INTERNAL: + return "internal"; + } +} + +string ProtoConverter::functionVisibility(LibraryFunction::Visibility _vis) +{ + switch (_vis) + { + case LibraryFunction_Visibility_PUBLIC: + return "public"; + case LibraryFunction_Visibility_PRIVATE: + return "private"; + case LibraryFunction_Visibility_EXTERNAL: + return "external"; + case LibraryFunction_Visibility_INTERNAL: + return "internal"; + } +} + +string ProtoConverter::stateMutability(ContractFunction::StateMutability _mut) +{ + switch (_mut) + { + case ContractFunction_StateMutability_PURE: + return "pure"; + case ContractFunction_StateMutability_VIEW: + return "view"; + case ContractFunction_StateMutability_PAYABLE: + return "payable"; + } +} + +string ProtoConverter::stateMutability(LibraryFunction::StateMutability _mut) +{ + switch (_mut) + { + case LibraryFunction_StateMutability_PURE: + return "pure"; + case LibraryFunction_StateMutability_VIEW: + return "view"; + } +} + +string ProtoConverter::stateMutability(InterfaceFunction::StateMutability _mut) +{ + switch (_mut) + { + case InterfaceFunction_StateMutability_PURE: + return "pure"; + case InterfaceFunction_StateMutability_VIEW: + return "view"; + case InterfaceFunction_StateMutability_PAYABLE: + return "payable"; + } +} \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToSol.h b/test/tools/ossfuzz/protoToSol.h new file mode 100644 index 000000000..cc7112555 --- /dev/null +++ b/test/tools/ossfuzz/protoToSol.h @@ -0,0 +1,150 @@ +/* + 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 + +namespace solidity::test::solprotofuzzer +{ +struct ProgramInfo +{ + enum class ProgramType + { + LIBRARY, + CONTRACT, + INTERFACE + }; + std::string m_name; + std::string m_userDefinedTypes; + ProgramType m_programType; +}; + +struct FunctionInfo +{ + enum class FunctionVisibility + { + PUBLIC, + PRIVATE, + INTERNAL, + EXTERNAL + }; + enum class StateMutability + { + PURE, + VIEW, + PAYABLE + }; + std::string m_name; + std::string m_params; + std::string m_returns; + FunctionVisibility m_visibility; + StateMutability m_mutability; + bool m_override; + bool m_virtual; +}; + +class ProtoConverter +{ +public: + ProtoConverter() {} + ProtoConverter(ProtoConverter const&) = delete; + ProtoConverter(ProtoConverter&&) = delete; + std::string protoToSolidity(Program const&); +private: + using CI = std::variant; + + std::string visit(Program const&); + std::string visit(ContractType const&); + std::string visit(ContractOrInterface const&); + std::string visit(Interface const&); + std::string visit(Contract const&); + std::string traverseOverrides( + Interface const&, + bool _isOverride, + bool _implement, + bool _inheritedByContract, + bool _isVirtual + ); + std::string traverseOverrides( + Contract const&, + bool _isAbstract + ); + std::string visit(Library const&); + std::pair visit(Block const&); + std::pair visit(Statement const&); + std::pair visit(solidity::test::abiv2fuzzer::VarDecl const&, bool _stateVar); + std::pair visit(IfStmt const&); + std::string visit(ForStmt const&); + std::string visit(SwitchStmt const&); + std::string visit(BreakStmt const&); + std::string visit(ContinueStmt const&); + std::string visit(ReturnStmt const&); + std::pair visit(DoStmt const&); + std::pair visit(WhileStmt const&); + std::string visit(Expression const&); + std::string visit(Literal const&); + std::string visit(BinaryOp const&); + std::string visit(UnaryOp const&); + std::string visit(VarRef const&); + std::tuple visit( + FunctionParamsAndReturns const& _pR, + bool _isExternal, + std::string _programName + ); + std::string visit( + InterfaceFunction const&, + unsigned _index, + bool _isOverride, + std::string _programName, + bool _implement = false, + bool _inheritedByContract = false, + bool _isVirtual = false + ); + std::string visit(LibraryFunction const& _func, unsigned _index, std::string _programName); + std::string visit( + ContractFunction const&, + unsigned _index, + bool _isOverride, + bool _isAbstractContract, + bool _isVirtual, + bool _isImplemented, + std::string _programName + ); + std::tuple visitContractHelper(CI _cOrI, std::string _programName); + std::string visit(Modifier const&); + static std::string functionVisibility(ContractFunction::Visibility _vis); + static std::string functionVisibility(LibraryFunction::Visibility _vis); + static std::string stateMutability(ContractFunction::StateMutability _mut); + static std::string stateMutability(LibraryFunction::StateMutability _mut); + static std::string stateMutability(InterfaceFunction::StateMutability _mut); + static bool disallowedContractFunction(ContractFunction const& _contractFunction, bool _isVirtual); + + unsigned m_numVars = 0; + unsigned m_numStructs = 0; + unsigned m_numMods = 0; + unsigned m_numContracts = 0; + bool m_isImplemented = false; + std::map m_interfaceNameMap; + std::map m_contractNameMap; + + static auto constexpr s_interfaceFunctionPrefix = "i"; + static auto constexpr s_libraryFunctionPrefix = "l"; + static auto constexpr s_contractFunctionPrefix = "c"; + static auto constexpr s_functionPrefix = "func"; +}; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/solProto.proto b/test/tools/ossfuzz/solProto.proto new file mode 100644 index 000000000..7a0b8c14d --- /dev/null +++ b/test/tools/ossfuzz/solProto.proto @@ -0,0 +1,228 @@ +/* + 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 "abiV2Proto.proto"; + +message FunctionParamsAndReturns { + repeated solidity.test.abiv2fuzzer.Type params = 1; + repeated solidity.test.abiv2fuzzer.Type returns = 2; +} + +message InterfaceFunction { + enum StateMutability { + PURE = 0; + VIEW = 1; + PAYABLE = 2; + } + required FunctionParamsAndReturns paramandret = 1; + required StateMutability mut = 2; + required bool override = 3; +} + +message LibraryFunction { + // Library functions cannot be payable + enum StateMutability { + PURE = 0; + VIEW = 1; + } + enum Visibility { + PUBLIC = 0; + EXTERNAL = 1; + INTERNAL = 2; + PRIVATE = 3; + } + required FunctionParamsAndReturns paramandret = 1; + required Visibility vis = 2; + required StateMutability mut = 3; + required Block b = 4; + optional Modifier m = 5; +} + +message ContractFunction { + enum StateMutability { + PURE = 0; + VIEW = 1; + PAYABLE = 2; + } + enum Visibility { + PUBLIC = 0; + EXTERNAL = 1; + INTERNAL = 2; + PRIVATE = 3; + } + required FunctionParamsAndReturns paramandret = 1; + required Visibility vis = 2; + required StateMutability mut = 3; + required Block b = 4; + optional Modifier m = 5; + required bool implemented = 6; + required bool virtualfunc = 7; + required bool override = 8; +} + +message Modifier { + repeated solidity.test.abiv2fuzzer.Type params = 1; + required Block b = 2; +} + +/// Expressions +message Expression { + oneof expr_oneof { + Literal lit = 1; + BinaryOp bop = 2; + UnaryOp uop = 3; + VarRef ref = 4; + } +} + +message VarRef { + required int32 varnum = 1; +} + +message Literal { + oneof literal_oneof { + bool blit = 1; + string slit = 2; + } +} + +message BinaryOp { + enum Operation { + ADD = 1; + SUB = 2; + MUL = 3; + DIV = 4; + } + required Operation op = 1; + required Expression arg1 = 2; + required Expression arg2 = 3; +} + +message UnaryOp { + enum Operation { + INC = 1; + DEC = 2; + } + required Operation op = 1; + required Expression arg = 2; +} + +/// Statements +message ElseStmt { + required Block statements = 1; +} + +message IfStmt { + required Expression condition = 1; + required Block statements = 2; + optional ElseStmt else = 3; +} + +message ForStmt { + required Block pre = 1; + required Expression condition = 2; + required Block post = 3; + required Block body = 4; +} + +message CaseStmt { + required Literal lit = 1; + required Block block = 2; +} + +message SwitchStmt { + required Expression condition = 1; + repeated CaseStmt cases = 2; + optional Block default = 3; +} + +message BreakStmt {} + +message ContinueStmt {} + +message ReturnStmt { + required Expression value = 1; +} + +message DoStmt { + required Expression condition = 1; + required Block statements = 2; +} + +message WhileStmt { + required Expression condition = 1; + required Block statements = 2; +} + +message Statement { + oneof stmt_oneof { + solidity.test.abiv2fuzzer.VarDecl var = 1; + IfStmt ifstmt = 2; + ForStmt forstmt = 3; + SwitchStmt switchstmt = 4; + BreakStmt breakstmt = 5; + ContinueStmt continuestmt = 6; + ReturnStmt returnstmt = 7; + DoStmt dostmt = 8; + WhileStmt whilestmt = 9; + } +} + +message Block { + repeated Statement statements = 1; +} + +message Library { + repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; + repeated LibraryFunction funcdef = 2; +} + +message Interface { + repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; + repeated InterfaceFunction funcdef = 2; + repeated Interface ancestors = 3; +} + +message Contract { + repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; + repeated ContractFunction funcdef = 2; + required bool abstract = 3; + repeated ContractOrInterface ancestors = 4; +} + +message ContractOrInterface { + oneof contract_or_interface_oneof { + Contract c = 1; + Interface i = 2; + } +} + +message ContractType { + oneof contract_type_oneof { + Contract c = 1; + Library l = 2; + Interface i = 3; + } +} + +message Program { + repeated ContractType contracts = 1; +} + +package solidity.test.solprotofuzzer; diff --git a/test/tools/ossfuzz/solProtoFuzzer.cpp b/test/tools/ossfuzz/solProtoFuzzer.cpp new file mode 100644 index 000000000..7c2914312 --- /dev/null +++ b/test/tools/ossfuzz/solProtoFuzzer.cpp @@ -0,0 +1,44 @@ +/* + 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 + +using namespace solidity::test::solprotofuzzer; +using namespace std; + +DEFINE_PROTO_FUZZER(Program const& _input) +{ + ProtoConverter converter; + string sol_source = converter.protoToSolidity(_input); + + 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()); + } + FuzzerUtil::testCompiler(sol_source, /*optimize=*/false); + return; +}