mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10856 from ethereum/add-import-stmt-generator
Solidity fuzzer: Add import stmt generator
This commit is contained in:
commit
1a949e5323
@ -41,6 +41,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#define GENERATORLIST(MACRO, SEP, ENDSEP) \
|
#define GENERATORLIST(MACRO, SEP, ENDSEP) \
|
||||||
|
MACRO(ImportGenerator) SEP \
|
||||||
MACRO(PragmaGenerator) SEP \
|
MACRO(PragmaGenerator) SEP \
|
||||||
MACRO(SourceUnitGenerator) SEP \
|
MACRO(SourceUnitGenerator) SEP \
|
||||||
MACRO(TestCaseGenerator) ENDSEP
|
MACRO(TestCaseGenerator) ENDSEP
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <test/tools/ossfuzz/SolidityGenerator.h>
|
#include <test/tools/ossfuzz/SolidityGenerator.h>
|
||||||
|
|
||||||
#include <libsolutil/Whiskers.h>
|
#include <libsolutil/Whiskers.h>
|
||||||
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
using namespace solidity::test::fuzzer;
|
using namespace solidity::test::fuzzer;
|
||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
@ -29,6 +30,7 @@ GeneratorBase::GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator)
|
|||||||
{
|
{
|
||||||
mutator = std::move(_mutator);
|
mutator = std::move(_mutator);
|
||||||
rand = mutator->randomEngine();
|
rand = mutator->randomEngine();
|
||||||
|
state = mutator->testState();
|
||||||
}
|
}
|
||||||
|
|
||||||
string GeneratorBase::visitChildren()
|
string GeneratorBase::visitChildren()
|
||||||
@ -40,10 +42,58 @@ string GeneratorBase::visitChildren()
|
|||||||
randomisedChildren.push_back(child);
|
randomisedChildren.push_back(child);
|
||||||
shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand);
|
shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand);
|
||||||
for (auto child: randomisedChildren)
|
for (auto child: randomisedChildren)
|
||||||
os << std::visit(GeneratorVisitor{}, child);
|
os << std::visit(GenericVisitor{
|
||||||
|
[&](auto const& _item) { return _item->generate(); }
|
||||||
|
}, child);
|
||||||
return os.str();
|
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()
|
void TestCaseGenerator::setup()
|
||||||
{
|
{
|
||||||
addGenerators({
|
addGenerators({
|
||||||
@ -62,6 +112,7 @@ string TestCaseGenerator::visit()
|
|||||||
<< sourcePath
|
<< sourcePath
|
||||||
<< " ===="
|
<< " ===="
|
||||||
<< "\n";
|
<< "\n";
|
||||||
|
updateSourcePath(sourcePath);
|
||||||
m_numSourceUnits++;
|
m_numSourceUnits++;
|
||||||
os << visitChildren();
|
os << visitChildren();
|
||||||
}
|
}
|
||||||
@ -71,7 +122,8 @@ string TestCaseGenerator::visit()
|
|||||||
void SourceUnitGenerator::setup()
|
void SourceUnitGenerator::setup()
|
||||||
{
|
{
|
||||||
addGenerators({
|
addGenerators({
|
||||||
mutator->generator<PragmaGenerator>(),
|
mutator->generator<ImportGenerator>(),
|
||||||
|
mutator->generator<PragmaGenerator>()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +145,36 @@ string PragmaGenerator::visit()
|
|||||||
return preamble + abiPragma;
|
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>
|
template <typename T>
|
||||||
shared_ptr<T> SolidityGenerator::generator()
|
shared_ptr<T> SolidityGenerator::generator()
|
||||||
{
|
{
|
||||||
@ -106,6 +188,7 @@ SolidityGenerator::SolidityGenerator(unsigned _seed)
|
|||||||
{
|
{
|
||||||
m_rand = make_shared<RandomEngine>(_seed);
|
m_rand = make_shared<RandomEngine>(_seed);
|
||||||
m_generators = {};
|
m_generators = {};
|
||||||
|
m_state = make_shared<TestState>(m_rand);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t I>
|
template <size_t I>
|
||||||
@ -122,7 +205,9 @@ string SolidityGenerator::generateTestProgram()
|
|||||||
{
|
{
|
||||||
createGenerators();
|
createGenerators();
|
||||||
for (auto& g: m_generators)
|
for (auto& g: m_generators)
|
||||||
std::visit(AddDependenciesVisitor{}, g);
|
std::visit(GenericVisitor{
|
||||||
|
[&](auto const& _item) { return _item->setup(); }
|
||||||
|
}, g);
|
||||||
string program = generator<TestCaseGenerator>()->generate();
|
string program = generator<TestCaseGenerator>()->generate();
|
||||||
destroyGenerators();
|
destroyGenerators();
|
||||||
return program;
|
return program;
|
||||||
|
@ -66,24 +66,54 @@ struct GenerationProbability
|
|||||||
{
|
{
|
||||||
return Distribution(1, _n)(*_rand);
|
return Distribution(1, _n)(*_rand);
|
||||||
}
|
}
|
||||||
};
|
/// @returns true with a probability of 1/(@param _n), false otherwise.
|
||||||
|
/// @param _n must be non zero.
|
||||||
struct AddDependenciesVisitor
|
static bool probable(size_t _n, std::shared_ptr<RandomEngine> const& _rand)
|
||||||
{
|
|
||||||
template <typename T>
|
|
||||||
void operator()(T const& _t)
|
|
||||||
{
|
{
|
||||||
_t->setup();
|
solAssert(_n > 0, "");
|
||||||
|
return distributionOneToN(_n, _rand) == 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GeneratorVisitor
|
struct TestState
|
||||||
{
|
{
|
||||||
template <typename T>
|
explicit TestState(std::shared_ptr<RandomEngine> _rand):
|
||||||
std::string operator()(T const& _t)
|
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
|
struct GeneratorBase
|
||||||
@ -136,6 +166,8 @@ struct GeneratorBase
|
|||||||
std::shared_ptr<RandomEngine> rand;
|
std::shared_ptr<RandomEngine> rand;
|
||||||
/// Set of generators used by this generator.
|
/// Set of generators used by this generator.
|
||||||
std::set<GeneratorPtr> generators;
|
std::set<GeneratorPtr> generators;
|
||||||
|
/// Shared ptr to global test state.
|
||||||
|
std::shared_ptr<TestState> state;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TestCaseGenerator: public GeneratorBase
|
class TestCaseGenerator: public GeneratorBase
|
||||||
@ -160,6 +192,13 @@ private:
|
|||||||
{
|
{
|
||||||
return m_sourceUnitNamePrefix + std::to_string(m_numSourceUnits) + ".sol";
|
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
|
/// Number of source units in test input
|
||||||
size_t m_numSourceUnits;
|
size_t m_numSourceUnits;
|
||||||
/// String prefix of source unit names
|
/// String prefix of source unit names
|
||||||
@ -189,6 +228,25 @@ public:
|
|||||||
std::string name() override { return "Pragma generator"; }
|
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>
|
class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -205,6 +263,11 @@ public:
|
|||||||
}
|
}
|
||||||
/// Returns a pseudo randomly generated test case.
|
/// Returns a pseudo randomly generated test case.
|
||||||
std::string generateTestProgram();
|
std::string generateTestProgram();
|
||||||
|
/// Returns shared ptr to global test state.
|
||||||
|
std::shared_ptr<TestState> testState()
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void createGenerator()
|
void createGenerator()
|
||||||
@ -223,5 +286,7 @@ private:
|
|||||||
std::shared_ptr<RandomEngine> m_rand;
|
std::shared_ptr<RandomEngine> m_rand;
|
||||||
/// Sub generators
|
/// Sub generators
|
||||||
std::set<GeneratorPtr> m_generators;
|
std::set<GeneratorPtr> m_generators;
|
||||||
|
/// Shared global test state
|
||||||
|
std::shared_ptr<TestState> m_state;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user