diff --git a/test/tools/ossfuzz/SolidityCustomMutatorInterface.cpp b/test/tools/ossfuzz/SolidityCustomMutatorInterface.cpp index ea5fa20ce..fad3f08a4 100644 --- a/test/tools/ossfuzz/SolidityCustomMutatorInterface.cpp +++ b/test/tools/ossfuzz/SolidityCustomMutatorInterface.cpp @@ -61,7 +61,7 @@ size_t SolidityCustomMutatorInterface::generate() { string testCase = generator->generateTestProgram(); solAssert( - !testCase.empty() && data, + data, "Solc custom mutator: Invalid mutant or memory pointer" ); size_t mutantSize = min(testCase.size(), maxMutantSize - 1); diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index 1d836a4ae..dd79f78d8 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -21,6 +21,12 @@ #include #include +#include +#include +#include + + +using namespace solidity::test::fuzzer; using namespace solidity::test::fuzzer::mutator; using namespace solidity::util; using namespace std; @@ -36,17 +42,32 @@ string GeneratorBase::visitChildren() { ostringstream os; // Randomise visit order - vector randomisedChildren; + vector> randomisedChildren; for (auto const& child: generators) randomisedChildren.push_back(child); shuffle(randomisedChildren.begin(), randomisedChildren.end(), *uRandDist->randomEngine); - for (auto child: randomisedChildren) - os << std::visit(GenericVisitor{ - [&](auto const& _item) { return _item->generate(); } - }, child); + for (auto const& child: randomisedChildren) + if (uRandDist->likely(child.second + 1)) + for (unsigned i = 0; i < uRandDist->distributionOneToN(child.second); i++) + os << std::visit(GenericVisitor{ + [&](auto const& _item) { return _item->generate(); } + }, child.first); return os.str(); } +void SourceState::print(std::ostream& _os) const +{ + for (auto const& import: importedSources) + _os << "Imports: " << import << std::endl; +} + +set TestState::sourceUnitPaths() const +{ + set keys; + boost::copy(sourceUnitState | boost::adaptors::map_keys, std::inserter(keys, keys.begin())); + return keys; +} + string TestState::randomPath(set const& _sourceUnitPaths) const { auto it = _sourceUnitPaths.begin(); @@ -63,14 +84,17 @@ string TestState::randomPath(set const& _sourceUnitPaths) const string TestState::randomPath() const { solAssert(!empty(), "Solc custom mutator: Null test state"); - return randomPath(sourceUnitPaths); + return randomPath(sourceUnitPaths()); } void TestState::print(std::ostream& _os) const { _os << "Printing test state" << std::endl; - for (auto const& item: sourceUnitPaths) - _os << "Source path: " << item << std::endl; + for (auto const& item: sourceUnitState) + { + _os << "Source path: " << item.first << std::endl; + item.second->print(_os); + } } string TestState::randomNonCurrentPath() const @@ -82,9 +106,10 @@ string TestState::randomNonCurrentPath() const set filteredSourcePaths; string currentPath = currentSourceUnitPath; + set sourcePaths = sourceUnitPaths(); copy_if( - sourceUnitPaths.begin(), - sourceUnitPaths.end(), + sourcePaths.begin(), + sourcePaths.end(), inserter(filteredSourcePaths, filteredSourcePaths.begin()), [currentPath](string const& _item) { return _item != currentPath; @@ -96,52 +121,42 @@ string TestState::randomNonCurrentPath() const void TestCaseGenerator::setup() { addGenerators({ - mutator->generator() + {mutator->generator(), s_maxSourceUnits} }); } string TestCaseGenerator::visit() { - ostringstream os; - for (unsigned i = 0; i < uRandDist->distributionOneToN(s_maxSourceUnits); i++) - { - string sourcePath = path(); - os << "\n" - << "==== Source: " - << sourcePath - << " ====" - << "\n"; - updateSourcePath(sourcePath); - m_numSourceUnits++; - os << visitChildren(); - } - return os.str(); + return visitChildren(); } void SourceUnitGenerator::setup() { addGenerators({ - mutator->generator(), - mutator->generator() + {mutator->generator(), s_maxImports}, + {mutator->generator(), 1} }); } string SourceUnitGenerator::visit() { - return visitChildren(); + state->addSource(); + ostringstream os; + os << "\n" + << "==== Source: " + << state->currentPath() + << " ====" + << "\n"; + os << visitChildren(); + return os.str(); } string PragmaGenerator::visit() { - static constexpr const char* preamble = R"( - pragma solidity >= 0.0.0; - pragma experimental SMTChecker; - )"; - // Choose equally at random from coder v1 and v2 - string abiPragma = "pragma abicoder v" + - to_string(uRandDist->distributionOneToN(2)) + - ";\n"; - return preamble + abiPragma; + set pragmas = uRandDist->subset(s_genericPragmas); + // Choose either abicoder v1 or v2 but not both. + pragmas.insert(s_abiPragmas[uRandDist->distributionOneToN(s_abiPragmas.size()) - 1]); + return boost::algorithm::join(pragmas, "\n") + "\n"; } string ImportGenerator::visit() @@ -152,24 +167,19 @@ string ImportGenerator::visit() * Case 3: At least two source units defined */ ostringstream os; - // Self import with a small probability only if - // there is one source unit present in test. - if (state->size() == 1) + string importPath; + // Import a different source unit if at least + // two source units available. + if (state->size() > 1) + importPath = state->randomNonCurrentPath(); + // Do not reimport already imported source unit + if (!importPath.empty() && !state->sourceUnitState[state->currentPath()]->sourcePathImported(importPath)) { - if (uRandDist->probable(s_selfImportInvProb)) - os << "import " - << "\"" - << state->randomPath() - << "\";"; - } - else - { - // Import a different source unit if at least - // two source units available. os << "import " - << "\"" - << state->randomNonCurrentPath() - << "\";"; + << "\"" + << importPath + << "\";\n"; + state->sourceUnitState[state->currentPath()]->addImportedSourcePath(importPath); } return os.str(); } diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index f56efe9a8..8cff2d33c 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -68,57 +68,139 @@ struct UniformRandomDistribution /// uniformly at random. [[nodiscard]] size_t distributionOneToN(size_t _n) const { + solAssert(_n > 0, ""); return Distribution(1, _n)(*randomEngine); } /// @returns true with a probability of 1/(@param _n), false otherwise. - /// @param _n must be non zero. + /// @param _n > 1. [[nodiscard]] bool probable(size_t _n) const { - solAssert(_n > 0, ""); + solAssert(_n > 1, ""); return distributionOneToN(_n) == 1; } + /// @returns true with a probability of 1 - 1/(@param _n), + /// false otherwise. + /// @param _n > 1. + [[nodiscard]] bool likely(size_t _n) const + { + solAssert(_n > 1, ""); + return !probable(_n); + } + /// @returns a subset whose elements are of type @param T + /// created from the set @param _container using + /// uniform selection. + template + std::set subset(std::set const& _container) + { + size_t s = _container.size(); + solAssert(s > 1, ""); + std::set subContainer; + for (auto const& item: _container) + if (probable(s)) + subContainer.insert(item); + return subContainer; + } std::unique_ptr randomEngine; }; +struct SourceState +{ + explicit SourceState(std::shared_ptr _urd): + uRandDist(std::move(_urd)), + importedSources({}) + {} + void addImportedSourcePath(std::string& _sourcePath) + { + importedSources.emplace(_sourcePath); + } + [[nodiscard]] bool sourcePathImported(std::string const& _sourcePath) const + { + return importedSources.count(_sourcePath); + } + ~SourceState() + { + importedSources.clear(); + } + /// Prints source state to @param _os. + void print(std::ostream& _os) const; + std::shared_ptr uRandDist; + std::set importedSources; +}; + struct TestState { explicit TestState(std::shared_ptr _urd): - sourceUnitPaths({}), + sourceUnitState({}), currentSourceUnitPath({}), - uRandDist(std::move(_urd)) + uRandDist(std::move(_urd)), + numSourceUnits(0) {} /// Adds @param _path to @name sourceUnitPaths updates /// @name currentSourceUnitPath. void addSourceUnit(std::string const& _path) { - sourceUnitPaths.insert(_path); + sourceUnitState.emplace(_path, std::make_shared(uRandDist)); currentSourceUnitPath = _path; } - /// @returns true if @name sourceUnitPaths is empty, + /// Returns true if @name sourceUnitPaths is empty, /// false otherwise. [[nodiscard]] bool empty() const { - return sourceUnitPaths.empty(); + return sourceUnitState.empty(); } - /// @returns the number of items in @name sourceUnitPaths. + /// Returns the number of items in @name sourceUnitPaths. [[nodiscard]] size_t size() const { - return sourceUnitPaths.size(); + return sourceUnitState.size(); + } + /// Returns a new source path name that is formed by concatenating + /// a static prefix @name m_sourceUnitNamePrefix, a monotonically + /// increasing counter starting from 0 and the postfix (extension) + /// ".sol". + [[nodiscard]] std::string newPath() const + { + return sourceUnitNamePrefix + std::to_string(numSourceUnits) + ".sol"; + } + [[nodiscard]] std::string currentPath() const + { + solAssert(numSourceUnits > 0, ""); + return currentSourceUnitPath; + } + /// Adds @param _path to list of source paths in global test + /// state and increments @name m_numSourceUnits. + void updateSourcePath(std::string const& _path) + { + addSourceUnit(_path); + numSourceUnits++; + } + /// Adds a new source unit to test case. + void addSource() + { + updateSourcePath(newPath()); + } + ~TestState() + { + sourceUnitState.clear(); } /// Prints test state to @param _os. void print(std::ostream& _os) const; - /// @returns a randomly chosen path from @param _sourceUnitPaths. + /// Returns a randomly chosen path from @param _sourceUnitPaths. [[nodiscard]] std::string randomPath(std::set const& _sourceUnitPaths) const; - /// @returns a randomly chosen path from @name sourceUnitPaths. + [[nodiscard]] std::set sourceUnitPaths() const; + /// Returns a randomly chosen path from @name sourceUnitPaths. [[nodiscard]] std::string randomPath() const; - /// @returns a randomly chosen non current source unit path. + /// Returns a randomly chosen non current source unit path. [[nodiscard]] std::string randomNonCurrentPath() const; /// List of source paths in test input. - std::set sourceUnitPaths; + std::map> sourceUnitState; /// Source path being currently visited. std::string currentSourceUnitPath; /// Uniform random distribution. std::shared_ptr uRandDist; + /// Number of source units in test input + size_t numSourceUnits; + /// String prefix of source unit names + std::string const sourceUnitNamePrefix = "su"; }; struct GeneratorBase @@ -128,8 +210,8 @@ struct GeneratorBase std::shared_ptr generator() { for (auto& g: generators) - if (std::holds_alternative>(g)) - return std::get>(g); + if (std::holds_alternative>(g.first)) + return std::get>(g.first); solAssert(false, ""); } /// @returns test fragment created by this generator. @@ -151,7 +233,7 @@ struct GeneratorBase std::string visitChildren(); /// Adds generators for child grammar elements of /// this grammar element. - void addGenerators(std::set _generators) + void addGenerators(std::set> _generators) { generators += _generators; } @@ -168,7 +250,7 @@ struct GeneratorBase /// Shared pointer to the mutator instance std::shared_ptr mutator; /// Set of generators used by this generator. - std::set generators; + std::set> generators; /// Shared ptr to global test state. std::shared_ptr state; /// Uniform random distribution @@ -179,8 +261,7 @@ class TestCaseGenerator: public GeneratorBase { public: explicit TestCaseGenerator(std::shared_ptr _mutator): - GeneratorBase(std::move(_mutator)), - m_numSourceUnits(0) + GeneratorBase(std::move(_mutator)) {} void setup() override; std::string visit() override; @@ -221,6 +302,8 @@ public: void setup() override; std::string visit() override; std::string name() override { return "Source unit generator"; } +private: + static unsigned constexpr s_maxImports = 2; }; class PragmaGenerator: public GeneratorBase @@ -231,6 +314,15 @@ public: {} std::string visit() override; std::string name() override { return "Pragma generator"; } +private: + std::set const s_genericPragmas = { + R"(pragma solidity >= 0.0.0;)", + R"(pragma experimental SMTChecker;)", + }; + std::vector const s_abiPragmas = { + R"(pragma abicoder v1;)", + R"(pragma abicoder v2;)" + }; }; class ImportGenerator: public GeneratorBase @@ -241,15 +333,6 @@ public: {} std::string visit() override; std::string name() override { return "Import generator"; } -private: - /// Inverse probability with which a source unit - /// imports itself. Keeping this at 17 seems to - /// produce self imported source units with a - /// frequency small enough so that it does not - /// consume too many fuzzing cycles but large - /// enough so that the fuzzer generates self - /// import statements every once in a while. - static constexpr size_t s_selfImportInvProb = 17; }; class SolidityGenerator: public std::enable_shared_from_this