Solidity fuzzer: Add simple import statements.

Co-authored-by: Leonardo <leo@ethereum.org>
This commit is contained in:
Bhargava Shastry 2021-01-27 14:55:30 +01:00
parent 8a4e6acdac
commit 0a59bd825b
3 changed files with 165 additions and 14 deletions

View File

@ -41,6 +41,7 @@
*
*/
#define GENERATORLIST(MACRO, SEP, ENDSEP) \
MACRO(ImportGenerator) SEP \
MACRO(PragmaGenerator) SEP \
MACRO(SourceUnitGenerator) SEP \
MACRO(TestCaseGenerator) ENDSEP

View File

@ -19,6 +19,7 @@
#include <test/tools/ossfuzz/SolidityGenerator.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/Visitor.h>
using namespace solidity::test::fuzzer;
using namespace solidity::util;
@ -29,6 +30,7 @@ GeneratorBase::GeneratorBase(std::shared_ptr<SolidityGenerator> _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<string> 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<string> 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<PragmaGenerator>(),
mutator->generator<ImportGenerator>(),
mutator->generator<PragmaGenerator>()
});
}
@ -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 <typename T>
shared_ptr<T> SolidityGenerator::generator()
{
@ -106,6 +188,7 @@ SolidityGenerator::SolidityGenerator(unsigned _seed)
{
m_rand = make_shared<RandomEngine>(_seed);
m_generators = {};
m_state = make_shared<TestState>(m_rand);
}
template <size_t I>
@ -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<TestCaseGenerator>()->generate();
destroyGenerators();
return program;

View File

@ -66,24 +66,54 @@ struct GenerationProbability
{
return Distribution(1, _n)(*_rand);
}
};
struct AddDependenciesVisitor
{
template <typename T>
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<RandomEngine> const& _rand)
{
_t->setup();
solAssert(_n > 0, "");
return distributionOneToN(_n, _rand) == 1;
}
};
struct GeneratorVisitor
struct TestState
{
template <typename T>
std::string operator()(T const& _t)
explicit TestState(std::shared_ptr<RandomEngine> _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<std::string> 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<std::string> sourceUnitPaths;
/// Source path being currently visited.
std::string currentSourceUnitPath;
/// Random number generator.
std::shared_ptr<RandomEngine> rand;
};
struct GeneratorBase
@ -136,6 +166,8 @@ struct GeneratorBase
std::shared_ptr<RandomEngine> rand;
/// Set of generators used by this generator.
std::set<GeneratorPtr> generators;
/// Shared ptr to global test state.
std::shared_ptr<TestState> 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<SolidityGenerator> _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<SolidityGenerator>
{
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> testState()
{
return m_state;
}
private:
template <typename T>
void createGenerator()
@ -223,5 +286,7 @@ private:
std::shared_ptr<RandomEngine> m_rand;
/// Sub generators
std::set<GeneratorPtr> m_generators;
/// Shared global test state
std::shared_ptr<TestState> m_state;
};
}