diff --git a/test/tools/ossfuzz/Generators.h b/test/tools/ossfuzz/Generators.h index 918faba6a..2741f5af2 100644 --- a/test/tools/ossfuzz/Generators.h +++ b/test/tools/ossfuzz/Generators.h @@ -44,6 +44,7 @@ MACRO(AssignmentStmtGenerator) SEP \ MACRO(BlockStmtGenerator) SEP \ MACRO(ContractGenerator) SEP \ + MACRO(FunctionCallGenerator) SEP \ MACRO(FunctionGenerator) SEP \ MACRO(ImportGenerator) SEP \ MACRO(PragmaGenerator) SEP \ diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index 264b2a1e7..e2f61c01c 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -165,6 +165,17 @@ string PragmaGenerator::visit() return boost::algorithm::join(pragmas, "\n") + "\n"; } +void SourceState::resolveImports(map _importedSymbols) +{ + for (auto const& item: _importedSymbols) + exports.emplace(item); +} + +void SourceState::mergeFunctionState(set> _importedFreeFunctions) +{ + freeFunctions += _importedFreeFunctions; +} + string ImportGenerator::visit() { /* @@ -189,6 +200,9 @@ string ImportGenerator::visit() state->sourceUnitState[state->currentPath()]->resolveImports( state->sourceUnitState[importPath]->exports ); + state->sourceUnitState[state->currentPath()]->mergeFunctionState( + state->sourceUnitState[importPath]->freeFunctions + ); } return os.str(); } @@ -205,10 +219,12 @@ string ContractGenerator::visit() ScopeGuard reset([&]() { mutator->generator()->scope(true); state->unindent(); + state->exitContract(); }); auto set = [&]() { state->indent(); mutator->generator()->scope(false); + state->enterContract(); }; ostringstream os; string inheritance; @@ -286,7 +302,8 @@ void StatementGenerator::setup() { addGenerators({ {mutator->generator(), 1}, - {mutator->generator(), 1} + {mutator->generator(), 1}, + {mutator->generator(), 1} }); } @@ -309,7 +326,7 @@ string StatementGenerator::visit() if (uRandDist->likely(child.second + 1)) { os << std::visit(GenericVisitor{ - [&](auto const& _item) { return _item->generate(); } + [](auto const& _item) { return _item->generate(); } }, child.first); if (holds_alternative>(child.first) && generateUncheckedBlock @@ -358,7 +375,7 @@ string FunctionGenerator::visit() { string visibility; string name = state->newFunction(); - state->updateFunction(name); + state->updateFunction(name, m_freeFunction); if (!m_freeFunction) visibility = "external"; @@ -385,6 +402,10 @@ string FunctionGenerator::visit() // Make sure block stmt generator does not output an unchecked block mutator->generator()->unchecked(false); block << visitChildren(); + if (m_freeFunction) + state->currentSourceState()->addFreeFunction(state->currentFunctionState()); + else + state->currentContractState()->addFunction(state->currentFunctionState()); // Since visitChildren() may not visit block stmt, we default to an empty // block. if (block.str().empty()) @@ -420,22 +441,9 @@ pair ExpressionGenerator::randomLValueExpression() } } -optional> ExpressionGenerator::expression() -{ - auto currentFunctionState = state->currentFunctionState(); - // TODO: Remove this barrier once we support more expression types. - if (currentFunctionState->inputs.empty() && currentFunctionState->outputs.empty()) - return nullopt; - return randomLValueExpression(); -} - -pair ExpressionGenerator::literal(SolidityTypePtr _type) -{ - string literalValue = visit(LiteralGenerator{state}, _type); - return pair(_type, literalValue); -} - -optional> ExpressionGenerator::expression(pair _typeName) +optional> ExpressionGenerator::lValueExpression( + pair _typeName +) { // Filter non-identical variables of the same type. auto liveTypedVariables = state->currentFunctionState()->inputs | @@ -453,6 +461,30 @@ optional> ExpressionGenerator::expression(pair>>(); if (liveTypedVariables.empty()) + return nullopt; + else + return liveTypedVariables[state->uRandDist->distributionOneToN(liveTypedVariables.size()) - 1]; +} + +optional> ExpressionGenerator::expression() +{ + auto currentFunctionState = state->currentFunctionState(); + // TODO: Remove this barrier once we support more expression types. + if (currentFunctionState->inputs.empty() && currentFunctionState->outputs.empty()) + return nullopt; + return randomLValueExpression(); +} + +pair ExpressionGenerator::literal(SolidityTypePtr _type) +{ + string literalValue = visit(LiteralGenerator{state}, _type); + return pair(_type, literalValue); +} + +optional> ExpressionGenerator::expression(pair _typeName) +{ + auto varRef = lValueExpression(_typeName); + if (!varRef.has_value()) { // TODO: Generate literals for contract and function types. if (!(holds_alternative>(_typeName.first) || holds_alternative>(_typeName.first))) @@ -460,7 +492,8 @@ optional> ExpressionGenerator::expression(pairuRandDist->distributionOneToN(liveTypedVariables.size()) - 1]; + else + return varRef.value(); } string LiteralGenerator::operator()(shared_ptr const&) @@ -574,7 +607,7 @@ SolidityTypePtr TypeProvider::type() case Type::ADDRESS: return make_shared(); case Type::FUNCTION: - return make_shared(); + return make_shared(false); case Type::CONTRACT: if (state->sourceUnitState[state->currentPath()]->contractType()) return state->sourceUnitState[state->currentPath()]->randomContractType(); @@ -584,6 +617,150 @@ SolidityTypePtr TypeProvider::type() } } +string FunctionCallGenerator::lhs(vector> _functionReturnTypeNames) +{ + ExpressionGenerator exprGen{state}; + ostringstream callStmtLhs; + + auto assignToVars = _functionReturnTypeNames | + ranges::views::transform([&exprGen](auto const& _item) -> pair>> { + auto e = exprGen.lValueExpression(_item); + if (e.has_value()) + return {true, e.value()}; + else + return {false, nullopt}; + }); + bool useExistingVars = ranges::all_of( + assignToVars, + [](auto const& _item) -> bool { return _item.first; } + ); + + if (useExistingVars) + { + auto vars = assignToVars | + ranges::views::transform([](auto const& _item) { return _item.second.value().second; }) | + ranges::to>(); + callStmtLhs << "(" + << boost::algorithm::join(vars, ",") + << ") = "; + } + else + { + auto newVars = _functionReturnTypeNames | + ranges::views::transform([&](auto const& _item) -> string { + state->currentFunctionState()->addLocal(_item.first); + string varName = state->currentFunctionState()->locals.back().second; + return std::visit( + GenericVisitor{[](auto const& _it) { return _it->toString(); }}, + _item.first + ) + + " " + + varName; + }) | + ranges::to>(); + callStmtLhs << "(" + << boost::algorithm::join(newVars, ", ") + << ") = "; + } + return callStmtLhs.str(); +} + +optional FunctionCallGenerator::rhs(vector> _functionInputTypeNames) +{ + ExpressionGenerator exprGen{state}; + ostringstream callStmtRhs; + + auto inputArguments = _functionInputTypeNames | + ranges::views::transform([&exprGen](auto const& _item) -> pair>> + { + auto e = exprGen.expression(_item); + if (e.has_value()) + return {true, e.value()}; + else + return {false, nullopt}; + }); + bool inputArgsValid = ranges::all_of( + inputArguments, + [](auto const& _item) -> bool { return _item.first; } + ); + + if (inputArgsValid) + { + auto vars = inputArguments | + ranges::views::transform([](auto const& _item) { return _item.second.value().second; }) | + ranges::to>(); + callStmtRhs << boost::algorithm::join(vars, ","); + return callStmtRhs.str(); + } + else + { + return nullopt; + } +} + +string FunctionCallGenerator::callStmt(shared_ptr _callee) +{ + ostringstream callStmtStream; + string lhsExpr; + string rhsExpr; + bool callValid = true; + + // Create lhs expression only if function outputs non-zero return values. + if (!_callee->outputs.empty()) + lhsExpr = lhs(_callee->outputs); + + // Create arguments only if function contains non-zero input parameters. + if (!_callee->inputs.empty()) + { + auto callRhs = rhs(_callee->inputs); + // Arguments may not be found for function and contract types. In this + // case, do not make the call. + if (callRhs.has_value()) + rhsExpr = (_callee->type->functionScope() ? "" : "this.") + _callee->name + "(" + callRhs.value() + ");"; + else + callValid = false; + } + else + rhsExpr = (_callee->type->functionScope() ? "" : "this.") + _callee->name + "();"; + + if (callValid) + callStmtStream << indentation() + << lhsExpr + << rhsExpr; + callStmtStream << "\n"; + return callStmtStream.str(); +} + +string FunctionCallGenerator::visit() +{ +// // TODO: Generalise call to varargs function +// for (auto const& f: state->currentFunctionState()->inputs) +// if (holds_alternative>(f.first)) +// return indentation() + f.second + "();\n"; + + // Consolidate available functions + auto availableFunctions = state->currentSourceState()->freeFunctions; + if (state->insideContract) + availableFunctions += state->currentContractState()->functions; + if (availableFunctions.empty()) + return "\n"; + + shared_ptr callee; + if (availableFunctions.size() > 1) + { + for (auto const& i: availableFunctions) + if (uRandDist->probable(availableFunctions.size())) + callee = i; + } + else + callee = *availableFunctions.begin(); + + if (callee) + return callStmt(callee); + else + return "\n"; +} + template shared_ptr SolidityGenerator::generator() { diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index d02e0f712..1c0a2a6f7 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -112,15 +112,6 @@ struct UniformRandomDistribution std::unique_ptr randomEngine; }; -struct ContractState -{ - explicit ContractState(std::shared_ptr _urd): - uRandDist(std::move(_urd)) - {} - - std::shared_ptr uRandDist; -}; - class SolType { public: @@ -302,7 +293,10 @@ public: class FunctionType: public SolType { public: - FunctionType() = default; + FunctionType(bool _freeFunction) + { + freeFunction = _freeFunction; + } ~FunctionType() override { inputs.clear(); @@ -319,6 +313,11 @@ public: outputs.emplace_back(_output); } + bool functionScope() + { + return freeFunction; + } + std::string toString() override; bool operator==(FunctionType const& _rhs) { @@ -333,20 +332,30 @@ public: std::vector inputs; std::vector outputs; + bool freeFunction; }; /// Forward declaration struct TestState; +struct FunctionState; struct SourceState { - explicit SourceState(std::shared_ptr _urd): + explicit SourceState( + std::shared_ptr _urd, + std::string _sourceName + ): uRandDist(std::move(_urd)), - importedSources({}) + importedSources({}), + sourceName(_sourceName) {} void addFreeFunction(std::string& _functionName) { - exports[std::make_shared()] = _functionName; + exports[std::make_shared(true)] = _functionName; + } + void addFreeFunction(std::shared_ptr _state) + { + freeFunctions.emplace(_state); } bool freeFunction(std::string const& _functionName) { @@ -387,11 +396,9 @@ struct SourceState { importedSources.emplace(_sourcePath); } - void resolveImports(std::map _imports) - { - for (auto const& item: _imports) - exports.emplace(item); - } + void resolveImports(std::map _importedSymbols); + void mergeFunctionState(std::set> _importedFreeFunctions); + [[nodiscard]] bool sourcePathImported(std::string const& _sourcePath) const { return importedSources.count(_sourcePath); @@ -399,12 +406,16 @@ struct SourceState ~SourceState() { importedSources.clear(); + freeFunctions.clear(); + exports.clear(); } /// Prints source state to @param _os. void print(std::ostream& _os) const; std::shared_ptr uRandDist; std::set importedSources; std::map exports; + std::set> freeFunctions; + std::string sourceName; }; struct FunctionState @@ -414,27 +425,63 @@ struct FunctionState INPUT, OUTPUT }; - FunctionState() = default; + FunctionState(std::string _functionName, bool _freeFunction): + numInputs(0), + numOutpus(0), + numLocals(0), + name(_functionName) + { + type = std::make_shared(_freeFunction); + } ~FunctionState() { inputs.clear(); outputs.clear(); + locals.clear(); } - using TypeId = std::pair; void addInput(SolidityTypePtr _input) { - inputs.emplace(_input, "i" + std::to_string(numInputs++)); + inputs.emplace_back(_input, "i" + std::to_string(numInputs++)); + type->addInput(_input); } void addOutput(SolidityTypePtr _output) { - outputs.emplace(_output, "o" + std::to_string(numOutpus++)); + outputs.emplace_back(_output, "o" + std::to_string(numOutpus++)); + type->addOutput(_output); + } + void addLocal(SolidityTypePtr _local) + { + locals.emplace_back(_local, "l" + std::to_string(numLocals++)); } std::string params(Params _p); - std::map inputs; - std::map outputs; - unsigned numInputs = 0; - unsigned numOutpus = 0; + std::vector> inputs; + std::vector> outputs; + std::vector> locals; + std::shared_ptr type; + unsigned numInputs; + unsigned numOutpus; + unsigned numLocals; + std::string name; +}; + +struct ContractState +{ + explicit ContractState( + std::shared_ptr _urd, + std::string _contractName + ): + uRandDist(std::move(_urd)), + name(_contractName) + {} + void addFunction(std::shared_ptr _function) + { + functions.emplace(_function); + } + + std::set> functions; + std::shared_ptr uRandDist; + std::string name; }; struct TestState @@ -449,28 +496,29 @@ struct TestState numSourceUnits(0), numContracts(0), numFunctions(0), - indentationLevel(0) + indentationLevel(0), + insideContract(false) {} /// Adds @param _path to @name sourceUnitPaths updates /// @name currentSourceUnitPath. void addSourceUnit(std::string const& _path) { - sourceUnitState.emplace(_path, std::make_shared(uRandDist)); + sourceUnitState.emplace(_path, std::make_shared(uRandDist, _path)); currentSourceUnitPath = _path; } /// Adds @param _name to @name contractState updates /// @name currentContract. void addContract(std::string const& _name) { - contractState.emplace(_name, std::make_shared(uRandDist)); + contractState.emplace(_name, std::make_shared(uRandDist, _name)); sourceUnitState[currentSourceUnitPath]->exports[ std::make_shared(_name) ] = _name; currentContract = _name; } - void addFunction(std::string const& _name) + void addFunction(std::string const& _name, bool _freeFunction) { - functionState.emplace(_name, std::make_shared()); + functionState.emplace(_name, std::make_shared(_name, _freeFunction)); currentFunction = _name; } std::shared_ptr currentFunctionState() @@ -483,6 +531,11 @@ struct TestState std::string currentSource = currentPath(); return sourceUnitState[currentSource]; } + std::shared_ptr currentContractState() + { + std::string contract = currentContractName(); + return contractState[contract]; + } /// Returns true if @name sourceUnitPaths is empty, /// false otherwise. [[nodiscard]] bool empty() const @@ -520,6 +573,11 @@ struct TestState solAssert(numFunctions > 0, ""); return currentFunction; } + std::string currentContractName() const + { + solAssert(numContracts > 0, ""); + return currentContract; + } /// Adds @param _path to list of source paths in global test /// state and increments @name m_numSourceUnits. void updateSourcePath(std::string const& _path) @@ -534,9 +592,9 @@ struct TestState addContract(_name); numContracts++; } - void updateFunction(std::string const& _name) + void updateFunction(std::string const& _name, bool _freeFunction) { - addFunction(_name); + addFunction(_name, _freeFunction); numFunctions++; } void addSource() @@ -554,6 +612,14 @@ struct TestState { --indentationLevel; } + void enterContract() + { + insideContract = true; + } + void exitContract() + { + insideContract = false; + } ~TestState() { sourceUnitState.clear(); @@ -590,6 +656,8 @@ struct TestState size_t numFunctions; /// Indentation level unsigned indentationLevel; + /// Contract scope + bool insideContract; /// Source name prefix std::string const sourceUnitNamePrefix = "su"; /// Contract name prefix @@ -668,10 +736,15 @@ struct ExpressionGenerator TYPEMAX }; - std::optional> expression(std::pair _typeName); + std::optional> expression( + std::pair _typeName + ); std::pair literal(SolidityTypePtr _type); std::optional> expression(); std::pair randomLValueExpression(); + std::optional> lValueExpression( + std::pair _typeName + ); std::shared_ptr state; }; @@ -924,6 +997,23 @@ private: static constexpr size_t s_uncheckedInvProb = 13; }; +class FunctionCallGenerator: public GeneratorBase +{ +public: + FunctionCallGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + std::string visit() override; + std::string name() override + { + return "Function call generator"; + } +private: + std::string lhs(std::vector> _functionReturnTypeNames); + std::optional rhs(std::vector> _functionInputTypeNames); + std::string callStmt(std::shared_ptr _callee); +}; + class SolidityGenerator: public std::enable_shared_from_this { public: