From 0a59bd825b219cbf2b062ba1fb53e0e8751a2626 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Wed, 27 Jan 2021 14:55:30 +0100 Subject: [PATCH] Solidity fuzzer: Add simple import statements. Co-authored-by: Leonardo --- test/tools/ossfuzz/Generators.h | 1 + test/tools/ossfuzz/SolidityGenerator.cpp | 91 +++++++++++++++++++++++- test/tools/ossfuzz/SolidityGenerator.h | 87 +++++++++++++++++++--- 3 files changed, 165 insertions(+), 14 deletions(-) diff --git a/test/tools/ossfuzz/Generators.h b/test/tools/ossfuzz/Generators.h index 675984857..4a5aae999 100644 --- a/test/tools/ossfuzz/Generators.h +++ b/test/tools/ossfuzz/Generators.h @@ -41,6 +41,7 @@ * */ #define GENERATORLIST(MACRO, SEP, ENDSEP) \ + MACRO(ImportGenerator) SEP \ MACRO(PragmaGenerator) SEP \ MACRO(SourceUnitGenerator) SEP \ MACRO(TestCaseGenerator) ENDSEP diff --git a/test/tools/ossfuzz/SolidityGenerator.cpp b/test/tools/ossfuzz/SolidityGenerator.cpp index ac903226e..471430a3d 100644 --- a/test/tools/ossfuzz/SolidityGenerator.cpp +++ b/test/tools/ossfuzz/SolidityGenerator.cpp @@ -19,6 +19,7 @@ #include #include +#include using namespace solidity::test::fuzzer; using namespace solidity::util; @@ -29,6 +30,7 @@ GeneratorBase::GeneratorBase(std::shared_ptr _mutator) { mutator = std::move(_mutator); rand = mutator->randomEngine(); + state = mutator->testState(); } string GeneratorBase::visitChildren() @@ -40,10 +42,58 @@ string GeneratorBase::visitChildren() randomisedChildren.push_back(child); shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand); for (auto child: randomisedChildren) - os << std::visit(GeneratorVisitor{}, child); + os << std::visit(GenericVisitor{ + [&](auto const& _item) { return _item->generate(); } + }, child); return os.str(); } +string TestState::randomPath(set const& _sourceUnitPaths) const +{ + auto it = _sourceUnitPaths.begin(); + /// Advance iterator by n where 0 <= n <= sourceUnitPaths.size() - 1 + size_t increment = PrngUtil{}.distributionOneToN(_sourceUnitPaths.size(), rand) - 1; + solAssert( + increment >= 0 && increment < _sourceUnitPaths.size(), + "Solc custom mutator: Invalid increment" + ); + advance(it, increment); + return *it; +} + +string TestState::randomPath() const +{ + solAssert(!empty(), "Solc custom mutator: Null test state"); + 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; +} + +string TestState::randomNonCurrentPath() const +{ + /// To obtain a source path that is not the currently visited + /// source unit itself, we require at least one other source + /// unit to be previously visited. + solAssert(size() >= 2, "Solc custom mutator: Invalid test state"); + + set filteredSourcePaths; + string currentPath = currentSourceUnitPath; + copy_if( + sourceUnitPaths.begin(), + sourceUnitPaths.end(), + inserter(filteredSourcePaths, filteredSourcePaths.begin()), + [currentPath](string const& _item) { + return _item != currentPath; + } + ); + return randomPath(filteredSourcePaths); +} + void TestCaseGenerator::setup() { addGenerators({ @@ -62,6 +112,7 @@ string TestCaseGenerator::visit() << sourcePath << " ====" << "\n"; + updateSourcePath(sourcePath); m_numSourceUnits++; os << visitChildren(); } @@ -71,7 +122,8 @@ string TestCaseGenerator::visit() void SourceUnitGenerator::setup() { addGenerators({ - mutator->generator(), + mutator->generator(), + mutator->generator() }); } @@ -93,6 +145,36 @@ string PragmaGenerator::visit() return preamble + abiPragma; } +string ImportGenerator::visit() +{ + /* + * Case 1: No source units defined + * Case 2: One source unit defined + * 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) + { + if (PrngUtil{}.probable(s_selfImportInvProb, rand)) + os << "import " + << "\"" + << state->randomPath() + << "\";"; + } + else + { + // Import a different source unit if at least + // two source units available. + os << "import " + << "\"" + << state->randomNonCurrentPath() + << "\";"; + } + return os.str(); +} + template shared_ptr SolidityGenerator::generator() { @@ -106,6 +188,7 @@ SolidityGenerator::SolidityGenerator(unsigned _seed) { m_rand = make_shared(_seed); m_generators = {}; + m_state = make_shared(m_rand); } template @@ -122,7 +205,9 @@ string SolidityGenerator::generateTestProgram() { createGenerators(); for (auto& g: m_generators) - std::visit(AddDependenciesVisitor{}, g); + std::visit(GenericVisitor{ + [&](auto const& _item) { return _item->setup(); } + }, g); string program = generator()->generate(); destroyGenerators(); return program; diff --git a/test/tools/ossfuzz/SolidityGenerator.h b/test/tools/ossfuzz/SolidityGenerator.h index 7f8e861b2..195cd4140 100644 --- a/test/tools/ossfuzz/SolidityGenerator.h +++ b/test/tools/ossfuzz/SolidityGenerator.h @@ -66,24 +66,54 @@ struct GenerationProbability { return Distribution(1, _n)(*_rand); } -}; - -struct AddDependenciesVisitor -{ - template - void operator()(T const& _t) + /// @returns true with a probability of 1/(@param _n), false otherwise. + /// @param _n must be non zero. + static bool probable(size_t _n, std::shared_ptr const& _rand) { - _t->setup(); + solAssert(_n > 0, ""); + return distributionOneToN(_n, _rand) == 1; } }; -struct GeneratorVisitor +struct TestState { - template - std::string operator()(T const& _t) + explicit TestState(std::shared_ptr _rand): + sourceUnitPaths({}), + currentSourceUnitPath({}), + rand(std::move(_rand)) + {} + /// Adds @param _path to @name sourceUnitPaths updates + /// @name currentSourceUnitPath. + void addSourceUnit(std::string const& _path) { - return _t->generate(); + sourceUnitPaths.insert(_path); + currentSourceUnitPath = _path; } + /// @returns true if @name sourceUnitPaths is empty, + /// false otherwise. + [[nodiscard]] bool empty() const + { + return sourceUnitPaths.empty(); + } + /// Returns the number of items in @name sourceUnitPaths. + [[nodiscard]] size_t size() const + { + return sourceUnitPaths.size(); + } + /// Prints test state to @param _os. + void print(std::ostream& _os) const; + /// 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::string randomPath() const; + /// Returns a randomly chosen non current source unit path. + [[nodiscard]] std::string randomNonCurrentPath() const; + /// List of source paths in test input. + std::set sourceUnitPaths; + /// Source path being currently visited. + std::string currentSourceUnitPath; + /// Random number generator. + std::shared_ptr rand; }; struct GeneratorBase @@ -136,6 +166,8 @@ struct GeneratorBase std::shared_ptr rand; /// Set of generators used by this generator. std::set generators; + /// Shared ptr to global test state. + std::shared_ptr state; }; class TestCaseGenerator: public GeneratorBase @@ -160,6 +192,13 @@ private: { return m_sourceUnitNamePrefix + std::to_string(m_numSourceUnits) + ".sol"; } + /// Adds @param _path to list of source paths in global test + /// state and increments @name m_numSourceUnits. + void updateSourcePath(std::string const& _path) + { + state->addSourceUnit(_path); + m_numSourceUnits++; + } /// Number of source units in test input size_t m_numSourceUnits; /// String prefix of source unit names @@ -189,6 +228,25 @@ public: std::string name() override { return "Pragma generator"; } }; +class ImportGenerator: public GeneratorBase +{ +public: + explicit ImportGenerator(std::shared_ptr _mutator): + GeneratorBase(std::move(_mutator)) + {} + 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 { public: @@ -205,6 +263,11 @@ public: } /// Returns a pseudo randomly generated test case. std::string generateTestProgram(); + /// Returns shared ptr to global test state. + std::shared_ptr testState() + { + return m_state; + } private: template void createGenerator() @@ -223,5 +286,7 @@ private: std::shared_ptr m_rand; /// Sub generators std::set m_generators; + /// Shared global test state + std::shared_ptr m_state; }; }