From 5b6a4dc8c413e060681ed1756e0187fc224a5b9f Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 10 Sep 2020 16:58:39 +0200 Subject: [PATCH] Manual rebase --- test/tools/ossfuzz/SolCustomMutator.cpp | 88 +++ test/tools/ossfuzz/SolCustomMutator.h | 43 ++ test/tools/ossfuzz/SolidityMutator.cpp | 766 +++++++++++++++++++++++- test/tools/ossfuzz/SolidityMutator.h | 195 ++++++ 4 files changed, 1070 insertions(+), 22 deletions(-) create mode 100644 test/tools/ossfuzz/SolCustomMutator.cpp create mode 100644 test/tools/ossfuzz/SolCustomMutator.h diff --git a/test/tools/ossfuzz/SolCustomMutator.cpp b/test/tools/ossfuzz/SolCustomMutator.cpp new file mode 100644 index 000000000..f6707b0cc --- /dev/null +++ b/test/tools/ossfuzz/SolCustomMutator.cpp @@ -0,0 +1,88 @@ +/* + 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 + +using namespace std; +using namespace antlr4; +using namespace solidity::test::fuzzer; + +namespace { +/// Forward declare libFuzzer's default mutator definition +extern "C" size_t LLVMFuzzerMutate(uint8_t* _data, size_t _size, size_t _maxSize); + +/// Define Solidity's custom mutator by implementing libFuzzer's +/// custom mutator external interface. +extern "C" size_t LLVMFuzzerCustomMutator( + uint8_t* _data, + size_t _size, + size_t _maxSize, + unsigned int _seed +) +{ + if (_maxSize <= _size || _size == 0) + return LLVMFuzzerMutate(_data, _size, _maxSize); + return SolCustomMutator{_data, _size, _maxSize, _seed}.mutate(); +} +} + +SolCustomMutator::SolCustomMutator(uint8_t* _data, size_t _size, size_t _maxSize, unsigned int _seed) +{ + Data = _data; + Size = _size; + In = string(_data, _data + _size), + MaxMutantSize = _maxSize; + Seed = _seed; +} + +size_t SolCustomMutator::mutate() +{ + try + { + antlr4::ANTLRInputStream AStream(In); + SolidityLexer Lexer(&AStream); + Lexer.removeErrorListeners(); + antlr4::CommonTokenStream Tokens(&Lexer); + Tokens.fill(); + SolidityParser Parser(&Tokens); + Parser.removeErrorListeners(); + SolidityMutator Mutator(Seed); + Parser.sourceUnit()->accept(&Mutator); + Out = Mutator.toString(); + + // If mutant is empty, default to libFuzzer's mutator + if (Out.empty()) + return Size; + else + { + size_t mutantSize = Out.size() >= MaxMutantSize ? MaxMutantSize - 1 : Out.size(); + fill_n(Data, MaxMutantSize, 0); + copy(Out.data(), Out.data() + mutantSize, Data); + return mutantSize; + } + } + // Range error is thrown by antlr4 runtime's ANTLRInputStream + // See https://github.com/antlr/antlr4/issues/2036 + catch (range_error const&) + { + // Default to libFuzzer's mutator + return Size; + } +} diff --git a/test/tools/ossfuzz/SolCustomMutator.h b/test/tools/ossfuzz/SolCustomMutator.h new file mode 100644 index 000000000..d62f5643d --- /dev/null +++ b/test/tools/ossfuzz/SolCustomMutator.h @@ -0,0 +1,43 @@ +/* + 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 +#include +#include + +namespace solidity::test::fuzzer +{ +struct SolCustomMutator +{ + SolCustomMutator(uint8_t* _data, size_t _size, size_t _maxSize, unsigned _seed); + size_t mutate(); + + uint8_t* Data = nullptr; + size_t Size = 0; + std::string In{}; + std::string Out{}; + size_t MaxMutantSize = 0; + unsigned int Seed = 0; +}; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/SolidityMutator.cpp b/test/tools/ossfuzz/SolidityMutator.cpp index b588c36e3..e8862c3eb 100644 --- a/test/tools/ossfuzz/SolidityMutator.cpp +++ b/test/tools/ossfuzz/SolidityMutator.cpp @@ -16,44 +16,766 @@ */ // SPDX-License-Identifier: GPL-3.0 +#include + #include +#include + #include +#include using namespace std; using namespace solidity::util; using namespace solidity::test::fuzzer; +using SolP = solidity::test::fuzzer::SolidityParser; -antlrcpp::Any SolidityMutator::visitSourceUnit(SolidityParser::SourceUnitContext* _ctx) +string Generator::operator()(std::string&& _template, std::map&& _args) { - if (_ctx->pragmaDirective().empty()) - m_out << Whiskers(s_pragmaDirective)("directive", "experimental SMTChecker").render(); - else - visitChildren(_ctx); + Whiskers w(move(_template)); + for (auto &&item: _args) + w(move(item.first), move(item.second)); + return w.render(); +} + +template +void SolidityMutator::fuzzRule(T* _ctx) +{ + mutate(_ctx); +// if (coinToss()) +// gen(); +} + +void SolidityMutator::gen() +{ + static const vector validPragmas = { + "experimental SMTChecker", + "experimental ABIEncoderV2" + }; + m_out << generator( + [&]() { + return Whiskers(R"(pragma ;)") + ("string", validPragmas[m_rand() % validPragmas.size()]) + .render(); + }, + randOneToN(s_maxPragmas), + "\n", + true + ); +} + +void SolidityMutator::mutate(SolP::PragmaDirectiveContext*) +{ + m_out << Whiskers(R"(pragma ;)") + ("mutation", mutatePragma()) + .render(); +} + +string SolidityMutator::eventParam() +{ + return Whiskers(R"( )") + ("typename", typeName()) + ("indexed", coinToss() ? "indexed" : "") + ("id", coinToss() ? genRandomId("ev"): "") + .render(); +} + +string SolidityMutator::visibility() +{ + if (interfaceScope()) + return "external"; + + switch (randOneToN(4)) + { + case 1: + return "public"; + case 2: + return "external"; + case 3: + return "private"; + case 4: + return "internal"; + } + assert(false); +} + +string SolidityMutator::mutability() +{ + switch (randOneToN(4)) + { + case 1: + return "view"; + case 2: + return "pure"; + case 3: + if (libraryScope() || globalScope()) + return ""; + else + return "payable"; + case 4: + return ""; + } + assert(false); +} + +string SolidityMutator::elementaryType(bool _allowAddressPayable) +{ + switch (randOneToN(8)) + { + case 1: + return "address"; + case 2: + if (_allowAddressPayable) + return "address payable"; + else + return "address"; + case 3: + return "bool"; + case 4: + return "string"; + case 5: + return "bytes"; + case 6: + return "int" + bitWidth(); + case 7: + return "uint" + bitWidth(); + case 8: + return "bytes" + byteWidth(); + } + assert(false); +} + +string SolidityMutator::typeName() +{ + switch (randOneToN(5)) + { + case 1: + // TODO: Implement function type mutator + return "function () external view returns (uint)"; + case 2: + return Whiskers(R"(mapping ( => ))") + ("keyType", elementaryType(false)) + ("typeName", typeName()) + .render(); + case 3: + if (m_numStructs > 0) + return "s" + to_string(m_rand() % m_numStructs); + else + return elementaryType(); + case 4: + // TODO: Implement complex types + return "int[]"; + case 5: + return elementaryType(); + } + assert(false); +} + +string SolidityMutator::dataLocation() +{ + switch (randOneToN(3)) + { + case 1: + return "storage"; + case 2: + return "memory"; + case 3: + return "calldata"; + } + assert(false); +} + +template +antlrcpp::Any SolidityMutator::genericVisitor(C* _ctx, std::string _ctxName) +{ + visitChildren(_ctx); return antlrcpp::Any(); } -antlrcpp::Any SolidityMutator::visitPragmaDirective(SolidityParser::PragmaDirectiveContext*) +string SolidityMutator::genRandomUserDefinedTypeName(unsigned _numIds, unsigned _len, char _start, char _end) { - auto PickLiteral = [&]() -> string - { - static const vector alphanum = { - "0", - "experimental", - "solidity < 142857", - "solidity >=0.0.0", - "solidity >=0.0.0 <0.8.0", - "experimental __test", - "experimental SMTChecker", - "experimental ABIEncoderV2", - "experimental \"xyz\"" - }; + uniform_int_distribution dist(_start, _end); + vector ids{}; + generate_n(back_inserter(ids), m_rand() % _numIds + 1, [&](){ return genRandString(_len, _start, _end); }); + return boost::algorithm::join(ids, "."); +} - return alphanum[randomOneToN(alphanum.size()) - 1]; +string SolidityMutator::genRandString(unsigned int _maxLen, char _start, char _end) +{ + uniform_int_distribution dist(_start, _end); + string result{}; + generate_n(back_inserter(result), m_rand() % _maxLen + 1, [&]{ return dist(m_rand); }); + return result; +} + +template +void SolidityMutator::visitItemsOrGenerate(T _container, F&& _generator, Args&&... _args) +{ + if (_container.size() == 0) + mayRun(forward(_generator), forward(_args)...); + else + for (auto item: _container) + item->accept(this); +} + +template +auto SolidityMutator::mayRun(F&& _func, Args&&... _args, unsigned _n) +{ + if (m_rand() % _n == 0) + return invoke(forward(_func), forward(_args)...); + else + return result_of::type(); +} + +antlrcpp::Any SolidityMutator::visitSourceUnit(SolP::SourceUnitContext* _ctx) +{ +// for (auto &item: _ctx->getRuleContexts()) +// visitItemsOrGenerate(item, gen, this, item); + +// mayRun( +// [](SolP::SourceUnitContext* _ctx) { +// visitItemsOrGenerate( +// _ctx->pragmaDirective(), +// [&]() { m_out << "pragma solidity >= 0.0.0;"; } +// ); +// visitItemsOrGenerate( +// _ctx->importDirective(), +// []() {} +// ); +// visitItemsOrGenerate( +// _ctx->contractDefinition(), +// [&]() { genContract(); } +// ); +// visitItemsOrGenerate( +// _ctx->interfaceDefinition(), +// [&]() { +// m_out << Whiskers(R"(interface { function f0() external; })") +// ("id", "I" + to_string(m_numInterfaces++)) +// .render(); +// } +// ); +// visitItemsOrGenerate( +// _ctx->libraryDefinition(), +// [&]() { +// m_out << Whiskers(R"(library { function f0() public {} })") +// ("id", "L" + to_string(m_numLibraries++)) +// .render(); +// } +// ); +// visitItemsOrGenerate( +// _ctx->functionDefinition(), +// [&]() { +// m_out << "function func() {}"; +// } +// ); +// visitItemsOrGenerate( +// _ctx->structDefinition(), +// [&]() { +// m_out << "struct S { uint sm0; }"; +// } +// ); +// visitItemsOrGenerate( +// _ctx->enumDefinition(), +// [&]() { +// m_out << "enum E { first }"; +// } +// ); +// } +// ); + + + return antlrcpp::Any(); +} + +//void SolidityMutator::gen(SolP::ModifierDefinitionContext*) +//{ +// constexpr string s = R"(modifier () ;)"; +// Whiskers mod(R"(modifier () ;)"); +//} +// +//void SolidityMutator::mutate(SolP::ModifierDefinitionContext* _ctx) +//{ +// for (auto &rule: _ctx->getRuleContexts()) +// std::cout << rule->getText() << std::endl; +//} + +antlrcpp::Any SolidityMutator::visitModifierDefinition(SolidityParser::ModifierDefinitionContext* _ctx) +{ + Whiskers mod(R"(modifier () ;)"); + + bool noDef = coinToss(); + m_out << mod + ("id", genRandomId("m", 2)) + ("params", _ctx->arguments ? _ctx->arguments->accept(this).as() : "") + ("virtual", coinToss() ? "virtual" : "") + ("override", coinToss() ? "override" : "") + ("noDef", noDef) + .render(); + if (!noDef && _ctx->body) + _ctx->body->accept(this); + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitStructDefinition(SolP::StructDefinitionContext* _ctx) +{ + Whiskers structDef(R"(struct { })"); + unsigned numMembers = 0; + auto fieldGenerator = [&]() { + return Whiskers(R"( ;)") + ("typeName", typeName()) + ("id", "sm" + to_string(numMembers++)) + .render(); }; + structDef("members", generator(fieldGenerator, randOneToN(5), " ", true)); + structDef("id", "s" + to_string(m_numStructs++)); + m_out << structDef.render(); + return antlrcpp::Any(); +} - string pragma = PickLiteral(); +antlrcpp::Any SolidityMutator::visitEnumDefinition(SolP::EnumDefinitionContext* _ctx) +{ + unsigned numEnums = 0; + string elements = generator( + [&]() { return "e" + to_string(numEnums++); }, + randOneToN(5), + ", ", + false + ); + m_out << Whiskers(R"(enum {})") + ("id", "en" + to_string(m_numEnums++)) + ("elements", elements) + .render(); + return antlrcpp::Any(); +} - m_out << Whiskers(s_pragmaDirective)("directive", pragma).render(); +string SolidityMutator::generator(function _gen, unsigned _n, string _separator, bool _removeDups) +{ + vector g{}; + generate_n( + back_inserter(g), + _n, + _gen + ); + if (_removeDups) + { + sort(g.begin(), g.end()); + g.erase(unique(g.begin(), g.end()), g.end()); + } + return boost::algorithm::join(g, _separator); +} + +string SolidityMutator::mutatePragma() +{ + string minVersion = generator( + [&]() { return genRandString(1, '0', '9'); }, + 3, + ".", + false + ); + string maxVersion = generator( + [&]() { return genRandString(1, '0', '9'); }, + 3, + ".", + false + ); + string lSign = coinToss() ? ">" : ">="; + string uSign = coinToss() ? "<" : "<="; + return Whiskers(R"(solidity )") + ("l", coinToss()) + ("u", coinToss()) + ("lSign", lSign) + ("minV", minVersion) + ("uSign", uSign) + ("maxV", maxVersion) + .render(); +} + +void SolidityMutator::genContract() +{ + m_out << Whiskers(R"(contract { function f0() public {} })") + ("id", "C" + to_string(m_numContracts++)) + .render(); +} + +antlrcpp::Any SolidityMutator::visitPragmaDirective(SolP::PragmaDirectiveContext* _ctx) +{ + fuzzRule(_ctx); + return antlrcpp::Any(); +} + +string SolidityMutator::genImport() +{ + string path = genRandomPath(); + string id = genRandomId("Im"); + + Whiskers importPathAsId(R"(import as ;)"); + Whiskers importStarAsIdFromPath(R"(import * as from ;)"); + Whiskers importPath(R"(import ;)"); + + switch (randOneToN(4)) + { + case 1: + return importPathAsId("path", path)("id", id).render(); + case 2: + return importStarAsIdFromPath("id", id)("path", path).render(); + case 3: + { + vector symbols{}; + unsigned numElements = randOneToN(10); + generate_n( + back_inserter(symbols), + numElements, + [&](){ + return Whiskers((R"( as )")) + ("as", coinToss()) + ("path", genRandomPath()) + ("id", genRandomId("Im")) + ("symbol", genRandString(5, 'A', 'z')) + .render(); + } + ); + return Whiskers(R"(import {} from ;)") + ("syms", boost::algorithm::join(symbols, ", ")) + ("path", path) + .render(); + } + case 4: + return importPath("path", path).render(); + } + assert(false); +} + +antlrcpp::Any SolidityMutator::visitImportDirective(SolP::ImportDirectiveContext* _ctx) +{ + m_out << regex_replace( + _ctx->getText(), + regex("as|from|*|"), + " $& " + ); + return antlrcpp::Any(); +} + +template +void SolidityMutator::interfaceContractLibraryVisitor(T* _ctx) +{ + assert( + antlrcpp::is(_ctx) || + antlrcpp::is(_ctx) || + antlrcpp::is(_ctx) + ); + + string baseNames{}; + bool atLeastOneBaseContract = m_numContracts > 0; + bool atLeastOneBaseInterface = m_numInterfaces > 0; + if (atLeastOneBaseContract || atLeastOneBaseInterface) + baseNames = generator( + [&]() { + if (contractScope()) + { + if (atLeastOneBaseContract && coinToss()) + return "C" + to_string(m_rand() % m_numContracts); + else if (atLeastOneBaseInterface && coinToss()) + return "I" + to_string(m_rand() % m_numInterfaces); + else + return string{}; + } + else if (interfaceScope() && atLeastOneBaseInterface) + return "I" + to_string(m_rand() % m_numInterfaces); + else + return string{}; + }, + randOneToN(3), + ", ", + true + ); + + string name{}; + if (contractScope()) + name = "C" + to_string(m_numContracts++); + else if (interfaceScope()) + name = "I" + to_string(m_numInterfaces++); + else + name = "L" + to_string(m_numLibraries++); + + Whiskers contractDef(R"(abstract contractinterfacelibrary is {)"); + + m_out << contractDef + ("abs", absContractScope()) + ("c", contractScope()) + ("i", interfaceScope()) + ("l", libraryScope()) + ("id", name) + ("inh", !libraryScope() && atLeastOneBaseContract && !baseNames.empty()) + ("iid", baseNames) + .render(); + constructorDefined = false; + if (coinToss()) + visitChildren(_ctx); + else + { + switch (randOneToN(10)) + { + case 1: + if ((contractScope() || absContractScope()) && !constructorDefined) + m_out << "constructor() external {}"; + break; + case 2: + if (!interfaceScope()) + m_out << "function f() public {}"; + else + m_out << "function f() external;"; + break; + case 3: + genModifier(); + break; + case 4: + if (contractScope() || absContractScope()) + m_out << "fallback() external {}"; + else if (interfaceScope()) + m_out << "fallback() external;"; + break; + case 5: + if (contractScope() || absContractScope()) + m_out << "receive() external payable {}"; + else if (interfaceScope()) + m_out << "receive() external payable;"; + break; + case 6: + m_out << "struct s { uint x; }"; + break; + case 7: + m_out << "enum e { first }"; + break; + case 8: + if (contractScope() || absContractScope()) + m_out << "uint x;"; + else if (libraryScope()) + m_out << "uint constant x = 1337;"; + break; + case 9: + m_out << "event e() anonymous;"; + break; + case 10: + if (!interfaceScope()) + m_out << "using L for *;"; + break; + } + } + + m_out << "}\n"; +} + +antlrcpp::Any SolidityMutator::visitContractDefinition(SolP::ContractDefinitionContext* _ctx) +{ + unsigned numGlobalFuncs = m_numFunctions; + m_numFunctions = 0; + ScopeGuard s( [&]() { m_type = Type::GLOBAL; m_numFunctions = numGlobalFuncs; }); + m_type = _ctx->Abstract() ? Type::ABSCONTRACT : Type::CONTRACT; + interfaceContractLibraryVisitor(_ctx); + if (coinToss()) + genContract(); + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitInterfaceDefinition(SolP::InterfaceDefinitionContext* _ctx) +{ + unsigned numGlobalFuncs = m_numFunctions; + m_numFunctions = 0; + ScopeGuard s( [&]() { m_type = Type::GLOBAL; m_numFunctions = numGlobalFuncs; }); + m_type = Type::INTERFACE; + interfaceContractLibraryVisitor(_ctx); + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitLibraryDefinition(SolP::LibraryDefinitionContext* _ctx) +{ + unsigned numGlobalFuncs = m_numFunctions; + m_numFunctions = 0; + ScopeGuard s( [&]() { m_type = Type::GLOBAL; m_numFunctions = numGlobalFuncs; }); + m_type = Type::LIBRARY; + interfaceContractLibraryVisitor(_ctx); + return antlrcpp::Any(); +} + +template +void SolidityMutator::visitItemOrGenerate(T _item, function _generator) +{ + if (_item) + _item->accept(this); + else + _generator(); +} + +antlrcpp::Any SolidityMutator::visitContractBodyElement(SolP::ContractBodyElementContext* _ctx) +{ + return genericVisitor(_ctx, "contractbodyelement"); +} + +antlrcpp::Any SolidityMutator::visitTypeName(SolP::TypeNameContext*) +{ + m_out << typeName(); + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitParameterList(SolP::ParameterListContext* _ctx) +{ + vector paramDecls{}; + for (auto &i: _ctx->parameterDeclaration()) + paramDecls.push_back(i->accept(this).as()); + generate_n( + back_inserter(paramDecls), + randOneToN(3), + [&]() { + return Whiskers(R"( )") + ("typeName", typeName()) + ("dataLoc", dataLocation()) + ("id", genRandomId("p", 10)) + .render(); + } + ); + return boost::algorithm::join(paramDecls, ", "); +} + +antlrcpp::Any SolidityMutator::visitParameterDeclaration(SolP::ParameterDeclarationContext* _ctx) +{ + Whiskers paramDecl(R"( )"); + return paramDecl("typeName", typeName())("dataLoc", dataLocation())("id", genRandomId("p", 10)).render(); +} + +antlrcpp::Any SolidityMutator::visitConstructorDefinition(SolP::ConstructorDefinitionContext* _ctx) +{ + // Skip over redundant constructor defs + if (!constructorDefined) + { + Whiskers constructorDef(R"(constructor () )"); + string args = _ctx->arguments ? _ctx->arguments->accept(this).as() : ""; + string visibility = coinToss() ? "public" : "internal"; + string payable = coinToss() ? "payable" : ""; + m_out << constructorDef("args", args)("payable", payable)("visibility", visibility).render(); + constructorDefined = true; + if (_ctx->body) + _ctx->body->accept(this); + else + m_out << "{}"; + } + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitEventParameter(SolP::EventParameterContext* _ctx) +{ + return eventParam(); +} + +antlrcpp::Any SolidityMutator::visitEventDefinition(SolP::EventDefinitionContext* _ctx) +{ + Whiskers eventDef(R"(event () ;))"); + eventDef("id", genRandomId("ev")); + + vector params{}; + for (auto &i: _ctx->parameters) + params.push_back(i->accept(this).as()); + eventDef("params", boost::algorithm::join(params, ", ")); + eventDef("anonymous", coinToss() ? "anonymous" : ""); + + m_out << eventDef.render(); + vector addEvents; + generate_n( + back_inserter(addEvents), + randOneToN(3), + [&]() { + Whiskers t = Whiskers(R"(event () ;)"); + vector params{}; + generate_n( + back_inserter(params), + randOneToN(3), + [&]() { return eventParam(); } + ); + return t + ("id", genRandomId("p", 10)) + ("params", boost::algorithm::join(params, ", ")) + ("anon", coinToss() ? "anonymous" : "") + .render(); + } + ); + m_out << boost::algorithm::join(addEvents, ""); + return antlrcpp::Any(); +} + +string SolidityMutator::genFunctionName() +{ + if (likely()) + return "f" + to_string(m_numFunctions++); + else + { + if (coinToss()) + return "fallback"; + else + return "receive"; + } +} + +void SolidityMutator::genModifier() +{ + Whiskers modDef(R"(modifier () )"); + modDef("id", "m" + to_string(m_numModifiers++)); + modDef("params", ""); + modDef("virtual", coinToss() ? "virtual" : ""); + modDef("override", coinToss() ? "override": ""); + modDef("SemicolonOrBody", coinToss() ? ";" : "{ _; }"); + m_out << modDef.render(); +} + +antlrcpp::Any SolidityMutator::visitFunctionDefinition(SolP::FunctionDefinitionContext* _ctx) +{ + // Free functions cannot + // - have visibility + // - are not payable + // - are not virtual + // - cannot be overridden + string vis{}; + if (interfaceScope()) + vis = "external"; + else if (contractScope() || libraryScope()) + vis = visibility(); + string mut = mutability(); + if ((vis == "internal" || vis == "private") && mut == "payable") + vis = "public"; + string id = genFunctionName(); + + // Must implement + bool mustImplement = contractScope() || libraryScope() || globalScope(); + // May implement + bool mayImplement = absContractScope() && coinToss(); + + Whiskers functionDef( + R"(function () virtual override )" + ); + bool mayVirtualise = (contractScope() || absContractScope()) && (vis == "public" || vis == "external"); + bool mayOverride = contractScope() || absContractScope() || interfaceScope(); + m_out << functionDef + ("id", id) + ("args", _ctx->arguments ? _ctx->arguments->accept(this).as() : "") + ("vis", vis) + ("mut", mut) + ("virt", mayVirtualise && coinToss()) + ("oride", mayOverride && coinToss()) + ("mod", "") + .render(); + if (_ctx->body && (mustImplement || mayImplement)) + _ctx->body->accept(this); + else + { + if (mustImplement || mayImplement) + m_out << "{}"; + else + m_out << ";"; + } + + return antlrcpp::Any(); +} + +antlrcpp::Any SolidityMutator::visitBlock(SolP::BlockContext* _ctx) +{ + m_out << "{}"; return antlrcpp::Any(); } \ No newline at end of file diff --git a/test/tools/ossfuzz/SolidityMutator.h b/test/tools/ossfuzz/SolidityMutator.h index 87cdaa58f..e09492449 100644 --- a/test/tools/ossfuzz/SolidityMutator.h +++ b/test/tools/ossfuzz/SolidityMutator.h @@ -31,9 +31,14 @@ #include namespace solidity::test::fuzzer { + using RandomEngine = std::mt19937_64; using Dist = std::uniform_int_distribution; +struct Generator { + std::string operator()(std::string&& _template, std::map&& _args); +}; + class SolidityMutator: public SolidityBaseVisitor { public: @@ -45,12 +50,201 @@ public: antlrcpp::Any visitSourceUnit(SolidityParser::SourceUnitContext* _ctx) override; antlrcpp::Any visitPragmaDirective(SolidityParser::PragmaDirectiveContext* _ctx) override; + + antlrcpp::Any visitImportDirective(SolidityParser::ImportDirectiveContext* _ctx) override; + antlrcpp::Any visitContractDefinition(SolidityParser::ContractDefinitionContext* _ctx) override; + antlrcpp::Any visitContractBodyElement(SolidityParser::ContractBodyElementContext* _ctx) override; + antlrcpp::Any visitConstructorDefinition(SolidityParser::ConstructorDefinitionContext* _ctx) override; + antlrcpp::Any visitParameterList(SolidityParser::ParameterListContext* _ctx) override; + antlrcpp::Any visitParameterDeclaration(SolidityParser::ParameterDeclarationContext* _ctx) override; + antlrcpp::Any visitBlock(SolidityParser::BlockContext* _ctx) override; + antlrcpp::Any visitFunctionDefinition(SolidityParser::FunctionDefinitionContext* _ctx) override; + antlrcpp::Any visitEnumDefinition(SolidityParser::EnumDefinitionContext* _ctx) override; + antlrcpp::Any visitStructDefinition(SolidityParser::StructDefinitionContext* _ctx) override; + antlrcpp::Any visitEventDefinition(SolidityParser::EventDefinitionContext* _ctx) override; + antlrcpp::Any visitEventParameter(SolidityParser::EventParameterContext* _ctx) override; + antlrcpp::Any visitInterfaceDefinition(SolidityParser::InterfaceDefinitionContext* _ctx) override; + antlrcpp::Any visitLibraryDefinition(SolidityParser::LibraryDefinitionContext* _ctx) override; + antlrcpp::Any visitModifierDefinition(SolidityParser::ModifierDefinitionContext* _ctx) override; + /// Types + antlrcpp::Any visitTypeName(SolidityParser::TypeNameContext* _ctx) override; + private: + enum class Type + { + CONTRACT, + ABSCONTRACT, + INTERFACE, + LIBRARY, + GLOBAL + }; + + bool globalScope() + { + return m_type == Type::GLOBAL; + } + + bool absContractScope() + { + return m_type == Type::ABSCONTRACT; + } + + bool contractScope() + { + return m_type == Type::CONTRACT; + } + + bool libraryScope() + { + return m_type == Type::LIBRARY; + } + + bool interfaceScope() + { + return m_type == Type::INTERFACE; + } + + template + antlrcpp::Any genericVisitor(C* _ctx, std::string _ctxName); + + template + void interfaceContractLibraryVisitor(T* _ctx); + + template + void visitItemsOrGenerate(T _container, std::function _generator); + + template + void visitItemOrGenerate(T _item, std::function _generator); + + bool likely() + { + return m_rand() % s_largePrime != 0; + } + /// @returns either true or false with roughly the same probability bool coinToss() { return m_rand() % 2 == 0; } + + /// @returns a pseudo randomly chosen unsigned number between one + /// and @param _n + unsigned randOneToN(unsigned _n = 20) + { + return m_rand() % _n + 1; + } + + /// @returns a pseudo randomly generated path string of + /// the form `.sol` where `` is a single + /// character in the ascii range [A-Z]. + std::string genRandomPath() + { + return "\"" + genRandString(1, 'A', 'Z') + ".sol" + "\""; + } + + /// @returns a pseudo randomly generated identifier that + /// comprises a single character in the ascii range [a-z]. + std::string genRandomId(std::string _prefix, unsigned _n = 3) + { + return _prefix + std::to_string(m_rand() % _n); + } + + template + void genRule(T* _ctx); + + template + void mutateRule(T* _ctx); + + void genContract(); + + void genModifier(); + + std::string generator(std::function _gen, unsigned _n, std::string _separator, bool _removeDups); + + std::string genImport(); + + std::string genFunctionName(); + + /// @returns bit width as a string where bit width is a multiple + /// of 8 and in the range [8, 256]. + std::string bitWidth() + { + return std::to_string(randOneToN(32) * 8); + } + + /// @returns byte width as a string where byte width is in + /// the range [1, 32]. + std::string byteWidth() + { + return std::to_string(randOneToN(32)); + } + + /// @returns a Solidity event parameter as string + std::string eventParam(); + + /// @returns a pseudo randomly chosen elementary Solidity type name + /// as a string including address payable type if @param _allowAddressPayable + /// is true (default), excluding otherwise. + std::string elementaryType(bool _allowAddressPayable = true); + + /// @returns a pseudo randomly chosen Solidity type name as a string. + std::string typeName(); + + /// @returns a pseudo randomly chosen Solidity data location + std::string dataLocation(); + + /// @returns a pseudo randomly chosen Solidity function visibility. + std::string visibility(); + + /// @returns a pseudo randomly chosen Solidity function state mutability. + std::string mutability(); + + /// @returns a pseudo randomly generated user defined + /// type name that comprises at most @param _numIds identifiers separated + /// by a period ('.'). + /// Each identifier is at most @param _len characters long each + /// character being choosen uniformly at random in the character + /// range between [@param _start, @param _end] + std::string genRandomUserDefinedTypeName( + unsigned _numIds = 2, + unsigned _len = 1, + char _start = 'a', + char _end = 'z' + ); + + + /// @returns a pseudo randomly generated string of length at most + /// @param _maxLen characters where each character in the string + /// is in the ASCII character range between @param _start and + /// @param _end characters. + std::string genRandString(unsigned _maxLen, char _start = '!', char _end = '~'); + + /// Flag that indicates if contract constructor has been defined + bool constructorDefined = false; + + unsigned m_numStructs = 0; + unsigned m_numFunctions = 0; + unsigned m_numLibraries = 0; + unsigned m_numContracts = 0; + unsigned m_numInterfaces = 0; + unsigned m_numEnums = 0; + unsigned m_numModifiers = 0; + Type m_type = Type::GLOBAL; + + static constexpr unsigned s_maxPragmas = 2; + static constexpr unsigned s_maxImports = 2; + + static constexpr unsigned s_largePrime = 1009; + void gen(SolidityParser::PragmaDirectiveContext*); + std::string mutatePragma(); + template + void fuzzRule(T* _ctx); + void mutate(SolidityParser::PragmaDirectiveContext*); + template + auto mayRun(F&& _func, Args&&... _args, unsigned int _n = 101); + template + void visitItemsOrGenerate(T _container, F&& _generator, Args&&... _args); + /// @returns a pseudo randomly chosen unsigned integer between one /// and @param _n uint32_t randomOneToN(uint32_t _n) @@ -64,5 +258,6 @@ private: RandomEngine m_rand; /// Whisker template strings for Solidity syntax const std::string s_pragmaDirective = R"(pragma ;)"; + void gen(); }; } \ No newline at end of file