From cbbefce4438f02328438560fd013982f7aec365e Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 27 Aug 2020 19:07:11 +0200 Subject: [PATCH] Add Solidity generators --- libsolidity/interface/CompilerStack.cpp | 9 + test/tools/fuzzer_common.cpp | 21 +- test/tools/fuzzer_common.h | 8 +- test/tools/ossfuzz/Generators.h | 16 +- test/tools/ossfuzz/SolidityGenerator.cpp | 1135 ++++++++++++++++++- test/tools/ossfuzz/SolidityGenerator.h | 1211 ++++++++++++++++++++- test/tools/ossfuzz/Types.h | 56 + test/tools/ossfuzz/solc_noopt_ossfuzz.cpp | 10 +- 8 files changed, 2443 insertions(+), 23 deletions(-) create mode 100644 test/tools/ossfuzz/Types.h diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4a0fbbd09..302afaf12 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -297,6 +297,10 @@ bool CompilerStack::parse() storeContractDefinitions(); + if (!m_hasError) + std::cout << "Fuzzer: Parsing successful" << std::endl; + else + std::cout << "Fuzzer: Parsing failure" << std::endl; return !m_hasError; } @@ -478,6 +482,11 @@ bool CompilerStack::analyze() if (!noErrors) m_hasError = true; + if (!m_hasError) + std::cout << "Fuzzer: Analysis successful" << std::endl; + else + std::cout << "Fuzzer: Analysis failure" << std::endl; + return !m_hasError; } diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index 9b0909bb3..968e13606 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -28,6 +28,7 @@ #include #include +#include #include @@ -81,7 +82,7 @@ void FuzzerUtil::forceSMT(StringMap& _input) sourceUnit.second += smtPragma; } -void FuzzerUtil::testCompiler(StringMap& _input, bool _optimize, unsigned _rand, bool _forceSMT) +FuzzerUtil::Error FuzzerUtil::testCompiler(StringMap& _input, bool _optimize, unsigned _rand, bool _forceSMT) { frontend::CompilerStack compiler; EVMVersion evmVersion = s_evmVersions[_rand % s_evmVersions.size()]; @@ -100,19 +101,35 @@ void FuzzerUtil::testCompiler(StringMap& _input, bool _optimize, unsigned _rand, compiler.setOptimiserSettings(optimiserSettings); try { - compiler.compile(); + if (!compiler.compile()) + { + langutil::SourceReferenceFormatter formatter(std::cerr, false, false); + + for (auto const& error: compiler.errors()) + formatter.printExceptionInformation( + *error, + formatter.formatErrorInformation(*error) + ); + return Error::FAILURE; + } + else + return Error::SUCCESS; } catch (Error const&) { + return Error::EXCEPTION; } catch (FatalError const&) { + return Error::EXCEPTION; } catch (UnimplementedFeatureError const&) { + return Error::EXCEPTION; } catch (StackTooDeepError const&) { + return Error::EXCEPTION; } } diff --git a/test/tools/fuzzer_common.h b/test/tools/fuzzer_common.h index de8d52f8f..13960466a 100644 --- a/test/tools/fuzzer_common.h +++ b/test/tools/fuzzer_common.h @@ -27,6 +27,12 @@ */ struct FuzzerUtil { + enum class Error + { + SUCCESS, + FAILURE, + EXCEPTION + }; static void runCompiler(std::string const& _input, bool _quiet); static void testCompilerJsonInterface(std::string const& _input, bool _optimize, bool _quiet); static void testConstantOptimizer(std::string const& _input, bool _quiet); @@ -37,7 +43,7 @@ struct FuzzerUtil /// version to be compiled for, and bool @param _forceSMT that, if true, /// adds the experimental SMTChecker pragma to each source file in the /// source map. - static void testCompiler( + static Error testCompiler( solidity::StringMap& _input, bool _optimize, unsigned _rand, diff --git a/test/tools/ossfuzz/Generators.h b/test/tools/ossfuzz/Generators.h index 675984857..e28cd8f03 100644 --- a/test/tools/ossfuzz/Generators.h +++ b/test/tools/ossfuzz/Generators.h @@ -41,6 +41,20 @@ * */ #define GENERATORLIST(MACRO, SEP, ENDSEP) \ + MACRO(ConstantVariableDeclaration) SEP \ + MACRO(ContractDefinitionGenerator) SEP \ + MACRO(EnumDeclaration) SEP \ + MACRO(ExpressionGenerator) SEP \ + MACRO(FunctionDefinitionGenerator) SEP \ + MACRO(ImportGenerator) SEP \ + MACRO(LocationGenerator) SEP \ + MACRO(NatSpecGenerator) SEP \ + MACRO(ParameterListGenerator) SEP \ MACRO(PragmaGenerator) SEP \ + MACRO(SimpleVarDeclGenerator) SEP \ MACRO(SourceUnitGenerator) SEP \ - MACRO(TestCaseGenerator) ENDSEP + MACRO(StatementGenerator) SEP \ + MACRO(StateVariableDeclarationGenerator) SEP \ + MACRO(TestCaseGenerator) SEP \ + MACRO(VariableDeclarationGenerator) ENDSEP + diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index ac903226e..ff8125f46 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -20,6 +20,11 @@ #include +#include +#include + +#include + using namespace solidity::test::fuzzer; using namespace solidity::util; using namespace std; @@ -29,6 +34,7 @@ GeneratorBase::GeneratorBase(std::shared_ptr _mutator) { mutator = std::move(_mutator); rand = mutator->randomEngine(); + state = mutator->testState(); } string GeneratorBase::visitChildren() @@ -39,8 +45,13 @@ string GeneratorBase::visitChildren() for (auto child: generators) randomisedChildren.push_back(child); shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand); + std::cout << "Visiting children" << std::endl; for (auto child: randomisedChildren) + { + std::cout << "Visiting " << std::visit(NameVisitor{}, child) << std::endl; os << std::visit(GeneratorVisitor{}, child); + } + return os.str(); } @@ -62,6 +73,7 @@ string TestCaseGenerator::visit() << sourcePath << " ====" << "\n"; + addSourceUnit(sourcePath); m_numSourceUnits++; os << visitChildren(); } @@ -70,9 +82,17 @@ string TestCaseGenerator::visit() void SourceUnitGenerator::setup() { - addGenerators({ - mutator->generator(), - }); + addGenerators( + { + mutator->generator(), + mutator->generator(), + mutator->generator(), + mutator->generator(), + mutator->generator(), + mutator->generator() + } + ); + mutator->generator()->freeFunctionMode(); } string SourceUnitGenerator::visit() @@ -106,6 +126,7 @@ SolidityGenerator::SolidityGenerator(unsigned _seed) { m_rand = make_shared(_seed); m_generators = {}; + m_state = make_shared(m_rand); } template @@ -118,12 +139,1116 @@ void SolidityGenerator::createGenerators() } } +using MP = solidity::test::fuzzer::GenerationProbability; + +const std::vector FunctionDefinitionGenerator::s_visibility = { + "public", + "private", + "external", + "internal" +}; + +const vector FunctionDefinitionGenerator::s_mutability = { + "payable", + "view", + "pure", + "" // non payable +}; + +map> NatSpecGenerator::s_tagLookup = { + { + NatSpecGenerator::TagCategory::CONTRACT, + { + NatSpecGenerator::Tag::TITLE, + NatSpecGenerator::Tag::AUTHOR, + NatSpecGenerator::Tag::NOTICE, + NatSpecGenerator::Tag::DEV, + } + }, + { + NatSpecGenerator::TagCategory::FUNCTION, + { + NatSpecGenerator::Tag::NOTICE, + NatSpecGenerator::Tag::DEV, + NatSpecGenerator::Tag::PARAM, + NatSpecGenerator::Tag::RETURN, + NatSpecGenerator::Tag::INHERITDOC + } + }, + { + NatSpecGenerator::TagCategory::PUBLICSTATEVAR, + { + NatSpecGenerator::Tag::NOTICE, + NatSpecGenerator::Tag::DEV, + NatSpecGenerator::Tag::RETURN, + NatSpecGenerator::Tag::INHERITDOC + } + }, + { + NatSpecGenerator::TagCategory::EVENT, + { + NatSpecGenerator::Tag::NOTICE, + NatSpecGenerator::Tag::DEV, + NatSpecGenerator::Tag::PARAM + } + } +}; + +const vector FunctionDefinitionGenerator::s_freeFunctionMutability = { + "view", + "pure", + "" // non payable +}; + +string GenerationProbability::generateRandomAsciiString(size_t _length, std::shared_ptr _rand) +{ + vector s{}; + for (size_t i = 0; i < _length * 2; i++) + s.push_back( + static_cast(Distribution(0x21, 0x7e)(*_rand)) + ); + return string(s.begin(), s.end()); +} + +string GenerationProbability::generateRandomHexString(size_t _length, std::shared_ptr _rand) +{ + static char const* hexDigit = "0123456789abcdefABCDEF"; + vector s{}; + for (size_t i = 0; i < _length * 2; i++) + s.push_back(hexDigit[distributionOneToN(22, _rand) - 1]); + return string(s.begin(), s.end()); +} + +pair GenerationProbability::generateRandomNumberLiteral( + size_t _length, + std::shared_ptr _rand +) +{ +// static char const* hexDigit = "0123456789abcdefABCDEF"; + static char const* decimalDigit = "0123456789"; + vector s{}; + for (size_t i = 0; i < _length; i++) + s.push_back(decimalDigit[distributionOneToN(10, _rand) - 1]); + if (s[0] == '0') + s[0] = decimalDigit[distributionOneToN(9, _rand)]; + return pair(NumberLiteral::DECIMAL, string(s.begin(), s.end())); +} + +string ExportedSymbols::randomSymbol(shared_ptr _rand) +{ + auto it = symbols.begin(); + auto idx = (*_rand)() % symbols.size(); + for (size_t i = 0; i < idx; i++) + it++; + return *it; +} + +string ExportedSymbols::randomUserDefinedType(shared_ptr _rand) +{ + auto it = types.begin(); + auto idx = (*_rand)() % types.size(); + for (size_t i = 0; i < idx; i++) + it++; + return *it; +} + +bool FunctionState::operator==(const FunctionState& _other) +{ + if (_other.inputParameters.size() != inputParameters.size()) + return false; + else + { + unsigned index = 0; + for (auto const& type: _other.inputParameters) + if (type.first->type != inputParameters[index++].first->type) + return false; + return name == _other.name; + } +} + +string TestState::randomPath() +{ + solAssert(!empty(), "Solc custom mutator: Null test state"); + for (auto iter = sourceUnitStates.begin(); iter != sourceUnitStates.end(); ) + { + // Choose this element equally at random + if (MP{}.chooseOneOfN(sourceUnitStates.size(), rand)) + return iter->first; + // If not chosen, increment iterator checking if this + // is the last element. If it is not the last element + // continue, otherwise choose it. + else if (++iter == sourceUnitStates.end()) + return (--iter)->first; + } + solAssert(false, "Solc custom mutator: No source path chosen"); +} + +GeneratorPtr GeneratorBase::randomGenerator() +{ + solAssert(generators.size() > 0, "Invalid hierarchy"); + + auto it = generators.begin(); + auto idx = (*rand)() % generators.size(); + for (size_t i = 0; i < idx; i++) + it++; + return *it; +} + +string TestCaseGenerator::randomPath() +{ + solAssert(!empty(), "Solc custom mutator: Invalid source unit"); + return path(MP{}.distributionOneToN(m_numSourceUnits, rand) - 1); +} + +string ExpressionGenerator::doubleQuotedStringLiteral() +{ + string s = MP{}.generateRandomAsciiString( + MP{}.distributionOneToN(s_maxStringLength, rand), + rand + ); + return s; +} + +string ExpressionGenerator::hexLiteral() +{ + size_t lengthInBytes = std::visit(SolidityType::TypeIndexVisitor{}, m_type.type.first) + 1; + string s = MP{}.generateRandomHexString( + MP{}.distributionOneToN(lengthInBytes, rand), + rand + ); + return "hex\"" + s + "\""; +} + +string ExpressionGenerator::numberLiteral() +{ + size_t lengthInBytes = std::visit(SolidityType::TypeIndexVisitor{}, m_type.type.first) + 1; + if (lengthInBytes > 32) + lengthInBytes -= 32; + auto [n, s] = MP{}.generateRandomNumberLiteral( + MP{}.distributionOneToN(lengthInBytes, rand), + rand + ); + if (n == MP::NumberLiteral::HEX) + return "hex\"" + s + "\""; + else + return s; +} + +string ExpressionGenerator::addressLiteral() +{ + string addr = "0x" + MP{}.generateRandomHexString(20, rand); + return getChecksummedAddress(addr); +} + +string ExpressionGenerator::literal() +{ + string lit; + switch (m_type.typeCategory) + { + case SolidityType::TypeCategory::ADDRESS: + lit = addressLiteral(); + break; + case SolidityType::TypeCategory::BOOL: + lit = boolLiteral(); + break; + case SolidityType::TypeCategory::BYTES: + lit = hexLiteral(); + break; + case SolidityType::TypeCategory::INTEGER: + lit = numberLiteral(); + break; + case SolidityType::TypeCategory::TYPEMAX: + solAssert(false, ""); + } + return typeString() + "(" + lit + ")"; +} + +string ExpressionGenerator::identifier() +{ + vector ids; + // TODO: Implement typed identifiers +// if (state->currentSourceState().symbols()) +// for (auto &item: state->currentSourceState().exportedSymbols.symbols) +// ids.push_back(item); + if (auto f = state->currentSourceState().currentFunction(); f && f->identifiers()) + { + for (auto& item: f->inputParameters) + if (item.first->typeCategory == m_type.typeCategory) + ids.push_back(item.second); + for (auto& item: f->returnParameters) + if (item.first->typeCategory == m_type.typeCategory) + ids.push_back(item.second); + for (auto& item: f->locals) + if (item.first->typeCategory == m_type.typeCategory) + ids.push_back(item.second); + } + if (ids.size() > 0) + return ids[MP{}.distributionOneToN(ids.size(), rand) - 1]; + else + return ""; +} + +string ExpressionGenerator::expression() +{ + if (nestingDepthTooHigh()) + return literal(); + + incrementNestingDepth(); + + string expr; + switch (MP{}.distributionOneToN(Type::TYPEMAX, rand) - 1) + { + case Type::INDEXACCESS: + // TODO: Implement index access + expr = literal(); +// expr = Whiskers(R"([])") +// ("baseExpr", expression()) +// ("indexExpr", expression()) +// .render(); + break; + case Type::INDEXRANGEACCESS: + // TODO: Implement index range access + expr = literal(); +// expr = Whiskers(R"([:])") +// ("baseExpr", expression()) +// ("startExpr", expression()) +// ("endExpr", expression()) +// .render(); + break; + case Type::METATYPE: + // TODO: Implement metatype + expr = literal(); +// expr = Whiskers(R"(type())") +// ("typeName", randomTypeString()) +// .render(); + break; + case Type::BITANDOP: + if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER) + expr = expression() + " & " + expression(); + else + expr = literal(); + break; + case Type::BITOROP: + if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER) + expr = expression() + " | " + expression(); + else + expr = literal(); + break; + case Type::BITXOROP: + if (m_type.typeCategory == SolidityType::TypeCategory::INTEGER) + expr = expression() + " ^ " + expression(); + else + expr = literal(); + break; + case Type::ANDOP: + if (m_type.typeCategory == SolidityType::TypeCategory::BOOL) + expr = expression() + " && " + expression(); + else + expr = literal(); + break; + case Type::OROP: + if (m_type.typeCategory == SolidityType::TypeCategory::BOOL) + expr = expression() + " || " + expression(); + else + expr = literal(); + break; + case Type::NEWEXPRESSION: + // TODO: Implement new expression + expr = literal(); +// expr = Whiskers(R"(new )") +// ("typeName", randomTypeString()) +// .render(); + break; + case Type::CONDITIONAL: + { + SolidityType oldType = m_type; + setType(SolidityType(SolidityType::TypeCategory::BOOL, rand)); + string condition = expression(); + setType(oldType); + expr = condition + " ? " + expression() + " : " + expression(); + break; + } + case Type::ASSIGNMENT: + { + // TODO: Implement lvalue expressions + auto id = identifier(); + if (!id.empty()) + expr = id + " = " + expression(); + else + expr = literal(); + break; + } + case Type::INLINEARRAY: + { + // TODO: Implement inline array expressions + expr = literal(); +// vector exprs{}; +// size_t numElementsInTuple = MP{}.distributionOneToN(s_maxElementsInlineArray, rand); +// for (size_t i = 0; i < numElementsInTuple; i++) +// exprs.push_back(expression()); +// expr = Whiskers(R"([])") +// ("inlineArrayExpression", boost::algorithm::join(exprs, ", ")) +// .render(); + break; + } + case Type::IDENTIFIER: + { + auto id = identifier(); + if (id.empty()) + expr = literal(); + else + expr = id; + break; + } + case Type::LITERAL: + expr = literal(); + break; + case Type::TUPLE: + { + // TODO: Implement tuple + expr = literal(); +// vector exprs{}; +// size_t numElementsInTuple = MP{}.distributionOneToN(s_maxElementsInTuple, rand); +// for (size_t i = 0; i < numElementsInTuple; i++) +// exprs.push_back(expression()); +// expr = Whiskers(R"(())") +// ("tupleExpression", boost::algorithm::join(exprs, ", ")) +// .render(); + break; + } + default: + expr = literal(); + } + // Typed expression + return typeString() + "(" + expr + ")"; +} + +string ExpressionGenerator::visit() +{ + string expr{}; + if (m_compileTimeConstantExpressionsOnly) + // TODO: Reference identifiers that point to + // compile time constant expressions. + return literal(); + else + return expression(); +} + +string StateVariableDeclarationGenerator::visibility() +{ + switch (MP{}.distributionOneToN(Visibility::VISIBILITYMAX, rand) - 1) + { + case Visibility::INTERNAL: + return "internal"; + case Visibility::PRIVATE: + return "private"; + case Visibility::PUBLIC: + return "public"; + default: + solAssert(false, ""); + } +} + +void StateVariableDeclarationGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator() + } + ); +} + +string StateVariableDeclarationGenerator::visit() +{ + string id = identifier(); + string vis = visibility(); + bool immutable = false; + bool constant = MP{}.chooseOneOfN(2, rand); + if (!constant) + immutable = MP{}.chooseOneOfN(2, rand); + solAssert(!(constant && immutable), "State variable cannot be both constant and immutable"); + // Immutables cannot have a non-value type + if (immutable) + generator()->setValueType(); + string expr = generator()->visit(); + string type = generator()->typeString(); + // TODO: Actually restrict this setting to public state variables only + generator()->tagCategory(NatSpecGenerator::TagCategory::PUBLICSTATEVAR); + string natSpecString = generator()->visit(); + return Whiskers(m_declarationTemplate) + ("natSpecString", natSpecString) + ("type", type) + ("vis", vis) + ("constant", constant) + ("immutable", immutable) + ("id", id) + ("value", expr) + .render(); +} + +//void UserDefinedTypeGenerator::setup() +//{ +// addGenerators({mutator->generator()}); +//} +// +//string UserDefinedTypeGenerator::visit() +//{ +// switch (MP{}.distributionOneToN(2, rand)) +// { +// case 1: +// if (state->currentSourceState().userDefinedTypes()) +// return state->currentSourceState().exportedSymbols.randomUserDefinedType(rand); +// else +// return "uint"; +// case 2: +// return generator()->visit(); +// } +// solAssert(false, ""); +//} + +string Location::visit() +{ + switch (loc) + { + case Location::Loc::CALLDATA: + return "calldata"; + case Location::Loc::MEMORY: + return "memory"; + case Location::Loc::STORAGE: + return "storage"; + case Location::Loc::STACK: + return ""; + } +} + +string LocationGenerator::visit() +{ + return ""; + // TODO: Implement locations + switch (MP{}.distributionOneToN(4, rand)) + { + case 1: + return Location(Location::Loc::MEMORY).visit(); + case 2: + return Location(Location::Loc::STORAGE).visit(); + case 3: + return Location(Location::Loc::CALLDATA).visit(); + case 4: + return Location(Location::Loc::STACK).visit(); + } + solAssert(false, ""); +} + +void SimpleVarDeclGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator() + } + ); +} + +string SimpleVarDeclGenerator::visit() +{ + return Whiskers(simpleVarDeclTemplate) + ("type", generator()->typeString()) + ("location", generator()->visit()) + ("name", "v") + ("assign", true) + ("expression", generator()->visit()) + .render(); + +} + +string ExpressionStatement::visit() +{ + // TODO: Implement expression generation + return Whiskers(exprStmtTemplate)("expression", "1").render(); +} + +void StatementGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator(), + } + ); +} + +string StatementGenerator::simpleStatement() +{ + bool variableDecl = MP{}.chooseOneOfN(2, rand); + string stmt; + if (variableDecl) + { + if (auto f = state->currentSourceState().currentFunction(); f) + { + if (f->numReturns > 0) + { + auto t = f->returnParameters[ + MP{}.distributionOneToN(f->returnParameters.size(), rand) - 1]; + generator()->setType(t.first); + stmt = t.second + " = " + generator()->visit() + ";\n"; + } + else + { + auto t = generator()->randomType(); + f->addVariable(t); + generator()->setType(*t); + stmt = t->type.second + + " " + + generator()->visit() + + " " + + "v" + to_string(f->numLocals - 1) + + " = " + + generator()->visit() + + ";" + + "\n"; + } + } + else + stmt = generator()->visit() + ";\n"; + } + else + stmt = generator()->visit() + ";" + "\n"; + return stmt; +} + +string StatementGenerator::blockStatement() +{ + static size_t constexpr maxBlockStmts = 3; + string stmt = "{"; + if (auto f = state->currentSourceState().currentFunction(); f && f->returnParameters.size() > 0) + // Always assign value to return parameters + for (auto t: f->returnParameters) + { + generator()->setType(t.first); + stmt = t.second + " = " + generator()->visit() + ";\n"; + } + // Add pseudo randomly generated statements + for (size_t i = 0; i < MP{}.distributionOneToN(maxBlockStmts, rand); i++) + stmt += statement(); + stmt += "}"; + return stmt; +} + +string StatementGenerator::statement() +{ + if (nestingDepthTooHigh()) + return simpleStatement(); + + incrementNestingDepth(); + + string stmt; + switch (randomType((*rand)())) + { + case Type::BLOCK: + { + stmt = blockStatement(); + break; + } + case Type::SIMPLE: + stmt = simpleStatement(); + break; + case Type::IF: + generator()->setType( + {SolidityType::TypeCategory::BOOL, rand} + ); + stmt = "if(" + generator()->visit() + ")"; + stmt += blockStatement(); + break; + case Type::FOR: + { + bool loop = m_loop; + m_loop = true; + stmt = "for(" + simpleStatement(); + std::cout << stmt << std::endl; + generator()->setType( + {SolidityType::TypeCategory::BOOL, rand} + ); + stmt += generator()->visit() + + "; " + + generator()->visit() + + ")"; + std::cout << stmt << std::endl; + stmt += blockStatement(); + m_loop = loop; + break; + } + case Type::WHILE: + { + bool loop = m_loop; + m_loop = true; + generator()->setType( + {SolidityType::TypeCategory::BOOL, rand} + ); + stmt = "while(" + generator()->visit() + ")"; + stmt += blockStatement(); + m_loop = loop; + break; + } + case Type::DOWHILE: + { + bool loop = m_loop; + m_loop = true; + stmt += "do" + blockStatement(); + generator()->setType( + {SolidityType::TypeCategory::BOOL, rand} + ); + stmt += "while(" + generator()->visit() + ");"; + m_loop = loop; + break; + } + case Type::CONTINUE: + if (m_loop) + stmt = "continue;"; + break; + case Type::BREAK: + if (m_loop) + stmt = "break;"; + break; + case Type::TRY: + // TODO: Implement try + break; + case Type::RETURN: + { + if (state->currentSourceState().currentFunction()) + { + size_t numReturns = state->currentSourceState().currentFunction()->numReturns; + if (numReturns > 0) + { + stmt = "return ("; + string sep{}; + for (size_t i = 0; i < numReturns; i++) + { + stmt += sep + "r" + to_string(i); + if (sep.empty()) + sep = ", "; + } + stmt += ");\n"; + } + } + break; + } + case Type::EMIT: + // TODO + break; + case Type::ASSEMBLY: + stmt = "assembly {}"; + break; + default: + stmt = simpleStatement(); + break; + } + return stmt; +} + +string StatementGenerator::visit() +{ + static size_t constexpr maxStatements = 5; + ostringstream os; + os << "{" << std::endl; + for (size_t i = 0; i < maxStatements; i++) + os << statement(); + os << "}"; + return os.str(); +} + +string VariableDeclaration::visit() +{ + return Whiskers(varDeclTemplate) + ("type", "uint"/*type->visit()*/) + ("location", location.visit()) + ("name", identifier) + .render(); +} + +string VariableDeclarationGenerator::identifier() +{ + string id = "v" + to_string(MP{}.distributionOneToN(10, rand)); + return id; +} + +void VariableDeclarationGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator() + } + ); +} + +string VariableDeclarationGenerator::visit() +{ + string type = generator()->typeString(); + string location = generator()->visit(); + return type + " " + location + " " + identifier(); +} + +void ParameterListGenerator::setup() +{ + addGenerators({mutator->generator()}); +} + +string ParameterListGenerator::visit() +{ + size_t numParameters = MP{}.distributionOneToN(4, rand); + ostringstream out; + string sep{}; + for (size_t i = 0; i < numParameters; i++) + { + out << sep << generator()->visit(); + if (sep.empty()) + sep = ", "; + } + return out.str(); +} + +string FunctionDefinitionGenerator::functionIdentifier() +{ + switch (MP{}.distributionOneToN(3, rand)) + { + case 1: + return "f" + to_string(MP{}.distributionOneToN(10, rand)); + case 2: + return "fallback"; + case 3: + return "receive"; + } + solAssert(false, "Invalid function identifier"); +} + +void FunctionDefinitionGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator(), + mutator->generator(), + mutator->generator() + } + ); +} + +string FunctionDefinitionGenerator::visit() +{ + m_functionState = make_shared(); + state->currentSourceState().enterFunction(m_functionState); + string identifier = functionIdentifier(); + if (!state->currentSourceState().exportedSymbols.symbols.count(identifier)) + state->currentSourceState().exportedSymbols.symbols.insert(identifier); + else + return ""; + m_functionState->setName(identifier); + string modInvocation = ""; + string virtualise = m_freeFunction ? "" : MP{}.chooseOneOfNStrings({"virtual", ""}, rand); + string override = ""; + string visibility = m_freeFunction ? "" : MP{}.chooseOneOfNStrings(s_visibility, rand); + string mutability = m_freeFunction ? + MP{}.chooseOneOfNStrings(s_freeFunctionMutability, rand) : + MP{}.chooseOneOfNStrings(s_mutability, rand); + + size_t numInputs = MP{}.distributionOneToN(4, rand) - 1; + string sep{}; + string inputs{}; + for (size_t i = 0; i < numInputs; i++) + { + string location; + auto inp = generator()->randomType(); + if (inp->type.second == "bytes") + location = MP{}.chooseOneOfN(2, rand) ? "memory" : "calldata"; + m_functionState->addInput(inp); + inputs += sep + inp->type.second + " " + location + " " + "i" + to_string(m_functionState->numInputs - 1); + if (sep.empty()) + sep = ", "; + } + sep.clear(); + size_t numReturns = MP{}.distributionOneToN(4, rand) - 1; + string returns{}; + for (size_t i = 0; i < numReturns; i++) + { + string location; + auto r = generator()->randomType(); + if (r->type.second == "bytes") + location = MP{}.chooseOneOfN(2, rand) ? "memory" : "calldata"; + m_functionState->addReturn(r); + returns += sep + r->type.second + " " + location + " " + "r" + to_string(m_functionState->numReturns - 1); + if (sep.empty()) + sep = ", "; + } + + generator()->tagCategory(NatSpecGenerator::TagCategory::FUNCTION); + string natSpecString = generator()->visit(); + if ( + (visibility == "internal" || visibility == "private") && + mutability == "payable" + ) + visibility = "public"; + if (visibility == "private" && virtualise == "virtual") + visibility = "public"; + state->currentSourceState().leaveFunction(); + return Whiskers(m_functionTemplate) + ("natSpecString", natSpecString) + ("id", identifier) + ("paramList", inputs) + ("visibility", visibility) + ("stateMutability", mutability) + ("modInvocation", modInvocation) + ("virtual", virtualise) + ("overrideSpec", override) + ("return", !returns.empty()) + ("retParamList", returns) + ("definition", true) + ("body", generator()->visit()) + .render(); +} + +string EnumDeclaration::visit() +{ + string name = enumName(); + if (!state->currentSourceState().exportedSymbols.types.count(name)) + state->currentSourceState().exportedSymbols.types.insert(name); + else + return ""; + + string members{}; + string sep{}; + for (size_t i = 0; i < MP{}.distributionOneToN(s_maxMembers, rand); i++) + { + members += sep + "M" + to_string(i); + if (sep.empty()) + sep = ", "; + } + return Whiskers(enumTemplate) + ("name", name) + ("members", members) + .render(); +} + +//void FunctionTypeGenerator::setup() +//{ +// addGenerators( +// { +// mutator->generator() +// } +// ); +//} +// +//string FunctionTypeGenerator::visit() +//{ +// string visibility = MP{}.chooseOneOfNStrings(s_visibility, rand); +// size_t numParams = MP{}.distributionOneToN(4, rand) - 1; +// size_t numReturns = MP{}.distributionOneToN(4, rand) - 1; +// string sep{}; +// string params{}; +// for (size_t i = 0; i < numParams; i++) +// { +// params += sep + generator()->visit(); +// if (sep.empty()) +// sep = ", "; +// } +// sep = {}; +// string returns{}; +// for (size_t i = 0; i < numReturns; i++) +// { +// returns += sep + generator()->visit(); +// if (sep.empty()) +// sep = ", "; +// } +// return Whiskers(m_functionTypeTemplate) +// ("paramList", params) +// ("visibility", visibility) +// ("stateMutability", MP{}.chooseOneOfNStrings(FunctionDefinitionGenerator::s_mutability, rand)) +// ("return", !returns.empty()) +// ("retParamList", returns) +// .render(); +//} + +void ConstantVariableDeclaration::setup() +{ + addGenerators( + { + mutator->generator() + } + ); +} + +string ConstantVariableDeclaration::visit() +{ + // TODO: Set compileTimeConstantExpressionsOnly in + // ExpressionGenerator to true + string type = generator()->typeString(); + return Whiskers(constantVarDeclTemplate) + ("type", type) + ("name", "c") + ("expression", type + "(" + generator()->visit() + ")") + .render(); +} + +void ContractDefinitionGenerator::setup() +{ + addGenerators( + { + mutator->generator(), + mutator->generator(), + mutator->generator() + } + ); +} + +string ContractDefinitionGenerator::visit() +{ + mutator->generator()->contractFunctionMode(); + string stateVar = generator()->visit(); + string func = generator()->visit(); + generator()->tagCategory(NatSpecGenerator::TagCategory::CONTRACT); + string natSpecString = generator()->visit(); + mutator->generator()->freeFunctionMode(); + // TODO: Implement inheritance + return Whiskers(m_contractTemplate) + ("natSpecString", natSpecString) + ("abstract", MP{}.chooseOneOfN(s_abstractInvProb, rand)) + ("id", "Cx") + ("inheritance", false) + ("inheritanceSpecifierList", "X") + ("stateVar", stateVar) + ("function", func) + .render(); +} + +void TestState::print() +{ + std::cout << "Printing test state" << std::endl; + for (auto const& item: sourceUnitStates) + std::cout << "Path: " << item.first << std::endl; +} + +string TestState::randomNonCurrentPath() +{ + solAssert(size() >= 2, "Solc custom mutator: Invalid test state"); + string fallBackPath{}; + for (auto const& item: sourceUnitStates) + { + string iterPath = item.first; + if (iterPath != currentSourceName) + { + // Fallback to first encountered non current path + fallBackPath = iterPath; + if (MP{}.chooseOneOfN(size() - 1, rand)) + return iterPath; + } + } + return fallBackPath; +} + +string ImportGenerator::visit() +{ + state->print(); + /* + * Case 1: No source units defined + * Case 2: One source unit defined + * Case 3: At least two source units defined + */ + // No import + if (state->empty()) + return {}; + // Self import with a small probability + else if (state->size() == 1) + { + if (MP{}.chooseOneOfN(s_selfImportInvProb, rand)) + return Whiskers(m_importPathAs) + ("path", state->randomPath()) + ("as", false) + .render(); + else + return {}; + } + // Import pseudo randomly choosen source unit + else + { + string importPath = state->randomNonCurrentPath(); + auto importedSymbols = state->sourceUnitStates[importPath].exportedSymbols.symbols; + state->currentSourceState().exportedSymbols.symbols.insert( + importedSymbols.begin(), + importedSymbols.end() + ); + return Whiskers(m_importPathAs) + ("path", importPath) + ("as", false) + .render(); + } +} + +NatSpecGenerator::Tag NatSpecGenerator::randomTag(TagCategory _category) +{ + return s_tagLookup[_category][MP{}.distributionOneToN(s_tagLookup[_category].size(), rand) - 1]; +} + +string NatSpecGenerator::randomNatSpecString(TagCategory _category) +{ + if (m_nestingDepth > s_maxNestedTags) + return {}; + else + { + m_nestingDepth++; + string tag{}; + switch (randomTag(_category)) + { + case Tag::TITLE: + tag = "@title"; + break; + case Tag::AUTHOR: + tag = "@author"; + break; + case Tag::NOTICE: + tag = "@notice"; + break; + case Tag::DEV: + tag = "@dev"; + break; + case Tag::PARAM: + tag = "@param"; + break; + case Tag::RETURN: + tag = "@return"; + break; + case Tag::INHERITDOC: + tag = "@inheritdoc"; + break; + } + return Whiskers(m_tagTemplate) + ("tag", tag) + ("random", MP{}.generateRandomAsciiString(s_maxTextLength, rand)) + ("recurse", randomNatSpecString(_category)) + .render(); + } +} + +string NatSpecGenerator::visit() +{ +// TODO: Enable Natspec strings once we have better precision +#if 1 + return ""; +#else + reset(); + return Whiskers(R"(/// )") + ("natSpecString", randomNatSpecString(m_tag)) + ("nl", "\n") + .render(); +#endif +} + string SolidityGenerator::generateTestProgram() { createGenerators(); - for (auto& g: m_generators) + for (auto &g: m_generators) std::visit(AddDependenciesVisitor{}, g); - string program = generator()->generate(); + string program = generator()->visit(); destroyGenerators(); + destroyState(); return program; } diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index 54ffe5462..740a6e943 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -23,9 +23,12 @@ #pragma once #include +#include #include +#include + #include #include #include @@ -60,17 +63,37 @@ using Distribution = std::uniform_int_distribution; struct GenerationProbability { + enum class NumberLiteral + { + DECIMAL, + HEX + }; + /// @returns an unsigned integer in the range [1, @param _n] chosen /// uniformly at random. static size_t distributionOneToN(size_t _n, std::shared_ptr _rand) { return Distribution(1, _n)(*_rand); } + static bool chooseOneOfN(size_t _n, std::shared_ptr _rand) + { + return distributionOneToN(_n, _rand) == 1; + } + static std::string chooseOneOfNStrings( + std::vector const& _list, + std::shared_ptr _rand + ) + { + return _list[GenerationProbability{}.distributionOneToN(_list.size(), _rand) - 1]; + } + static std::string generateRandomAsciiString(size_t _length, std::shared_ptr _rand); + static std::string generateRandomHexString(size_t _length, std::shared_ptr _rand); + static std::pair generateRandomNumberLiteral(size_t _length, std::shared_ptr _rand); }; struct AddDependenciesVisitor { - template + template void operator()(T const& _t) { _t->setup(); @@ -86,6 +109,43 @@ struct GeneratorVisitor } }; +struct NameVisitor +{ + + template + std::string operator()(T const& _t) + { + return _t->name(); + } +}; + + +/// Forward declarations +#define SEMICOLON() ; +#define COMMA() , +#define EMPTY() +#define FORWARDDECLAREGENERATORS(G) class G +GENERATORLIST(FORWARDDECLAREGENERATORS, SEMICOLON(), SEMICOLON()) +#undef FORWARDDECLAREGENERATORS +class SolidityGenerator; +struct TestState; + +/// Type declarations +using GeneratorPtr = std::variant< +#define VARIANTOFSHARED(G) std::shared_ptr +GENERATORLIST(VARIANTOFSHARED, COMMA(), EMPTY()) +>; +#undef VARIANTOFSHARED + +using Generator = std::variant< +#define VARIANTOFGENERATOR(G) G +GENERATORLIST(VARIANTOFGENERATOR, COMMA(), EMPTY()) +>; +#undef VARIANTOFGENERATOR +#undef EMPTY +#undef COMMA +#undef SEMICOLON + struct GeneratorBase { GeneratorBase(std::shared_ptr _mutator); @@ -104,6 +164,7 @@ struct GeneratorBase endVisit(); return generatedCode; } + GeneratorPtr randomGenerator(); /// Virtual visitor that returns a string representing /// the generation of the Solidity grammar element. virtual std::string visit() = 0; @@ -136,6 +197,568 @@ struct GeneratorBase std::shared_ptr rand; /// Set of generators used by this generator. std::set generators; + std::shared_ptr state; +}; + +//class FunctionTypeGenerator: public GeneratorBase +//{ +//public: +// FunctionTypeGenerator(std::shared_ptr _mutator): +// GeneratorBase(std::move(_mutator)) +// {} +// void setup() override; +// void reset() override {} +// std::string name() override +// { +// return "Function type generator"; +// } +// std::string visit() override; +//private: +// static const std::vector s_visibility; +// std::string const m_functionTypeTemplate = +// std::string(R"(function () )") + +// R"( )" + +// R"( returns ())"; +//}; +// +//class UserDefinedTypeGenerator: public GeneratorBase +//{ +//public: +// UserDefinedTypeGenerator(std::shared_ptr _mutator): +// GeneratorBase(std::move(_mutator)) +// {} +// void setup() override; +// std::string visit() override; +// void reset() override {} +// std::string name() override +// { +// return "User defined type generator"; +// } +//}; + +struct SolidityType +{ + TYPE_ENUM_DECLS(Address, ADDRESS) + TYPE_ENUM_DECLS(Bool, BOOL) + TYPE_ENUM_DECLS( + Bytes, + BOOST_PP_REPEAT_FROM_TO(1, 34, BYTES_ENUM_ELEM, unused) + ) + TYPE_ENUM_DECLS( + Integer, + BOOST_PP_REPEAT_FROM_TO(0, 32, INTEGER_ENUM_ELEM, INT), + BOOST_PP_REPEAT_FROM_TO(0, 32, INTEGER_ENUM_ELEM, UINT) + ) + enum class TypeCategory: size_t + { + BOOL = 0, + ADDRESS, + INTEGER, + BYTES, + TYPEMAX + }; + using Type = std::variant; + struct TypeStringVisitor + { + template + std::string operator()(T const& _type) + { + return toString(_type); + } + }; + struct TypeIndexVisitor + { + template + size_t operator()(T const& _type) + { + return static_cast(_type); + } + }; + SolidityType(TypeCategory _type, std::shared_ptr _rand): + typeCategory(_type), + randomEngine(std::move(_rand)) + { + switch (typeCategory) + { + case TypeCategory::ADDRESS: + type = indexedAddressType(0); + break; + case TypeCategory::BOOL: + type = indexedBoolType(0); + break; + case TypeCategory::BYTES: + type = indexedBytesType(GenerationProbability{}.distributionOneToN(size_t(Bytes::BYTES) + 1, randomEngine) - 1); + break; + case TypeCategory::INTEGER: + type = indexedIntegerType(GenerationProbability{}.distributionOneToN(size_t(Integer::UINT256) + 1, randomEngine) - 1); + break; + case TypeCategory::TYPEMAX: + solAssert(false, ""); + } + } + void setValueType() + { + switch (typeCategory) + { + case TypeCategory::ADDRESS: + type = indexedAddressType(0); + break; + case TypeCategory::BOOL: + type = indexedBoolType(0); + break; + case TypeCategory::BYTES: + type = indexedBytesType(GenerationProbability{}.distributionOneToN(size_t(Bytes::BYTES), randomEngine) - 1); + break; + case TypeCategory::INTEGER: + type = indexedIntegerType(GenerationProbability{}.distributionOneToN(size_t(Integer::UINT256) + 1, randomEngine) - 1); + break; + case TypeCategory::TYPEMAX: + solAssert(false, ""); + } + } + void setNonValueType() + { + type = indexedBytesType(size_t(Bytes::BYTES)); + } + virtual ~SolidityType() = default; + TypeCategory typeCategory; + std::pair type; + std::shared_ptr randomEngine; +}; + +class ExpressionGenerator: public GeneratorBase +{ +public: + enum Type + { + INDEXACCESS = 0ul, + INDEXRANGEACCESS, + MEMBERACCESS, + FUNCTIONCALLOPTIONS, + FUNCTIONCALL, + PAYABLECONVERSION, + METATYPE, + UNARYPREFIXOP, + UNARYSUFFIXOP, + EXPOP, + MULDIVMODOP, + ADDSUBOP, + SHIFTOP, + BITANDOP, + BITXOROP, + BITOROP, + ORDERCOMPARISON, + EQUALITYCOMPARISON, + ANDOP, + OROP, + CONDITIONAL, + ASSIGNMENT, + NEWEXPRESSION, + TUPLE, + INLINEARRAY, + IDENTIFIER, + LITERAL, + ELEMENTARYTYPENAME, + USERDEFINEDTYPENAME, + TYPEMAX + }; + ExpressionGenerator( + std::shared_ptr _mutator, + bool _compileTimeConstantExpressionsOnly = false + ): + GeneratorBase(std::move(_mutator)), + m_expressionNestingDepth(0), + m_compileTimeConstantExpressionsOnly(_compileTimeConstantExpressionsOnly), + m_type(randomTypeCategory((*rand)()), rand) + {} + void setup() override {} + std::string visit() override; + void endVisit() override + { + m_expressionNestingDepth = 0; + } + std::string name() override + { + return "Expression Generator"; + } + std::string typeString() + { + return m_type.type.second; + } + std::string randomTypeString() + { + return SolidityType(randomTypeCategory((*rand)()), rand).type.second; + } + std::shared_ptr randomType() + { + return std::make_shared( + SolidityType(randomTypeCategory((*rand)()), rand) + ); + } + void setType(std::shared_ptr _type) + { + m_type = *_type; + } + void setType(SolidityType _type) + { + m_type = _type; + } + void setValueType() + { + m_type.setValueType(); + } + void setNonValueType() + { + m_type.setNonValueType(); + } +private: + static SolidityType::TypeCategory randomTypeCategory(size_t _pseudoRandomNumber) + { + return static_cast( + _pseudoRandomNumber % static_cast(SolidityType::TypeCategory::TYPEMAX) + ); + } + std::string identifier(); + std::string boolLiteral() + { + return GenerationProbability{}.chooseOneOfN(2, rand) ? "true" : "false"; + } + std::string doubleQuotedStringLiteral(); + std::string hexLiteral(); + std::string numberLiteral(); + std::string addressLiteral(); + std::string literal(); + std::string expression(); + void incrementNestingDepth() + { + m_expressionNestingDepth++; + } + bool nestingDepthTooHigh() + { + return m_expressionNestingDepth > s_maxNumNestedExpressions; + } + size_t m_expressionNestingDepth; + bool m_compileTimeConstantExpressionsOnly; + SolidityType m_type; + static constexpr size_t s_maxNumNestedExpressions = 5; + static constexpr size_t s_maxStringLength = 10; + static constexpr size_t s_maxHexLiteralLength = 64; + static constexpr size_t s_maxElementsInTuple = 4; + static constexpr size_t s_maxElementsInlineArray = 4; +}; + +class StateVariableDeclarationGenerator: public GeneratorBase +{ +public: + enum Visibility + { + PUBLIC = 0, + PRIVATE, + INTERNAL, + VISIBILITYMAX + }; + StateVariableDeclarationGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override + { + return "StateVariableDeclarationGenerator"; + } +private: + std::string identifier() + { + return "sv" + std::to_string(GenerationProbability{}.distributionOneToN(s_maxStateVariables, rand)); + } + std::string visibility(); + static constexpr size_t s_maxStateVariables = 3; + std::string const m_declarationTemplate = + std::string(R"()") + + R"( constant immutable = ;)"; +}; + +struct Exports +{ + Exports(std::string& _path): sourceUnitPath(_path), symbols({}), types({}) + {} + /// Source unit path + std::string sourceUnitPath; + /// Exported symbols + std::set symbols; + /// Exported user defined types + std::set types; +}; + +struct ExportedSymbols +{ + ExportedSymbols(): symbols({}), types({}) + {} + ExportedSymbols& operator+=(ExportedSymbols& _right) + { + for (auto item: _right.symbols) + if (!symbols.count(item)) + symbols.emplace(item); + for (auto item: _right.types) + if (!types.count(item)) + types.emplace(item); + return *this; + } + ExportedSymbols& operator+=(std::string& _right) + { + if (!symbols.count(_right)) + symbols.emplace(_right); + if (!types.count(_right)) + types.emplace(_right); + return *this; + } + void removeSymbolsAndTypes() + { + symbols.clear(); + types.clear(); + } + std::string randomSymbol(std::shared_ptr _rand); + std::string randomUserDefinedType(std::shared_ptr _rand); + std::set symbols; + std::set types; +}; + +struct State +{ + virtual ~State() {} +}; + +struct FunctionState: State +{ + ~FunctionState() + { + std::cout << "Destroying function state" << std::endl; + inputParameters.clear(); + returnParameters.clear(); + locals.clear(); + } + /// Parameter type, name pair + using ParamType = std::pair, std::string>; + enum class Mutability + { + PURE, + VIEW, + PAYABLE, + NONPAYABLE, + }; + enum class Visibility + { + EXTERNAL, + INTERNAL, + PUBLIC, + PRIVATE + }; + enum class Inheritance + { + VIRTUAL, + OVERRIDE, + VIRTUALOVERRIDE, + NONE + }; + Mutability randomMutability(std::shared_ptr _rand) + { + switch (GenerationProbability{}.distributionOneToN(4, _rand)) + { + case 1: + return Mutability::PURE; + case 2: + return Mutability::VIEW; + case 3: + return Mutability::PAYABLE; + case 4: + return Mutability::NONPAYABLE; + } + solAssert(false, ""); + } + Mutability randomFreeFunctionMutability(std::shared_ptr _rand) + { + switch (GenerationProbability{}.distributionOneToN(3, _rand)) + { + case 1: + return Mutability::PURE; + case 2: + return Mutability::VIEW; + case 3: + return Mutability::NONPAYABLE; + } + solAssert(false, ""); + } + void setName(std::string _name) + { + name = _name; + } + void setMutability(Mutability _mut) + { + mutability = _mut; + } + void setVisibility(Visibility _vis) + { + visibility = _vis; + } + void setParameterTypes(std::vector _paramTypes) + { + inputParameters = std::move(_paramTypes); + } + void setReturnTypes(std::vector _returnTypes) + { + returnParameters = std::move(_returnTypes); + } + void addReturn(std::shared_ptr& _returnType) + { + returnParameters.push_back({_returnType, "r" + std::to_string(numReturns++)}); + } + void addInput(std::shared_ptr& _inputType) + { + inputParameters.push_back({_inputType, "i" + std::to_string(numInputs++)}); + } + void addVariable(std::shared_ptr& _variableType) + { + locals.push_back({_variableType, "v" + std::to_string(numLocals++)}); + } + void setInheritance(Inheritance _inh) + { + inheritance = _inh; + } + bool identifiers() + { + return numReturns > 0 || numInputs > 0 || numLocals > 0; + } + bool operator==(FunctionState const& _other); + std::string name; + Mutability mutability; + Visibility visibility; + size_t numInputs; + size_t numReturns; + size_t numLocals; + std::vector inputParameters; + std::vector returnParameters; + std::vector locals; + Inheritance inheritance; +}; + +struct SourceUnitState: State +{ + SourceUnitState(): exportedSymbols({}) + {} + ~SourceUnitState() + { + std::cout << "Destroying source unit state" << std::endl; + functions.clear(); + exportedSymbols.removeSymbolsAndTypes(); + } + void exportSymbol(std::string& _symbol) + { + exportedSymbols += _symbol; + } + void exportSymbols(ExportedSymbols& _symbols) + { + exportedSymbols += _symbols; + } + void enterFunction(std::shared_ptr _function) + { + exportedSymbols += _function->name; + functions.emplace_back(_function); + } + void leaveFunction() + { + functions.pop_back(); + } + std::shared_ptr currentFunction() + { + if (functions.size() > 0) + return functions[functions.size() - 1]; + else + return nullptr; + } + bool functionExists(std::shared_ptr _function) + { + for (auto const& f: functions) + if (f == _function) + return true; + return false; + } + bool symbols() + { + return exportedSymbols.symbols.size() > 0; + } + bool userDefinedTypes() + { + return exportedSymbols.types.size() > 0; + } + ExportedSymbols exportedSymbols; + std::vector> functions; +}; + +struct ImportState +{ + /// Maps a symbol to its alias identifier + using SymbolAliases = std::map; + /// A single alias identifier for all symbols + using UnitAlias = std::string; + /// An alias is optional, when present it is either + /// a single identifier or a mapping of symbols to + /// their respective alias identifiers. + using Alias = std::optional>; + ImportState(std::string&& _path, std::set&& _symbols, Alias _alias): + path(_path), + symbols(std::move(_symbols)), + aliases(std::move(_alias)) + {} + /// Import path + std::string path; + /// Imported symbols + std::set symbols; + /// Alias representation + Alias aliases; +}; + +struct TestState: State +{ + TestState(std::shared_ptr _rand): + sourceUnitStates({}), + currentSourceName({}), + rand(std::move(_rand)) + {} + ~TestState() + { + std::cout << "Destroying test state" << std::endl; + } + void addSourceUnit(std::string& _path) + { + sourceUnitStates.emplace(_path, SourceUnitState{}); + currentSourceName = _path; + } + bool empty() + { + return sourceUnitStates.empty(); + } + size_t size() + { + return sourceUnitStates.size(); + } + void print(); + std::string randomPath(); + std::string randomNonCurrentPath(); + std::string currentSourceUnit() + { + return currentSourceName; + } + SourceUnitState& currentSourceState() + { + return sourceUnitStates[currentSourceUnit()]; + } + void removeSourceStates() + { + sourceUnitStates.clear(); + } + std::map sourceUnitStates; + std::string currentSourceName; + std::shared_ptr rand; }; class TestCaseGenerator: public GeneratorBase @@ -152,10 +775,27 @@ public: return "Test case generator"; } private: + bool empty() + { + return m_numSourceUnits == 0; + } + std::string randomPath(); + std::shared_ptr testState() + { + return state; + } + std::string path(size_t _number) const + { + return m_sourceUnitNamePrefix + std::to_string(_number) + ".sol"; + } std::string path() const { return m_sourceUnitNamePrefix + std::to_string(m_numSourceUnits) + ".sol"; } + void addSourceUnit(std::string& _path) + { + state->addSourceUnit(_path); + } /// Number of source units in test input size_t m_numSourceUnits; /// String prefix of source unit names @@ -164,17 +804,6 @@ private: static constexpr unsigned s_maxSourceUnits = 3; }; -class SourceUnitGenerator: public GeneratorBase -{ -public: - SourceUnitGenerator(std::shared_ptr _mutator): - GeneratorBase(std::move(_mutator)) - {} - void setup() override; - std::string visit() override; - std::string name() override { return "Source unit generator"; } -}; - class PragmaGenerator: public GeneratorBase { public: @@ -185,6 +814,553 @@ public: std::string name() override { return "Pragma generator"; } }; +class ImportGenerator: public GeneratorBase +{ +public: + ImportGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + std::string visit() override; + std::string name() override { return "Import generator"; } +private: + std::vector m_globalExports; + std::string const m_importPathAs = R"(import "" as ;)"; + std::string const m_importStar = R"(import * as from "";)"; + std::string const m_alias = R"( as )"; + std::string const m_importSymAliases = R"(import {} from "";)"; + static constexpr size_t s_selfImportInvProb = 101; +}; + +struct InterfaceFunction +{ +// FunctionMutability mutability; +}; + +struct InterfaceState +{ + +}; + +struct ContractState +{ + ContractState(): + baseContractStates({}), + functionStates({}) + {} + + void addBaseContract(); + void addFunction(); + std::vector> baseContractStates; + std::vector> functionStates; +}; + +struct Expression +{ + std::string visit() + { + return expressionTemplate; + } + std::string const expressionTemplate = R"(1)"; +}; + +struct NamedArgument +{ + std::string visit() + { + return identifier + ": " + expression.visit(); + } + std::string identifier; + Expression expression; +}; + +struct NamedArgumentList +{ + std::set namedArguments; + std::string const namedTemplate = R"({})"; +}; + +struct CallArgument +{ + using Argument = std::variant; + Argument argument; +}; + +struct CallArgumentList +{ + std::vector callArguments; +}; + +struct InheritanceSpecifier +{ + std::string name; + std::optional callArguments; +}; + +struct InheritanceSpecifierList +{ + std::set inheritanceSpecifier; +}; + +struct Location +{ + enum class Loc + { + MEMORY, + STORAGE, + CALLDATA, + STACK + }; + Location(Loc _l): loc(_l) {} + Loc loc; + std::string visit(); +}; + +class LocationGenerator: public GeneratorBase +{ +public: + LocationGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + std::string visit() override; + std::string name() override + { + return "LocationGenerator"; + } +}; + +struct IntegerWidth +{ + IntegerWidth(unsigned _w) + { + width = (8 * _w) % 256; + } + std::string visit() + { + return width > 0 ? std::to_string(width) : std::string("256"); + } + unsigned width; +}; + +struct Statement +{ + virtual ~Statement() {} + virtual std::string visit() = 0; +}; + +struct VariableDeclaration +{ + VariableDeclaration(std::shared_ptr _type, Location _loc, std::string&& _id): + type(std::move(_type)), + location(_loc), + identifier(std::move(_id)) + {} + std::shared_ptr type; + Location location; + std::string identifier; + std::string visit(); + std::string const varDeclTemplate = R"( ;)"; +}; + +class VariableDeclarationGenerator: public GeneratorBase +{ +public: + VariableDeclarationGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string name() override + { + return "VariableDeclarationGenerator"; + } + std::string visit() override; +private: + std::string identifier(); +}; + +struct ParameterListState +{ + +}; + +class ParameterListGenerator: public GeneratorBase +{ +public: + ParameterListGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override + { + return "ParameterListGenerator"; + } +}; + +struct ParameterList +{ + std::vector params; + std::string const parameterListTemplate = R"()"; +}; + +struct VariableDeclarationTuple +{ + std::vector varDecls; + std::string const varDeclTupleTemplate = + R"()"; +}; + +struct ExpressionStatement: Statement +{ + ExpressionStatement() + { + + } + std::string visit() override; + Expression expression; + std::string const exprStmtTemplate = R"(;)"; +}; + +struct VariableDeclarationTupleAssignment: Statement +{ + VariableDeclarationTuple tuple; + Expression expression; + std::string visit() override; + std::string const varDeclTupleAssignTemplate = + R"( = ;)"; +}; + +class SimpleVarDeclGenerator: public GeneratorBase +{ +public: + SimpleVarDeclGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override + { + return "Simple var decl statement generator"; + } + std::string const simpleVarDeclTemplate = + R"( = )"; +}; + +struct GeneratorTypeVisitor +{ + template + std::string operator()(T& _value) + { + return _value.visit(); + } +}; + +//struct VariableDeclarationStatement: Statement +//{ +// using VarDeclStmt = std::variant; +// VariableDeclarationStatement(VarDeclStmt _stmt): stmt(std::move(_stmt)) +// {} +// VarDeclStmt stmt; +// std::string visit() override +// { +// return std::visit(GeneratorTypeVisitor{}, stmt); +// } +//}; + +//struct SimpleStatement: Statement +//{ +// using Stmt = std::variant; +// SimpleStatement(Stmt _stmt): statement(std::move(_stmt)) +// {} +// Stmt statement; +// std::string visit() override +// { +// return std::visit(GeneratorTypeVisitor{}, statement); +// } +//}; + +/// Forward declaration +//using StatementTy = std::variant; + +class StatementGenerator: public GeneratorBase +{ +public: + enum class Type: size_t + { + BLOCK = 0, + SIMPLE, + IF, + FOR, + WHILE, + DOWHILE, + CONTINUE, + BREAK, + TRY, + RETURN, + EMIT, + ASSEMBLY, + TYPEMAX + }; + enum class YulStmtType: size_t + { + BLOCK = 0, + VARDECL, + ASSIGN, + FUNCTIONCALL, + IF, + FOR, + SWITCH, + LEAVE, + BREAK, + CONTINUE, + FUNCTIONDEF + }; + StatementGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)), + m_type(randomType((*rand)())), + m_statementNestingDepth(0), + m_numVariables(0), + m_loop(false) + {} + void setup() override; + std::string visit() override; + std::string name() override + { + return "Block statement generator"; + } +private: + std::string variableName() + { + return "v" + std::to_string(m_numVariables++); + } + std::string statement(); + static Type randomType(size_t _randomNumber) + { + return static_cast( + _randomNumber % static_cast(Type::TYPEMAX) + ); + } + void incrementNestingDepth() + { + m_statementNestingDepth++; + } + bool nestingDepthTooHigh() + { + return m_statementNestingDepth > s_maxNumNestedStatements; + } + std::string simpleStatement(); + std::string blockStatement(); + Type m_type; + size_t m_statementNestingDepth; + size_t m_numVariables; + bool m_loop; + static size_t constexpr s_maxNumNestedStatements = 5; +}; + +class EnumDeclaration: public GeneratorBase +{ +public: + EnumDeclaration(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override {} + std::string visit() override; + std::string name() override + { + return "Enum generator"; + } +private: + std::string enumName() + { + return "E" + std::to_string((*rand)() % s_maxIdentifiers); + } + std::string const enumTemplate = R"(enum { })"; + static constexpr size_t s_maxMembers = 5; + static constexpr size_t s_maxIdentifiers = 4; +}; + +class ConstantVariableDeclaration: public GeneratorBase +{ +public: + ConstantVariableDeclaration(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override { return "Constant variable generator"; } +private: + std::string const constantVarDeclTemplate = + R"( constant = ;)"; +}; + +class FallbackDefinitionGenerator: public GeneratorBase +{ +public: + FallbackDefinitionGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override + { + return "FallbackDefinitionGenerator"; + } +}; + +class FunctionDefinitionGenerator: public GeneratorBase +{ +public: + FunctionDefinitionGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + void setup() override; + std::string visit() override; + std::string name() override { return "Function generator"; } + void freeFunctionMode() + { + m_freeFunction = true; + } + void contractFunctionMode() + { + m_freeFunction = false; + } + static const std::vector s_mutability; +private: + std::string functionIdentifier(); + std::shared_ptr m_state; + std::shared_ptr m_functionState; + bool m_freeFunction; + static const std::vector s_visibility; + static const std::vector s_freeFunctionMutability; + std::string const m_functionTemplate = + R"()" + + std::string(R"(function () )") + + R"( )" + + R"( returns ())" + + R"(;)"; +}; + +class ContractDefinitionGenerator: public GeneratorBase +{ +public: + ContractDefinitionGenerator(std::shared_ptr _generator): + GeneratorBase(std::move(_generator)) + {} + void setup() override; + std::string visit() override; + std::string name() override { return "Contract generator"; } +private: + std::optional m_inheritanceList; + const std::string m_contractTemplate = + R"()" + + std::string(R"(abstract contract )") + + R"( is { })"; + /// List of inverse probabilities of sub-components + static constexpr size_t s_abstractInvProb = 10; + static constexpr size_t s_inheritanceInvProb = 10; +}; + +class SourceUnitGenerator: public GeneratorBase +{ +public: + SourceUnitGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + { + m_sourceState = std::make_shared(); + } + void setup() override; + std::string visit() override; + std::string name() override { return "Source unit generator"; } +private: + void saveState(); + std::shared_ptr m_testState; + std::shared_ptr m_sourceState; + static constexpr unsigned s_maxElements = 10; +}; + +class NatSpecGenerator: public GeneratorBase +{ +public: + enum class TagCategory + { + CONTRACT, + FUNCTION, + PUBLICSTATEVAR, + EVENT + }; + enum class Tag + { + TITLE, + AUTHOR, + NOTICE, + DEV, + PARAM, + RETURN, + INHERITDOC + }; + NatSpecGenerator(std::shared_ptr _generator): GeneratorBase(std::move(_generator)) + { + m_nestingDepth = 0; + } + void setup() override {} + std::string visit() override; + void endVisit() override + { + m_nestingDepth = 0; + } + std::string name() override { return "NatSpec generator"; } + void tagCategory(TagCategory _tag) + { + m_tag = _tag; + } +private: + std::string randomNatSpecString(TagCategory _category); + Tag randomTag(TagCategory _category); + TagCategory m_tag; + size_t m_nestingDepth; + static std::map> s_tagLookup; + static constexpr size_t s_maxTextLength = 8; + static constexpr size_t s_maxNestedTags = 3; + std::string const m_tagTemplate = R"( )"; +}; + +struct InterfaceSpecifiers +{ + std::set typeNames; +}; + +struct SourceState { + unsigned numPragmas; + unsigned numImports; + unsigned numContracts; + unsigned numAbstractContracts; + unsigned numInterfaces; + unsigned numLibraries; + unsigned numGlobalStructs; + unsigned numGlobalFuncs; + unsigned numGlobalEnums; +}; + +struct ProgramState +{ + enum class ContractType + { + CONTRACT, + ABSTRACTCONTRACT, + INTERFACE, + LIBRARY + }; + + unsigned numFunctions; + unsigned numModifiers; + unsigned numContracts; + unsigned numLibraries; + unsigned numInterfaces; + unsigned numStructs; + unsigned numEvents; + bool constructorDefined; + ContractType contractType; +}; + class SolidityGenerator: public std::enable_shared_from_this { public: @@ -201,6 +1377,10 @@ public: { return m_rand; } + std::shared_ptr testState() + { + return m_state; + } /// Returns a pseudo randomly generated test case. std::string generateTestProgram(); private: @@ -217,9 +1397,16 @@ private: { m_generators.clear(); } + void destroyState() + { + m_state->removeSourceStates(); + } + void initialize(); /// Random number generator std::shared_ptr m_rand; /// Sub generators std::set m_generators; + /// Test state + std::shared_ptr m_state; }; } diff --git a/test/tools/ossfuzz/Types.h b/test/tools/ossfuzz/Types.h new file mode 100644 index 000000000..3c243918c --- /dev/null +++ b/test/tools/ossfuzz/Types.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Convenience macros for Solidity type declarations. + */ + +#pragma once + +#include +#include + +#define BYTES_ENUM_ELEM(z, n, unused) BOOST_PP_COMMA_IF(BOOST_PP_DEC(n)) BOOST_PP_IF(BOOST_PP_NOT(BOOST_PP_EQUAL(n, 33)), BOOST_PP_CAT(BYTES, n), BYTES) +#define INTEGER_ENUM_ELEM(z, n, typeString) BOOST_PP_COMMA_IF(n) BOOST_PP_CAT(typeString, BOOST_PP_MUL(BOOST_PP_INC(n), 8)) + +#define ENUM_SWITCH_CASE(r, data, elem) \ + case data::elem: \ + return boost::algorithm::to_lower_copy(std::string(BOOST_PP_STRINGIZE(elem))); + +#define TYPE_ENUM_DECLS(typeName, ...) \ +enum class typeName: size_t \ +{ \ + __VA_ARGS__ \ +}; \ +static std::string toString(typeName e) \ +{ \ + switch(e) \ + { \ + BOOST_PP_SEQ_FOR_EACH( \ + ENUM_SWITCH_CASE, \ + typeName, \ + BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) \ + ) \ + } \ +} \ +static std::pair BOOST_PP_CAT(indexed, BOOST_PP_CAT(typeName, Type))(size_t _index) \ +{ \ + auto t = typeName(_index); \ + return std::pair(t, toString(t)); \ +} \ +static std::pair BOOST_PP_CAT(random, BOOST_PP_CAT(typeName, Type))(); + diff --git a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp index 0ca9eac2b..3cfcd7e2d 100644 --- a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp +++ b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp @@ -30,7 +30,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size); extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { - if (_size <= 600) + if (_size <= 10240) { string input(reinterpret_cast(_data), _size); map sourceCode; @@ -43,12 +43,18 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { return 0; } - FuzzerUtil::testCompiler( + auto e = FuzzerUtil::testCompiler( sourceCode, /*optimize=*/false, /*_rand=*/static_cast(_size), /*forceSMT=*/true ); + if (e == FuzzerUtil::Error::SUCCESS) + std::cout << "Compiler: Success" << std::endl; + else if (e == FuzzerUtil::Error::FAILURE) + std::cout << "Compiler: Failure" << std::endl; + else + std::cout << "Compiler: Exception" << std::endl; } return 0; }