From 8973ca3c85f4ab737ad172dde91e0d2d6f3802a7 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Fri, 10 Apr 2020 18:29:03 +0200 Subject: [PATCH] Add adaptor and support library and interface inheritance --- liblangutil/Exceptions.h | 1 + libsolidity/codegen/ArrayUtils.cpp | 3 +- libsolidity/codegen/CompilerUtils.cpp | 27 +- libyul/backends/evm/AsmCodeGen.cpp | 3 +- test/tools/fuzzer_common.cpp | 8 +- test/tools/ossfuzz/SolProtoAdaptor.cpp | 662 ++++++++++++ test/tools/ossfuzz/SolProtoAdaptor.h | 520 +++++++++ test/tools/ossfuzz/abiV2FuzzerCommon.cpp | 7 +- test/tools/ossfuzz/abiV2FuzzerCommon.h | 5 +- test/tools/ossfuzz/protoToSol.cpp | 1227 ++++++++++------------ test/tools/ossfuzz/protoToSol.h | 248 +++-- test/tools/ossfuzz/solProto.proto | 174 +-- test/tools/ossfuzz/solProtoFuzzer.cpp | 211 +++- 13 files changed, 2185 insertions(+), 911 deletions(-) create mode 100644 test/tools/ossfuzz/SolProtoAdaptor.cpp create mode 100644 test/tools/ossfuzz/SolProtoAdaptor.h diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index b8688afa6..908ff7904 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -42,6 +42,7 @@ struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {}; struct InvalidAstError: virtual util::Exception {}; +struct FuzzerError: virtual util::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 089d0f5cf..2ae54749a 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, + CompilerError, "Stack too deep, try removing local variables." ); // fetch target storage reference diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 3d0117707..47ad59f5f 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, + CompilerError, + "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, + CompilerError, "Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables." ); m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; @@ -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, + CompilerError, + "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, + CompilerError, + "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, + CompilerError, + "Stack too deep, try removing local variables." + ); for (unsigned i = 1; i < _items; ++i) m_context << swapInstruction(i); } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index a864b0a09..06e7673cb 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -208,8 +208,9 @@ void CodeGenerator::assemble( } catch (StackTooDeepError const& _e) { - yulAssert( + assertThrow( false, + CompilerError, "Stack too deep when compiling inline assembly" + (_e.comment() ? ": " + *_e.comment() : ".") ); diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index e4de0f185..c1839d3f8 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -85,7 +85,7 @@ void FuzzerUtil::testCompiler(string const& _input, bool _optimize) compiler.setOptimiserSettings(optimiserSettings); try { - compiler.compile(); + solAssert(compiler.compile(), "Compilation failed"); } catch (Error const&) { @@ -96,6 +96,12 @@ void FuzzerUtil::testCompiler(string const& _input, bool _optimize) catch (UnimplementedFeatureError const&) { } + catch (CompilerError const&) + { + } + catch (StackTooDeepException const&) + { + } } void FuzzerUtil::runCompiler(string const& _input, bool _quiet) diff --git a/test/tools/ossfuzz/SolProtoAdaptor.cpp b/test/tools/ossfuzz/SolProtoAdaptor.cpp new file mode 100644 index 000000000..ef56272dc --- /dev/null +++ b/test/tools/ossfuzz/SolProtoAdaptor.cpp @@ -0,0 +1,662 @@ +/* + 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::adaptor; +using namespace solidity::test::solprotofuzzer; +using namespace std; +using namespace solidity::util; + +namespace +{ +SolFunctionStateMutability mutabilityConverter(InterfaceFunction_StateMutability _mut) +{ + switch (_mut) + { + case InterfaceFunction_StateMutability_PURE: + return SolFunctionStateMutability::PURE; + case InterfaceFunction_StateMutability_VIEW: + return SolFunctionStateMutability::VIEW; + case InterfaceFunction_StateMutability_PAYABLE: + return SolFunctionStateMutability::PAYABLE; + } +} + +SolFunctionStateMutability mutabilityConverter(ContractFunction_StateMutability _mut) +{ + switch (_mut) + { + case ContractFunction_StateMutability_PURE: + return SolFunctionStateMutability::PURE; + case ContractFunction_StateMutability_VIEW: + return SolFunctionStateMutability::VIEW; + case ContractFunction_StateMutability_PAYABLE: + return SolFunctionStateMutability::PAYABLE; + } +} + +SolLibraryFunctionStateMutability mutabilityConverter(LibraryFunction_StateMutability _mut) +{ + switch (_mut) + { + case LibraryFunction_StateMutability_PURE: + return SolLibraryFunctionStateMutability::PURE; + case LibraryFunction_StateMutability_VIEW: + return SolLibraryFunctionStateMutability::VIEW; + } +} + +SolFunctionVisibility visibilityConverter(ContractFunction_Visibility _vis) +{ + switch (_vis) + { + case ContractFunction_Visibility_PUBLIC: + return SolFunctionVisibility::PUBLIC; + case ContractFunction_Visibility_PRIVATE: + return SolFunctionVisibility::PRIVATE; + case ContractFunction_Visibility_EXTERNAL: + return SolFunctionVisibility::EXTERNAL; + case ContractFunction_Visibility_INTERNAL: + return SolFunctionVisibility::INTERNAL; + } +} + +SolFunctionVisibility visibilityConverter(LibraryFunction_Visibility _vis) +{ + switch (_vis) + { + case LibraryFunction_Visibility_PUBLIC: + return SolFunctionVisibility::PUBLIC; + case LibraryFunction_Visibility_PRIVATE: + return SolFunctionVisibility::PRIVATE; + case LibraryFunction_Visibility_EXTERNAL: + return SolFunctionVisibility::EXTERNAL; + case LibraryFunction_Visibility_INTERNAL: + return SolFunctionVisibility::INTERNAL; + } +} + +string functionVisibility(SolFunctionVisibility _vis) +{ + switch (_vis) + { + case SolFunctionVisibility::PUBLIC: + return "public"; + case SolFunctionVisibility::PRIVATE: + return "private"; + case SolFunctionVisibility::EXTERNAL: + return "external"; + case SolFunctionVisibility::INTERNAL: + return "internal"; + } +} + +string functionMutability(SolFunctionStateMutability _mut) +{ + switch (_mut) + { + case SolFunctionStateMutability::PURE: + return "pure"; + case SolFunctionStateMutability::VIEW: + return "view"; + case SolFunctionStateMutability::PAYABLE: + return "payable"; + } +} + +string libraryFunctionMutability(SolLibraryFunctionStateMutability _mut) +{ + switch (_mut) + { + case SolLibraryFunctionStateMutability::PURE: + return "pure"; + case SolLibraryFunctionStateMutability::VIEW: + return "view"; + } +} +} + +SolInterfaceFunction::SolInterfaceFunction( + std::string _functionName, + SolFunctionStateMutability _mutability, + bool _override +) +{ + m_functionName = _functionName; + m_mutability = _mutability; + m_override = _override; +} + +bool SolInterfaceFunction::operator==(SolInterfaceFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() == _rhs.name(); +} + +bool SolInterfaceFunction::operator!=(SolInterfaceFunction const& _rhs) const +{ + // TODO: Change this once we permit arbitrary function parameter types + return name() != _rhs.name(); +} + +string SolInterfaceFunction::str() const +{ + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", "0") + .render(); + + return Whiskers(R"( + function () external override + virtual returns (uint); + )") + ("functionName", name()) + ("stateMutability", functionMutability(mutability())) + ("isOverride", override()) + ("isVirtual", isVirtual()) + ("isImplemented", implement()) + ("body", bodyStr) + .render(); +} + +SolContractFunction::SolContractFunction( + ContractFunction const& _function, + std::string _contractName, + std::string _functionName, + std::string _returnValue +) +{ + m_contractName = _contractName; + m_functionName = _functionName; + m_visibility = visibilityConverter(_function.vis()); + m_mutability = mutabilityConverter(_function.mut()); + m_virtual = _function.virtualfunc(); + m_returnValue = _returnValue; +} + +bool SolContractFunction::operator==(SolContractFunction const& _rhs) const +{ + return this->m_visibility == _rhs.m_visibility && this->m_mutability == _rhs.m_mutability; +} + +bool SolContractFunction::operator!=(SolContractFunction const& _rhs) const +{ + return this->m_visibility != _rhs.m_visibility || this->m_mutability != _rhs.m_mutability; +} + +string SolContractFunction::str() const +{ + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", returnValue()) + .render(); + + return Whiskers(R"( + function () override + virtual + returns (uint);)") + ("functionName", name()) + ("isVirtual", isVirtual()) + ("isOverride", "_override") + ("visibility", functionVisibility(visibility())) + ("stateMutability", functionMutability(mutability())) + ("body", bodyStr) + ("isImplemented", "_implement") + .render(); +} + +SolLibraryFunction::SolLibraryFunction( + LibraryFunction const& _function, + std::string _libraryName, + std::string _functionName, + std::string _returnValue +) +{ + m_libraryName = _libraryName; + m_functionName = _functionName; + m_visibility = visibilityConverter(_function.vis()); + m_mutability = mutabilityConverter(_function.mut()); + m_returnValue = _returnValue; +} + +string SolLibraryFunction::str() const +{ + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", returnValue()) + .render(); + + return Whiskers(R"( + function (uint) returns (uint))") + ("functionName", name()) + ("visibility", functionVisibility(visibility())) + ("stateMutability", libraryFunctionMutability(mutability())) + ("body", bodyStr) + .render(); +} + +SolBaseContract::SolBaseContract(ProtoBaseContract _base, string _name, shared_ptr _prng) +{ + if (auto c = get(_base)) + m_base.push_back( + make_shared(SolContract(*c, _name, _prng)) + ); + else if (auto i = get(_base)) + m_base.push_back( + make_shared(SolInterface(*i, _name, _prng)) + ); +} + +void SolInterface::overrideHelper( + shared_ptr _function, + shared_ptr _base +) +{ + auto functionName = _function->name(); + auto mutability = _function->mutability(); + // Check if two or more bases define this function + bool multipleOverride = false; + // If function has already been overridden, add + // new base to list of overridden bases + for (auto &m: m_overrideMap) + { + // Must override if two or more bases define the + // same function + if (m.first->operator==(*_function)) + { + // Report error if state mutability of identically + // named functions differ + if (m.first->mutability() != _function->mutability()) + assertThrow( + false, + langutil::FuzzerError, + "Input specifies multiple function overrides with identical names" + " and parameter types but different mutability." + ); + // Add new base to list of overridden bases + m_overrideMap[m.first].push_back(_base); + multipleOverride = true; + break; + } + } + // If function has not been overridden, add new override + if (!multipleOverride) + m_overrideMap.insert( + pair( + make_shared( + SolInterfaceFunction( + functionName, + mutability, + true + ) + ), + vector>{_base} + ) + ); +} + +void SolInterface::addOverrides() +{ + for (auto &base: m_baseInterfaces) + { + // Override base interface functions + for (auto &f: base->m_interfaceFunctions) + overrideHelper(f, base); + // Override base interface functions that are themselves overrides + for (auto &e: base->m_overrideMap) + overrideHelper(e.first, base); + } +} + +void SolInterface::addBases(Interface const& _interface) +{ + for (auto &b: _interface.bases()) + { + auto base = make_shared(SolInterface(b, newBaseName(), m_prng)); + m_baseInterfaces.push_back(base); + cout << "Added " << base->name() << " as base" << endl; + // Worst case, we override all base functions so we + // increment derived contract's function index by + // this amount. + m_functionIndex += base->functionIndex(); + m_lastBaseName = base->lastBaseName(); + } +} + +void SolInterface::addFunctions(Interface const& _interface) +{ + for (auto &f: _interface.funcdef()) + m_interfaceFunctions.push_back( + make_shared( + SolInterfaceFunction( + newFunctionName(), + mutabilityConverter(f.mut()) + ) + ) + ); +} + +SolInterface::SolInterface(Interface const& _interface, string _name, shared_ptr _prng) +{ + cout << "Constructing " << _name << endl; + m_prng = _prng; + m_interfaceName = _name; + m_lastBaseName = m_interfaceName; + addBases(_interface); + addOverrides(); + addFunctions(_interface); +} + +string SolInterface::baseNames() const +{ + ostringstream bases; + string separator{}; + for (auto &b: m_baseInterfaces) + { + bases << separator << b->name(); + if (separator.empty()) + separator = ", "; + } + return bases.str(); +} + +string SolInterface::baseInterfaceStr() const +{ + ostringstream baseInterfaces; + for (auto &b: m_baseInterfaces) + baseInterfaces << b->str(); + + return baseInterfaces.str(); +} + +string SolInterface::overrideStr() const +{ + ostringstream overriddenFunctions; + for (auto &f: m_overrideMap) + { + ostringstream overriddenBaseNames; + if (f.second.size() > 1) + { + string sep{}; + for (auto &b: f.second) + { + overriddenBaseNames << Whiskers(R"()") + ("sep", sep) + ("name", b->name()) + .render(); + if (sep.empty()) + sep = ", "; + } + } + overriddenFunctions << Whiskers(R"( + function () external override() returns (uint);)") + ("functionName", f.first->name()) + ("stateMutability", functionMutability(f.first->mutability())) + ("multiple", f.second.size() > 1) + ("baseNames", overriddenBaseNames.str()) + .render(); + } + return overriddenFunctions.str(); +} + +string SolInterface::str() const +{ + ostringstream functions; + ostringstream bases; + + // Print overridden functions + functions << overrideStr(); + // Print non-overridden functions + for (auto &f: m_interfaceFunctions) + functions << f->str(); + + for (auto &b: m_baseInterfaces) + bases << b->str(); + + return Whiskers(R"( + +interface is { + +})") + ("bases", bases.str()) + ("programName", name()) + ("inheritance", m_baseInterfaces.size() > 0) + ("baseNames", baseNames()) + ("functionDefs", functions.str()) + .render(); +} + +SolContract::SolContract(Contract const& _contract, std::string _name, shared_ptr _prng) +{ + m_prng = _prng; + m_contractName = _name; + m_abstract = _contract.abstract(); + for (auto &f: _contract.funcdef()) + m_contractFunctions.push_back( + make_unique( + SolContractFunction( + f, + m_contractName, + newFunctionName(), + newReturnValue() + ) + ) + ); + for (auto &b: _contract.bases()) + { + switch (b.contract_or_interface_oneof_case()) + { + case ContractOrInterface::kC: + m_baseContracts.push_back( + make_unique( + SolBaseContract(&b.c(), newContractBaseName(), m_prng) + ) + ); + break; + case ContractOrInterface::kI: + m_baseContracts.push_back( + make_unique( + SolBaseContract(&b.i(), newInterfaceBaseName(), m_prng) + ) + ); + break; + case ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET: + break; + } + } +} + +void SolLibrary::addFunction(LibraryFunction const& _function) +{ + // Register function name and return value + string functionName = newFunctionName(); + string outputStr = newReturnValue(); + + SolFunctionVisibility visibility = visibilityConverter(_function.vis()); + + if (visibility == SolFunctionVisibility::PUBLIC || visibility == SolFunctionVisibility::EXTERNAL) + { + solAssert(!m_publicFunctionMap.count(functionName), "Sol proto adapter: Duplicate library function"); + m_publicFunctionMap.insert(pair(functionName, outputStr)); + } + + // Create and add function to library + m_functions.push_back( + make_unique( + SolLibraryFunction(_function, + name(), + functionName, + outputStr + ) + ) + ); +} + +SolLibrary::SolLibrary(Library const& _library, string _name) +{ + m_libraryName = _name; + for (LibraryFunction const& f: _library.funcdef()) + addFunction(f); +} + +string SolLibrary::str() const +{ + ostringstream functions; + + for (auto &f: m_functions) + functions << f->str(); + + return Whiskers(R"( +library { + +})") + ("name", name()) + ("functions", functions.str()) + .render(); +} + +bool SolLibrary::validTest() const +{ + return m_publicFunctionMap.size() > 0; +} + +pair SolLibrary::pseudoRandomTest(unsigned _randomIdx) +{ + solAssert(m_publicFunctionMap.size() > 0, "Sol proto adaptor: Empty library map"); + string chosenFunction; + unsigned numFunctions = m_publicFunctionMap.size(); + unsigned functionIndex = _randomIdx % numFunctions; + unsigned mapIdx = 0; + for (auto &e: m_publicFunctionMap) + { + if (functionIndex == mapIdx) + chosenFunction = e.first; + mapIdx++; + } + solAssert(m_publicFunctionMap.count(chosenFunction), "Sol proto adaptor: Invalid library function chosen"); + return pair(chosenFunction, m_publicFunctionMap[chosenFunction]); +} + +CFunctionOverride::CFunctionOverrideType CFunctionOverride::functionType() const +{ + if (holds_alternative>(m_function.second)) + return CFunctionOverrideType::CONTRACT; + solAssert(interfaceFunction(), "Sol proto fuzzer: Invalid override function type"); + return CFunctionOverrideType::INTERFACE; +} + +string CFunctionOverride::name() const +{ + if (holds_alternative>(m_function.second)) + return get>(m_function.second)->name(); + solAssert(interfaceFunction(), "Sol proto fuzzer: Invalid override function type"); + return get>(m_function.second)->name(); +} + +bool CFunctionOverride::interfaceFunction() const +{ + return functionType() == CFunctionOverrideType::INTERFACE; +} + +bool CFunctionOverride::contractFunction() const +{ + return functionType() == CFunctionOverrideType::CONTRACT; +} + +SolFunctionVisibility CFunctionOverride::visibility() const +{ + if (contractFunction()) + return get>(m_function.second)->visibility(); + solAssert(interfaceFunction(), "Sol proto fuzzer: Invalid override function type"); + return SolFunctionVisibility::EXTERNAL; +} + +SolFunctionStateMutability CFunctionOverride::mutability() const +{ + if (contractFunction()) + return get>(m_function.second)->mutability(); + solAssert(interfaceFunction(), "Sol proto fuzzer: Invalid override function type"); + return get>(m_function.second)->mutability(); +} + +string CFunctionOverride::str() const +{ + string bodyStr = Whiskers(R"( + { + return ; + })") + ("uint", returnValue()) + .render(); + + return Whiskers(R"( + function () override virtual + returns (uint);)") + ("functionName", name()) + ("isVirtual", virtualized() || !implemented()) + ("visibility", functionVisibility(visibility())) + ("stateMutability", functionMutability(mutability())) + ("body", bodyStr) + ("isImplemented", implemented()) + .render(); +} + +//string CFunctionOverride::commaSeparatedBaseNames() +//{ +// ostringstream baseNames; +// string separator{}; +// for (auto &override: m_function) +// { +// auto base = override.first; +// string baseName; +// if (auto b = get(base)) +// baseName = b->name(); +// else +// baseName = get(base)->name(); +// +// baseNames << Whiskers(R"()") +// ("sep", separator) +// ("base", baseName) +// .render(); +// if (separator.empty()) +// separator = ", "; +// } +// return baseNames.str(); +//} + +//string CFunctionOverride::baseName() const +//{ +// auto base = m_function.first; +// if (contractFunction()) +// return get>(base)->name(); +// solAssert(interfaceFunction(), "Sol proto fuzzer: Invalid override function type"); +// return get>(base)->name(); +//} \ No newline at end of file diff --git a/test/tools/ossfuzz/SolProtoAdaptor.h b/test/tools/ossfuzz/SolProtoAdaptor.h new file mode 100644 index 000000000..76c1c36d1 --- /dev/null +++ b/test/tools/ossfuzz/SolProtoAdaptor.h @@ -0,0 +1,520 @@ +/* + 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 . +*/ +#pragma once + +#include + +#include +#include +#include +#include + +namespace solidity::test::solprotofuzzer::adaptor +{ +/// Solidity contract abstraction class +struct SolContract; +/// Solidity interface abstraction class +struct SolInterface; +/// Solidity library abstraction class +struct SolLibrary; +/// Solidity interface function abstraction class +struct SolInterfaceFunction; +/// Solidity contract function abstraction class +struct SolContractFunction; +/// Solidity library function abstraction class +struct SolLibraryFunction; +/// Solidity contract function override abstraction class +struct CFunctionOverride; +/// Solidity interface function override abstraction class +struct IFunctionOverride; + +/// Type that defines a contract function override as a variant of interface or +/// contract function types. +using OverrideFunction = std::variant, std::unique_ptr>; +/// Type that defines a list of base contracts as a list of variants of interface or +/// contract types. +using BaseContracts = std::vector, std::shared_ptr>>; +/// Type that defines a contract function override as a pair of base contracts +/// and its (their) function. +using OverrideCFunction = std::pair; +/// Type that defines an interface function override as a pair of interface +/// and its function. +using OverrideIFunction = std::pair>, std::unique_ptr>; +/// Type that defines a map of interface overrides +using InterfaceOverrideMap = std::map, std::vector>>; +/// Type that defines an interface function as either a vanilla interface function +/// or an override function. +using IFunction = std::variant, OverrideIFunction>; +/// Type that defines a contract function as either a vanilla contract function +/// or an override function. +using CFunction = std::variant, OverrideCFunction>; +/// Variant type that points to one of contract, interface protobuf messages +using ProtoBaseContract = std::variant; + +enum class SolFunctionVisibility +{ + PUBLIC, + PRIVATE, + INTERNAL, + EXTERNAL +}; + +enum class SolFunctionStateMutability +{ + PURE, + VIEW, + PAYABLE +}; + +enum class SolLibraryFunctionStateMutability +{ + PURE, + VIEW +}; + +struct SolInterfaceFunction +{ + SolInterfaceFunction( + std::string _functionName, + SolFunctionStateMutability _mutability, + bool _override = false + ); + bool operator==(SolInterfaceFunction const& _rhs) const; + bool operator!=(SolInterfaceFunction const& _rhs) const; + std::string str() const; + + std::string name() const + { + return m_functionName; + } + SolFunctionStateMutability mutability() const + { + return m_mutability; + } + bool override() const + { + return m_override; + } + bool isVirtual() const + { + return false; + } + bool implement() const + { + return false; + } + + std::string m_functionName; + SolFunctionStateMutability m_mutability = SolFunctionStateMutability::PURE; + bool m_override = false; + bool m_virtual = false; + bool m_implement = false; +}; + +struct SolContractFunction +{ + SolContractFunction( + ContractFunction const& _function, + std::string _contractName, + std::string _functionName, + std::string _returnValue + ); + bool operator==(SolContractFunction const& _rhs) const; + bool operator!=(SolContractFunction const& _rhs) const; + std::string str() const; + + std::string name() const + { + return m_functionName; + } + std::string contractName() const + { + return m_contractName; + } + bool isVirtual() const + { + return m_virtual; + } + std::string returnValue() const + { + return m_returnValue; + } + SolFunctionVisibility visibility() const + { + return m_visibility; + } + SolFunctionStateMutability mutability() const + { + return m_mutability; + } + + std::string m_contractName; + std::string m_functionName; + SolFunctionVisibility m_visibility = SolFunctionVisibility::PUBLIC; + SolFunctionStateMutability m_mutability = SolFunctionStateMutability::PURE; + bool m_virtual = false; + std::string m_returnValue; +}; + +struct SolLibraryFunction +{ + SolLibraryFunction( + LibraryFunction const& _function, + std::string _libraryName, + std::string _functionName, + std::string _returnValue + ); + std::string str() const; + + std::string name() const + { + return m_functionName; + } + std::string libraryName() const + { + return m_libraryName; + } + std::string returnValue() const + { + return m_returnValue; + } + SolFunctionVisibility visibility() const + { + return m_visibility; + } + SolLibraryFunctionStateMutability mutability() const + { + return m_mutability; + } + + std::string m_libraryName; + std::string m_functionName; + SolFunctionVisibility m_visibility = SolFunctionVisibility::PUBLIC; + SolLibraryFunctionStateMutability m_mutability = SolLibraryFunctionStateMutability::PURE; + std::string m_returnValue; +}; + +struct SolLibrary +{ + SolLibrary(Library const& _library, std::string _name); + std::vector> m_functions; + /// Maps publicly exposed function name to expected output + std::map m_publicFunctionMap; + + void addFunction(LibraryFunction const& _function); + + bool validTest() const; + + /// Returns a pair of function name and expected output + /// that is pseudo randomly chosen from the list of all + /// library functions. + /// @param _randomIdx A pseudo randomly generated number that is + /// used to index the list of all library functions. + std::pair pseudoRandomTest(unsigned _randomIdx); + + std::string str() const; + + std::string name() const + { + return m_libraryName; + } + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + std::string newReturnValue() + { + return std::to_string(m_returnValue++); + } + + std::string m_libraryName; + unsigned m_functionIndex = 0; + unsigned m_returnValue = 0; +}; + +struct SolBaseContract +{ + SolBaseContract(ProtoBaseContract _base, std::string _name, std::shared_ptr _prng); + + BaseContracts m_base; + std::string m_baseName; + std::shared_ptr m_prng; +}; + +struct SolContract +{ + SolContract(Contract const& _contract, std::string _name, std::shared_ptr _prng); + + std::string str() const; + + unsigned randomNumber() + { + return m_prng->operator()(); + } + std::string name() const + { + return m_contractName; + } + + bool abstract() const + { + return m_abstract; + } + + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + + std::string newContractBaseName() + { + return name() + "B" + std::to_string(m_baseIndex++); + } + + std::string newInterfaceBaseName() + { + return "IB" + std::to_string(m_baseIndex++); + } + + std::string newReturnValue() + { + return std::to_string(m_returnValue++); + } + + std::string m_contractName; + bool m_abstract = false; + unsigned m_baseIndex = 0; + unsigned m_functionIndex = 0; + unsigned m_returnValue = 0; + std::vector> m_contractFunctions; + std::vector> m_baseContracts; + std::vector> m_overriddenFunctions; + std::shared_ptr m_prng; +}; + +struct SolInterface +{ + SolInterface( + Interface const& _interface, + std::string _interfaceName, + std::shared_ptr _prng + ); + + std::string name() const + { + return m_interfaceName; + } + + unsigned randomNumber() const + { + return m_prng->operator()(); + } + + bool coinFlip() const + { + return randomNumber() % 2 == 0; + } + + std::string newFunctionName() + { + return "f" + std::to_string(m_functionIndex++); + } + void incrementFunctionIndex() + { + m_functionIndex++; + } + void resetFunctionIndex() + { + m_functionIndex = 0; + } + void setFunctionIndex(unsigned _index) + { + m_functionIndex = _index; + } + unsigned functionIndex() const + { + return m_functionIndex; + } + std::string newBaseName() + { + m_lastBaseName += "B"; + return m_lastBaseName; + } + std::string lastBaseName() + { + return m_lastBaseName; + } + + std::string str() const; + std::string overrideStr() const; + + /// Returns the Solidity code for all base interfaces + /// inherited by this interface. + std::string baseInterfaceStr() const; + + /// Returns comma-space separated names of base interfaces inherited by + /// this interface. + std::string baseNames() const; + + /// Add base contracts in a depth-first manner + void addBases(Interface const& _interface); + /// Add functions + void addFunctions(Interface const& _interface); + /// Add overrides + void addOverrides(); + /// Helper for adding overrides + void overrideHelper( + std::shared_ptr _function, + std::shared_ptr _interface + ); + + unsigned m_functionIndex = 0; + std::string m_lastBaseName; + std::string m_interfaceName; + std::vector> m_interfaceFunctions; + std::vector> m_baseInterfaces; + InterfaceOverrideMap m_overrideMap; + std::shared_ptr m_prng; +}; + +struct CFunctionOverride +{ + CFunctionOverride( + BaseContracts _base, + OverrideFunction _function, + bool _implemented, + bool _virtualized, + bool _redeclared, + std::string _returnValue + ) + { + m_function = std::make_pair(_base, std::move(_function)); + m_implemented = _implemented; + m_virtualized = _virtualized; + m_redeclared = _redeclared; + m_returnValue = _returnValue; + } + + enum class CFunctionOverrideType + { + INTERFACE, + CONTRACT + }; + + std::string str() const; + + std::string name() const; + + CFunctionOverrideType functionType() const; + + bool interfaceFunction() const; + + bool contractFunction() const; + + SolFunctionVisibility visibility() const; + + SolFunctionStateMutability mutability() const; + + std::string commaSeparatedBaseNames() const; + + std::string baseName() const; + + /// Overridden function + OverrideCFunction m_function; + /// Flag that is true if overridden function is implemented + bool m_implemented = true; + /// Flag that is true if overridden function is marked virtual + bool m_virtualized = false; + /// Flag that is true if overridden function is redeclared but not implemented + bool m_redeclared = false; + /// The uint value to be returned by the overridden function if it is + /// implemented + std::string m_returnValue; + + bool implemented() const + { + return m_implemented; + } + + bool virtualized() const + { + return m_virtualized; + } + + bool redeclared() const + { + return m_redeclared; + } + + std::string returnValue() const + { + return m_returnValue; + } +}; + +struct IFunctionOverride +{ +IFunctionOverride( + std::vector> _base, + std::unique_ptr _function, + bool _implement, + bool _virtual, + bool _redeclare, + std::string _returnValue = "" +) +{ + m_function = std::make_pair(_base, std::move(_function)); + m_implemented = _implement; + m_virtualized = _virtual; + m_redeclared = _redeclare; + m_returnValue = _returnValue; +} + + std::string str() const; + + bool implemented() const + { + return m_implemented; + } + + bool virtualized() const + { + return m_virtualized; + } + + bool redeclared() const + { + return m_redeclared; + } + + std::string returnValue() const + { + return m_returnValue; + } + + OverrideIFunction m_function; + /// Flag that is true if overridden function is implemented in derived contract + bool m_implemented = false; + /// Flag that is true if overridden function implemented in derived contract is + /// marked virtual + bool m_virtualized = false; + /// Flag that is true if overridden function is redeclared but not implemented + bool m_redeclared = false; + /// The uint value to be returned if the overridden interface function is implemented + std::string m_returnValue; +}; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.cpp b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp index f30a8517a..c9109ecdb 100644 --- a/test/tools/ossfuzz/abiV2FuzzerCommon.cpp +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp @@ -9,13 +9,16 @@ SolidityCompilationFramework::SolidityCompilationFramework(langutil::EVMVersion solidity::bytes SolidityCompilationFramework::compileContract( std::string const& _sourceCode, - std::string const& _contractName + std::string const& _contractName, + std::map const& _libraryAddresses, + frontend::OptimiserSettings _optimization ) { std::string sourceCode = _sourceCode; m_compiler.setSources({{"", sourceCode}}); + m_compiler.setLibraries(_libraryAddresses); m_compiler.setEVMVersion(m_evmVersion); - m_compiler.setOptimiserSettings(m_optimiserSettings); + m_compiler.setOptimiserSettings(_optimization); if (!m_compiler.compile()) { langutil::SourceReferenceFormatter formatter(std::cerr); diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.h b/test/tools/ossfuzz/abiV2FuzzerCommon.h index 3094fbdc1..e18458b77 100644 --- a/test/tools/ossfuzz/abiV2FuzzerCommon.h +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.h @@ -23,12 +23,13 @@ public: } bytes compileContract( std::string const& _sourceCode, - std::string const& _contractName = {} + std::string const& _contractName, + std::map const& _libraryAddresses = {}, + frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal() ); protected: frontend::CompilerStack m_compiler; langutil::EVMVersion m_evmVersion; - frontend::OptimiserSettings m_optimiserSettings = frontend::OptimiserSettings::none(); }; } diff --git a/test/tools/ossfuzz/protoToSol.cpp b/test/tools/ossfuzz/protoToSol.cpp index 3713685c1..dfc82df48 100644 --- a/test/tools/ossfuzz/protoToSol.cpp +++ b/test/tools/ossfuzz/protoToSol.cpp @@ -15,21 +15,104 @@ along with solidity. If not, see . */ -#include -#include "protoToSol.h" -#include "protoToAbiV2.h" +#include +#include + +#include #include +#include + using namespace solidity::test::solprotofuzzer; +using namespace solidity::test::solprotofuzzer::adaptor; using namespace std; using namespace solidity::util; string ProtoConverter::protoToSolidity(Program const& _p) { + m_randomGen = make_shared(_p.seed()); return visit(_p); } +string ProtoConverter::visit(TestContract const& _testContract) +{ + string testCode; + string usingLibDecl; + m_libraryTest = false; + + switch (_testContract.type()) + { + case TestContract::LIBRARY: + { + if (emptyLibraryTests()) + { + testCode = Whiskers(R"( + return 0;)") + .render(); + } + else + { + m_libraryTest = true; + auto testTuple = pseudoRandomLibraryTest(_testContract.programidx()); + m_libraryName = get<0>(testTuple); + usingLibDecl = Whiskers(R"( + using for uint;)") + ("libraryName", get<0>(testTuple)) + .render(); + testCode = Whiskers(R"( + uint x; + if (x.() != ) + return 1; + return 0;)") + ("testFunction", get<1>(testTuple)) + ("expectedOutput", get<2>(testTuple)) + .render(); + } + break; + } + case TestContract::CONTRACT: +#if 0 + testCode = Whiskers(R"( + testContract = new (); + if (testContract.() != ) + return 1; + return 0;)") + ("contractName", "") + ("testFunction", "") + ("expectedOutput", "") + .render(); +#else + testCode = Whiskers(R"( + return 0;)") + .render(); +#endif + break; + } + + return Whiskers(R"( +contract C { + function test() public returns (uint) + { + } +} +)") + ("isLibrary", m_libraryTest) + ("usingDecl", usingLibDecl) + ("testCode", testCode) + .render(); +} + +bool ProtoConverter::libraryTest() const +{ + return m_libraryTest; +} + +string ProtoConverter::libraryName() const +{ + return m_libraryName; +} + string ProtoConverter::visit(Program const& _p) { ostringstream program; @@ -40,25 +123,32 @@ string ProtoConverter::visit(Program const& _p) program << Whiskers(R"( pragma solidity >=0.0; -pragma experimental ABIEncoderV2; -pragma experimental SMTChecker; + + )") ("contracts", contracts.str()) + ("testContract", visit(_p.test())) .render(); return program.str(); } string ProtoConverter::visit(ContractType const& _contractType) { + m_mostDerivedAbstractContract = false; switch (_contractType.contract_type_oneof_case()) { case ContractType::kC: - return visit(_contractType.c()); + m_mostDerivedAbstractContract = _contractType.c().abstract(); + m_mostDerivedProgram = MostDerivedProgram::CONTRACT; +// return visit(_contractType.c()); + return ""; case ContractType::kL: + m_mostDerivedProgram = MostDerivedProgram::LIBRARY; return visit(_contractType.l()); case ContractType::kI: + m_mostDerivedProgram = MostDerivedProgram::INTERFACE; return visit(_contractType.i()); case ContractType::CONTRACT_TYPE_ONEOF_NOT_SET: return ""; @@ -70,734 +160,569 @@ string ProtoConverter::visit(ContractOrInterface const& _contractOrInterface) switch (_contractOrInterface.contract_or_interface_oneof_case()) { case ContractOrInterface::kC: - return visit(_contractOrInterface.c()); +// return visit(_contractOrInterface.c()); + return ""; case ContractOrInterface::kI: - return visit(_contractOrInterface.i()); +// return visit(_contractOrInterface.i()); + return ""; case ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET: return ""; } } -string ProtoConverter::traverseOverrides(Interface const& _interface, bool _isOverride, bool _implement, bool _inheritedByContract, bool _isVirtual) +bool ProtoConverter::contractFunctionImplemented( + Contract const* _contract, + CIFunc _function +) +{ + auto v = m_contractFunctionMap[_contract]; + for (auto &e: v) + if (get<0>(e) == _function) + return get<1>(e); + return false; +} + +bool ProtoConverter::contractFunctionVirtual( + Contract const* _contract, + CIFunc _function +) +{ + auto v = m_contractFunctionMap[_contract]; + for (auto &e: v) + if (get<0>(e) == _function) + return get<2>(e); + return false; +} + +#if 0 +string ProtoConverter::mostDerivedInterfaceOverrides(Interface const& _interface) { ostringstream funcs; - for (auto ancestor = _interface.ancestors().rbegin(); ancestor != _interface.ancestors().rend(); ancestor++) + for (auto base = _interface.bases().rbegin(); base != _interface.bases().rend(); base++) { - unsigned index = 0; - m_numContracts++; - - m_numStructs = 0; - - for (auto& f: ancestor->funcdef()) + for (auto& f: base->funcdef()) { + // An interface may override base interface's function + bool override = pseudoRandomCoinFlip(); + if (!override) + continue; + funcs << overrideFunction(&f, false, false); + } + funcs << mostDerivedInterfaceOverrides(*base); + } + return funcs.str(); +} + +string ProtoConverter::mostDerivedContractOverrides(Interface const& _interface) +{ + ostringstream funcs; + + for (auto base = _interface.bases().rbegin(); base != _interface.bases().rend(); base++) + { + for (auto& f: base->funcdef()) + { + bool override = pseudoRandomCoinFlip() || mostDerivedProgramAbstractContract(); + + /// We can arrive here from contract through other contracts and interfaces. + /// We define most derived contract (MDC) as the most derived contract in + /// the inheritence chain to which the visited interface belongs to. + + /// When MDC is not abstract we must implement (with override) all + /// interface functions that MDC inherits unless it has been implemented + /// by some other contract in the inheritence chain that MDC also derives + /// from. + /// In other words, if an interface function has never been implemented + /// thus far in the inheritence chain, it must be implemented. + /// If it has already been implemented, it may be reimplemented provided + /// it is virtual. + /// When reimplementing, it may be revirtualized. + + /// When MDC is abstract, we may or may not redeclare interface function. + /// If interface function has been implemented in the inheritence chain, + /// and we redeclare it, we must reimplement it. + /// If inheritence function has not been implemented and we redeclare it, + /// we may implement it. + /// If we implement it, it may be marked virtual. + /// If we don't implement it, it must be marked virtual. + + /// . We may virtualize. + /// We create a pair and add it to list of + /// implemented interface functions. + + /// When IAC is abstract: + /// - we may redeclare + /// - if redeclared, we may implement + /// - if we do not implement redeclared function then: + /// - we must override and virtualize + /// - add to list of unimplemented interface functions + /// - if we implement then: + /// - we must override + /// - we may virtualize + /// - create a pair and add it to list of + /// implemented interface functions. + + + /// - ancestor contract is not abstract and this interface function + /// has been implemented by some other contract in the traversal path + /// that also virtualizes the function. + /// When we override, we may mark it as virtual. + + /// We may override when ancestor contract is abstract + /// If ancestor contract is overriding, we may or may not implement it. + /// If we do not implement it, we must mark it virtual since otherwise + /// we are left with unimplementable function. + /// If we implement it, we may mark it as virtual. string funcStr = visit( f, index++, - true, - m_interfaceNameMap[&*ancestor], - _implement, - _inheritedByContract, - _isVirtual + override, + programName(&*base) ); - if (f.override() || _isOverride) + if (override) funcs << funcStr; } - funcs << traverseOverrides(*ancestor, _isOverride, _implement, _inheritedByContract, _isVirtual); + funcs << mostDerivedContractOverrides(*base); } return funcs.str(); } -string ProtoConverter::traverseOverrides(Contract const& _contract, bool _isAbstract) +pair ProtoConverter::contractFunctionParams( + Contract const* _contract, + ContractFunction const* _function +) { - 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(); + // If contract is abstract, we may implement this function. If contract + // is not abstract, we must implement this function. + bool implement = !_contract->abstract() || pseudoRandomCoinFlip(); + // We may mark a non-overridden function as virtual. + // We must mark an unimplemented abstract contract function as + // virtual. + bool virtualFunc = _function->virtualfunc() || (_contract->abstract() && !implement); + return pair(implement, virtualFunc); } -tuple ProtoConverter::visitContractHelper(CI _cOrI, string _programName) +pair ProtoConverter::contractFunctionOverrideParams( + Contract const* _base, + Contract const* _derived, + CIFunc _f +) { - ostringstream ancestors; - ostringstream funcs; - ostringstream ancestorNames; + bool baseAbstract = _base->abstract(); + bool derivedAbstract = _derived->abstract(); - string separator{}; - if (holds_alternative(_cOrI)) + bool implement = false; + bool revirtualize = false; + + // There are four possibilities here: + // 1. both base and derived are abstract, + // 2. base abstract, derived not + // 3. base not abstract, derived is + // 4. both base and derived are not abstract + if (baseAbstract && derivedAbstract) { - 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 = ", "; - } + // Case 1: Both base and derived are abstract + // virtual base functions may be redeclared (with override) + // if redeclared virtual base function has been implemented, it must be reimplemented but + // may be revirtualized + // if revirtualized, there is nothing to be changed in list of + // if not revirtualized, we changed the boolean to false in the list of implemented contract + // functions. + // if redeclared virtual base function has not been implemented, it may be implemented. + // if it is implemented (with override), it may be revirtualized. + // if it is not implemented, it must be marked virtual. + // if virtual base function not redeclared, there is no status change + bool virtualImplemented = contractFunctionImplemented(_base, _f); + implement = virtualImplemented || pseudoRandomCoinFlip(); + revirtualize = (!implement && !virtualImplemented) || pseudoRandomCoinFlip(); + } + else if (baseAbstract && !derivedAbstract) + { + // Case 2: Base abstract, derived not + // If base function appears in list of unimplemented virtual contract functions, we + // must implement it (with override). Remove from unimplemented virtual contract functions. We may + // revirtualize. Add to list of implemented contract functions. - unsigned wasNumContract = m_numContracts; + // Unimplemented virtual functions must be implemented (with override) - // First define overridden functions - bool overrides = interface->ancestors_size() > 0 && !ancestorNames.str().empty(); - if (overrides) - { - funcs << traverseOverrides(*interface, false, false, false, false); - } + // Implemented virtual functions may be implemented (with override) - 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 if (!baseAbstract && derivedAbstract) + { + // Case 3: Base not abstract, derived is + // All base functions are implemented. Base functions marked virtual may be overridden. + // If they are overridden they must be reimplemented. They may be revirtualized. + // Base functions that are not virtual may not be redeclared. } else { - auto contract = get(_cOrI); + // Case 4: Neither base nor derived are abstract + // All base functions are implemented. Base functions marked virtual may be overridden. + // If they are overridden they must be reimplemented. They may be revirtualized. + // Base functions that are not virtual may not be redeclared. + } + return pair(implement, revirtualize); +} - for (auto &ancestor: contract->ancestors()) +string ProtoConverter::traverseOverrides(Contract const& _contract) +{ + ostringstream funcs; + + for (auto base = _contract.bases().rbegin(); base != _contract.bases().rend(); base++) + { + if (base->contract_or_interface_oneof_case() == ContractOrInterface::CONTRACT_OR_INTERFACE_ONEOF_NOT_SET) + continue; + + if (base->has_c()) { - string ancestorStr = visit(ancestor); - if (ancestorStr.empty()) + for (auto& f: base->c().funcdef()) + { + // We may redeclare virtual functions in base contract + bool redeclareVirtual = f.virtualfunc() && pseudoRandomCoinFlip(); + // If base function is not virtual or if we choose to not + // redeclare the virtual function, we skip to the next function + // after incrementing the function index. + if (!f.virtualfunc() || !redeclareVirtual) + continue; + + // Check if overridden function may be implemented/revirtualized + // by the derived contract. + auto [implement, revirtualize] = contractFunctionOverrideParams( + &base->c(), + &_contract, + &f + ); + funcs << overrideFunction(&f, revirtualize, implement); + // Update contract function map + m_contractFunctionMap[&_contract].push_back(CITuple(&f, implement, revirtualize)); + } + // Override revirtualized functions. + // TODO: Function that returns all revirtualized functions and their implementation + // status. + for (auto &tuple: m_contractFunctionMap[&base->c()]) + { + auto function = get<0>(tuple); + bool overrideRevirtualized = true; + if (holds_alternative(function)) + { + auto contractFunction = get(function); + + auto [implementRevirtualized, revirtualizeRevirtualized] = contractFunctionOverrideParams( + &base->c(), + &_contract, + get<0>(tuple) + ); + funcs << visit( + *contractFunction, + index++, + overrideRevirtualized, + revirtualizeRevirtualized, + implementRevirtualized, + programName(&base->c()) + ); + } + } + // Traverse base + funcs << traverseOverrides(base->c()); + } + else if (base->has_i()) + { + for (auto& f: base->i().funcdef()) + { + bool implement = pseudoRandomCoinFlip(); + m_counter += base->i().funcdef_size(); + bool override = pseudoRandomCoinFlip(); + + if (_contract.abstract() && !implement && !override) + continue; + + funcs << overrideFunction( + &f, + !_contract.abstract() || implement, + pseudoRandomCoinFlip() || (override && _contract.abstract()) + ); + } + funcs << mostDerivedContractOverrides(base->i(), !_contract.abstract()); + } + } + return funcs.str(); +} + +/// This function is called when root is interface. +tuple ProtoConverter::visitMostDerivedInterface(Interface const& _interface) +{ + ostringstream bases; + ostringstream baseNames; + ostringstream funcs; + + string separator{}; + for (auto &base: _interface.bases()) + { + string baseStr = visit(base); + if (baseStr.empty()) + continue; + bases << baseStr; + baseNames << separator + << programName(&base); + if (separator.empty()) + separator = ", "; + } + + // First define overridden functions + bool overrides = _interface.bases_size() > 0 && !baseNames.str().empty(); + if (overrides) + funcs << mostDerivedInterfaceOverrides(_interface); + + unsigned index = 0; + // Define non-overridden functions + for (auto &f: _interface.funcdef()) + funcs << registerAndVisitFunction( + &_interface, + &f, + index++, + false, + false, + false + ); + + return make_tuple(bases.str(), baseNames.str(), funcs.str()); +} + +void ProtoConverter::registerFunctionName(CIL _program, CILFunc _function, unsigned _index) +{ + string pName = programName(_program); + string fName = createFunctionName(_program, _index); + solAssert(!m_functionNameMap.count(_function), "Sol proto fuzzer: Duplicate function registration"); + m_functionNameMap.insert(pair(_function, fName)); +} + +string ProtoConverter::registerAndVisitFunction( + CIL _program, + CILFunc _function, + unsigned _index, + bool _override, + bool _virtual, + bool _implement +) +{ + registerFunctionName(_program, _function, _index); + return visit(_function, _override, _virtual, _implement); +} + +tuple ProtoConverter::visitProgramHelper(CIL _program) +{ + ostringstream bases; + ostringstream funcs; + ostringstream baseNames; + + string pName = programName(_program); + + string separator{}; + if (holds_alternative(_program)) + { + Contract const* contract = get(_program); + + for (auto &base: contract->bases()) + { + string baseStr = visit(base); + if (baseStr.empty()) continue; - ancestors << ancestorStr; - ancestorNames << separator - << (ancestor.has_c() ? m_contractNameMap[&ancestor.c()] : m_interfaceNameMap[&ancestor.i()]); + bases << baseStr; + baseNames << separator + << (base.has_c() ? programName(&base.c()) : programName(&base.i())); if (separator.empty()) separator = ", "; } - unsigned wasNumContract = m_numContracts; - // First define overridden functions - bool overrides = contract->ancestors_size() > 0 && !ancestorNames.str().empty(); + bool overrides = contract->bases_size() > 0 && !baseNames.str().empty(); if (overrides) { - funcs << traverseOverrides(*contract, contract->abstract()); + funcs << traverseOverrides(*contract); } - m_numContracts = wasNumContract; - m_numStructs = 0; - - // Define non-overridden functions + // Declare/define non-overridden functions unsigned index = 0; for (auto &f: contract->funcdef()) - funcs << visit( - f, + { + auto [implement, virtualize] = contractFunctionParams(contract, &f); + + funcs << registerAndVisitFunction( + _program, + &f, index++, false, - contract->abstract(), - (f.virtualfunc() || (contract->abstract() && !f.implemented())), + virtualize, + implement + ); + + // Update contract function map + m_contractFunctionMap[contract].push_back(CITuple(&f, implement, virtualize)); + } + } + else if (holds_alternative(_program)) + { + // If we are here, it means most derived program is a contract. + + auto interface = get(_program); + for (auto &base: interface->bases()) + { + string baseStr = visit(base); + if (baseStr.empty()) + continue; + bases << baseStr; + baseNames << separator + << programName(&base); + if (separator.empty()) + separator = ", "; + } + + // First define overridden functions + bool overrides = interface->bases_size() > 0 && !baseNames.str().empty(); + if (overrides) + { + funcs << mostDerivedContractOverrides(*interface, false); + } + + unsigned index = 0; + // Declare non-overridden functions + for (auto &f: interface->funcdef()) + funcs << registerAndVisitFunction( + _program, + &f, + index++, false, - _programName + false, + false ); } - 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(); + else + { + auto library = get(_program); + unsigned index = 0; + for (auto &f: library->funcdef()) + funcs << registerAndVisitFunction( + _program, + &f, + index++, + false, + false, + false + ); + } + return make_tuple(bases.str(), baseNames.str(), funcs.str()); } 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); + openProgramScope(&_contract); + auto [bases, baseNames, funcs] = visitProgramHelper(&_contract); return Whiskers(R"( - -abstract is { + +abstract contract is { })") - ("ancestors", ancestors) + ("bases", bases) ("isAbstract", _contract.abstract()) - ("programType", "contract") - ("programName", programName) - ("inheritance", _contract.ancestors_size() > 0 && !ancestorNames.empty()) - ("ancestorNames", ancestorNames) + ("programName", programName(&_contract)) + ("inheritance", _contract.bases_size() > 0 && !baseNames.empty()) + ("baseNames", baseNames) ("functionDefs", funcs) .render(); } +#endif -string ProtoConverter::visit(Modifier const& _mod) +string ProtoConverter::visit(Interface const& _interface) { - return Whiskers(R"( - modifier m() { - - _; - })") - ("i", to_string(m_numMods++)) - ("isParams", _mod.params_size() > 0) - ("params", "") - ("body", "") - .render(); + if (_interface.funcdef_size() == 0 && _interface.bases_size() == 0) + return ""; + + openProgramScope(&_interface); + try { + auto interface = SolInterface(_interface, programName(&_interface), m_randomGen); + return interface.str(); + } + catch (langutil::FuzzerError const&) + { + // Return empty string if input specification is invalid. + return ""; + } } -tuple ProtoConverter::visit( - FunctionParamsAndReturns const& _pr, - bool _isExternal, - string _programName -) +string ProtoConverter::visit(Library const& _library) { - string paramsString; - string typeDefsParamsString; - unsigned structsAdded = 0; - unsigned index = 0; - string separator{}; - string structPrefix = _programName + "S"; + if (emptyLibrary(_library)) + return ""; - for (auto ¶m: _pr.params()) + openProgramScope(&_library); + auto lib = SolLibrary(_library, programName(&_library)); + if (lib.validTest()) { - 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 = ", "; - } + auto libTestPair = lib.pseudoRandomTest(_library.random()); + m_libraryTests.push_back({lib.name(), libTestPair.first, libTestPair.second}); } - - 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); + return lib.str(); } -bool ProtoConverter::disallowedContractFunction(ContractFunction const& _contractFunction, bool _isVirtual) +tuple ProtoConverter::pseudoRandomLibraryTest(unsigned _randomIdx) { + solAssert(m_libraryTests.size() > 0, "Sol proto fuzzer: No library tests found"); + unsigned index = _randomIdx % m_libraryTests.size(); + return m_libraryTests[index]; +} + +void ProtoConverter::openProgramScope(CIL _program) +{ + string programNamePrefix; + if (holds_alternative(_program)) + programNamePrefix = "C"; + else if (holds_alternative(_program)) + programNamePrefix = "I"; + else + programNamePrefix = "L"; + string programName = programNamePrefix + to_string(m_numPrograms++); + m_programNameMap.insert(pair(_program, programName)); + + if (holds_alternative(_program)) + m_contractFunctionMap.insert(pair(get(_program), vector{})); +} + +string ProtoConverter::programName(CIL _program) +{ + solAssert(m_programNameMap.count(_program), "Sol proto fuzzer: Unregistered program"); + return m_programNameMap[_program]; +} + +unsigned ProtoConverter::randomNumber() +{ + solAssert(m_randomGen, "Sol proto fuzzer: Uninitialized random number generator"); + return m_randomGen->operator()(); +} + +#if 0 +bool ProtoConverter::disallowedContractFunction(SolContractFunction const& _contractFunction, bool _isVirtual) +{ + string visibility = functionVisibility(_contractFunction.m_visibility); + string mutability = functionMutability(_contractFunction.m_mutability); + // Private virtual functions are disallowed - if (functionVisibility(_contractFunction.vis()) == "private" && _isVirtual) + if (visibility == "private" && _isVirtual) return true; // Private payable functions are disallowed - else if (functionVisibility(_contractFunction.vis()) == "private" && stateMutability(_contractFunction.mut()) == "payable") + else if (visibility == "private" && mutability == "payable") return true; // Internal payable functions are disallowed - else if (functionVisibility(_contractFunction.vis()) == "internal" && stateMutability(_contractFunction.mut()) == "payable") + else if (visibility == "internal" && mutability == "payable") return true; return false; } -string ProtoConverter::visit( - ContractFunction const& _contractFunction, - unsigned _index, - bool _isOverride, - bool _isAbstractContract, - bool _isVirtual, - bool _isImplemented, - string _programName -) +string ProtoConverter::functionName(CILFunc _function) { - 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(); + solAssert(m_functionNameMap.count(_function), "Sol proto fuzzer: Unregistered function"); + return m_functionNameMap[_function]; } - -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 +#endif \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToSol.h b/test/tools/ossfuzz/protoToSol.h index cc7112555..7081dc8ef 100644 --- a/test/tools/ossfuzz/protoToSol.h +++ b/test/tools/ossfuzz/protoToSol.h @@ -14,48 +14,29 @@ You should have received a copy of the GNU General Public License along with solidity. If not, see . */ +#pragma once -#include #include + +#include +#include +#include #include namespace solidity::test::solprotofuzzer { -struct ProgramInfo +struct SolRandomNumGenerator { - enum class ProgramType - { - LIBRARY, - CONTRACT, - INTERFACE - }; - std::string m_name; - std::string m_userDefinedTypes; - ProgramType m_programType; -}; + using RandomEngine = std::minstd_rand; -struct FunctionInfo -{ - enum class FunctionVisibility + explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {} + + unsigned operator()() { - 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; + return m_random(); + } + + RandomEngine m_random; }; class ProtoConverter @@ -65,82 +46,155 @@ public: ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; std::string protoToSolidity(Program const&); -private: - using CI = std::variant; + /// @returns true if test calls a library function, false + /// otherwise + bool libraryTest() const; + /// @returns name of the library under test + std::string libraryName() const; +private: + enum MostDerivedProgram { + CONTRACT, + INTERFACE, + LIBRARY + }; + + /// Variant type that points to one of contract, interface, library protobuf messages + using CIL = std::variant; + /// Variant type that points to one of contract, interface, library function protobuf + /// messages + using CILFunc = std::variant; + /// Variant type that points to one of contract, interface protobuf messages + using CI = std::variant; + /// Variant type that points to one of contract, interface function protobuf messages + using CIFunc = std::variant; + /// Tuple of contract or interface function variant and a boolean stating whether + /// the function is implemented (true) or not and a second boolean stating whether + /// the function is virtual (true) or not. + using CITuple = std::tuple; + /// Protobuf message visitors that accept a const reference to a protobuf message + /// type and return its solidity translation. std::string visit(Program const&); + std::string visit(TestContract const&); std::string visit(ContractType const&); std::string visit(ContractOrInterface const&); - std::string visit(Interface const&); + std::string visit(Interface const& _interface); + /// Visitor for most derived interface messages. + /// @param _interface is a const reference to interface protobuf message + /// @returns a 3-tuple containing Solidity translation of all base contracts + /// this interface derives from, names of all base contracts, and the Solidity + /// translation of this interface. + std::tuple + visitMostDerivedInterface(Interface const& _interface); std::string visit(Contract const&); - std::string traverseOverrides( - Interface const&, - bool _isOverride, - bool _implement, - bool _inheritedByContract, - bool _isVirtual + /// Define overrides for most derived interface. + std::string mostDerivedInterfaceOverrides(Interface const& _interface); + /// Define overrides for most derived contract. + std::string mostDerivedContractOverrides(Interface const& _interface); + std::string traverseOverrides(Contract const&); + std::string registerAndVisitFunction( + CIL _program, + CILFunc _func, + unsigned _index, + bool _override, + bool _virtual, + bool _implement ); - std::string traverseOverrides( - Contract const&, - bool _isAbstract + std::string overrideFunction(CILFunc _function, bool _virtual, bool _implement); + std::pair contractFunctionParams( + Contract const* _contract, + ContractFunction const* _function ); + std::string visit(CILFunc _function, bool _override, bool _virtual, bool _implement); 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); + std::string programName(CIL _program); + std::string createFunctionName(CIL _program, unsigned _index); + std::string functionName(CILFunc _function); + void registerFunctionName(CIL _program, CILFunc _function, unsigned _index); + std::tuple visitProgramHelper(CIL _program); + bool contractFunctionImplemented(Contract const* _contract, CIFunc _function); + bool contractFunctionVirtual(Contract const* _contract, CIFunc _function); + std::tuple mostDerivedContractOverrideParams(); + + std::pair contractFunctionOverrideParams( + Contract const* _base, + Contract const* _derived, + CIFunc _baseFunc + ); + std::tuple pseudoRandomLibraryTest(unsigned _randomIdx); + + bool emptyLibrary(Library const& _library) + { + return _library.funcdef_size() == 0; + } + bool emptyLibraryTests() + { + return m_libraryTests.size() == 0; + } + + void openProgramScope(CIL _program); + bool pseudoRandomCoinFlip() + { + return m_counter++ % 2 == 0; + } + bool mostDerivedProgramContract() + { + return m_mostDerivedProgram == MostDerivedProgram::CONTRACT; + } + bool mostDerivedProgramInterface() + { + return m_mostDerivedProgram == MostDerivedProgram::INTERFACE; + } + bool mostDerivedProgramAbstractContract() + { + return m_mostDerivedAbstractContract; + } + unsigned randomNumber(); + +#if 0 + static bool disallowedContractFunction(SolContractFunction const& _contractFunction, bool _isVirtual); +#endif - unsigned m_numVars = 0; - unsigned m_numStructs = 0; unsigned m_numMods = 0; - unsigned m_numContracts = 0; - bool m_isImplemented = false; + unsigned m_numPrograms = 0; + unsigned m_counter = 0; + bool m_mostDerivedAbstractContract = false; + bool m_libraryTest = false; + std::shared_ptr m_randomGen; + + MostDerivedProgram m_mostDerivedProgram = MostDerivedProgram::CONTRACT; + /// Map whose key is pointer to protobuf interface message + /// and whose value is its contract name std::map m_interfaceNameMap; + /// Map whose key is pointer to protobuf contract message + /// and whose value is its contract name std::map m_contractNameMap; + /// Map whose key is library name and whose value is the + /// number of implemented functions in it. + std::map m_libraryFuncMap; + /// Map whose key is a const pointer to protobuf contract + /// message and whose value is a list of 3-tuples that + /// store a const pointer to a protobuf interface or contract + /// function belonging to the keyed contract, a boolean flag that is + /// true when the function is implemented, false otherwise, and + /// a second boolean flag that is true when the function is virtualized + /// false otherwise. + std::map> m_contractFunctionMap; + /// Map whose key is a const pointer to protobuf contract, interface or + /// library function message type and whose value is the function name + /// assigned to it. + std::map m_functionNameMap; + std::map m_programNameMap; + /// Map whose key is a const pointer to protobuf contract or interface + /// function message type and whose value is a pair of const pointer to + /// protobuf contract or interface it belongs to and its declaration + /// position (which is an unsigned integer that starts from 0). + std::map> m_functionProgramMap; + + std::map>> m_programFunctionNameMap; + + std::vector> m_libraryTests; + std::string m_libraryName; static auto constexpr s_interfaceFunctionPrefix = "i"; static auto constexpr s_libraryFunctionPrefix = "l"; diff --git a/test/tools/ossfuzz/solProto.proto b/test/tools/ossfuzz/solProto.proto index 7a0b8c14d..c73f9b73d 100644 --- a/test/tools/ossfuzz/solProto.proto +++ b/test/tools/ossfuzz/solProto.proto @@ -17,22 +17,13 @@ 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; + required StateMutability mut = 1; } message LibraryFunction { @@ -47,11 +38,8 @@ message LibraryFunction { 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 Visibility vis = 1; + required StateMutability mut = 2; } message ContractFunction { @@ -66,144 +54,25 @@ message ContractFunction { 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; + required Visibility vis = 1; + required StateMutability mut = 2; + required bool virtualfunc = 3; } message Library { - repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; - repeated LibraryFunction funcdef = 2; + repeated LibraryFunction funcdef = 1; + required uint64 random = 2; } message Interface { - repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; - repeated InterfaceFunction funcdef = 2; - repeated Interface ancestors = 3; + repeated InterfaceFunction funcdef = 1; + repeated Interface bases = 2; } message Contract { - repeated solidity.test.abiv2fuzzer.VarDecl state_vars = 1; - repeated ContractFunction funcdef = 2; - required bool abstract = 3; - repeated ContractOrInterface ancestors = 4; + repeated ContractFunction funcdef = 1; + required bool abstract = 2; + repeated ContractOrInterface bases = 3; } message ContractOrInterface { @@ -221,8 +90,19 @@ message ContractType { } } -message Program { - repeated ContractType contracts = 1; +message TestContract { + enum Type { + LIBRARY = 0; + CONTRACT = 1; + } + required Type type = 1; + required uint64 programidx = 2; } -package solidity.test.solprotofuzzer; +message Program { + repeated ContractType contracts = 1; + required TestContract test = 2; + required uint64 seed = 3; +} + +package solidity.test.solprotofuzzer; \ No newline at end of file diff --git a/test/tools/ossfuzz/solProtoFuzzer.cpp b/test/tools/ossfuzz/solProtoFuzzer.cpp index 7c2914312..f8606f298 100644 --- a/test/tools/ossfuzz/solProtoFuzzer.cpp +++ b/test/tools/ossfuzz/solProtoFuzzer.cpp @@ -17,16 +17,178 @@ #include #include +#include +#include -#include - +#include #include #include +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; + +using namespace solidity; +using namespace solidity::test; using namespace solidity::test::solprotofuzzer; +using namespace solidity::util; +using namespace solidity::test::abiv2fuzzer; using namespace std; +namespace +{ +/// Compares two runs of EVMC returing true if they are +/// equal and false otherwise. +bool isOutputExpected(evmc::result const& _run1, evmc::result const& _run2) +{ + if (_run1.output_size != _run2.output_size) + return false; + + if (getenv("SOL_DEBUG_FILE") != nullptr) + { + unsigned r = _run1.output_data[31] | _run1.output_data[30] << 8 | + _run1.output_data[29] << 16 | _run1.output_data[28] << 24; + std::cout << r << std::endl; + } + + 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 (evmasm::StackTooDeepException const&) + { + return std::make_pair(bytes{}, Json::Value(0)); + } +} + +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 [minimalBytecode, ids] = compileContract( + _sourceCode, + _contractName, + _libraryAddressMap, + _optimization + ); + + return deployAndExecute( + hostContext, + minimalBytecode, + ids[_methodName].asString() + ); +} +} + DEFINE_PROTO_FUZZER(Program const& _input) { ProtoConverter converter; @@ -39,6 +201,47 @@ DEFINE_PROTO_FUZZER(Program const& _input) ofstream of(dump_path); of.write(sol_source.data(), sol_source.size()); } - FuzzerUtil::testCompiler(sol_source, /*optimize=*/false); + + 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; + } + + auto minimalResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::minimal(), + converter.libraryTest() ? converter.libraryName() : "" + ); + + auto optResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::standard(), + converter.libraryTest() ? converter.libraryName() : "" + ); + + // Both executions should either return success or identical evmone status code + bool successState = minimalResult.status_code == EVMC_SUCCESS && optResult.status_code == EVMC_SUCCESS; + bool identicalState = minimalResult.status_code == optResult.status_code; + bool executeState = successState || identicalState; + solAssert(executeState, "Proto solc fuzzer: Different execution status"); + + if (successState) + solAssert( + isOutputExpected(minimalResult, optResult), + "Proto solc fuzzer: Output mismatch" + ); + return; -} +} \ No newline at end of file