Solidity fuzzer: Add simple import statements and test/source state.

This commit is contained in:
Bhargava Shastry 2021-01-27 14:55:30 +01:00
parent 98e7b61a37
commit fa9328cb0e
3 changed files with 175 additions and 82 deletions

View File

@ -61,7 +61,7 @@ size_t SolidityCustomMutatorInterface::generate()
{ {
string testCase = generator->generateTestProgram(); string testCase = generator->generateTestProgram();
solAssert( solAssert(
!testCase.empty() && data, data,
"Solc custom mutator: Invalid mutant or memory pointer" "Solc custom mutator: Invalid mutant or memory pointer"
); );
size_t mutantSize = min(testCase.size(), maxMutantSize - 1); size_t mutantSize = min(testCase.size(), maxMutantSize - 1);

View File

@ -21,6 +21,12 @@
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <libsolutil/Visitor.h> #include <libsolutil/Visitor.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm/copy.hpp>
using namespace solidity::test::fuzzer;
using namespace solidity::test::fuzzer::mutator; using namespace solidity::test::fuzzer::mutator;
using namespace solidity::util; using namespace solidity::util;
using namespace std; using namespace std;
@ -36,17 +42,32 @@ string GeneratorBase::visitChildren()
{ {
ostringstream os; ostringstream os;
// Randomise visit order // Randomise visit order
vector<GeneratorPtr> randomisedChildren; vector<std::pair<GeneratorPtr, unsigned>> randomisedChildren;
for (auto const& child: generators) for (auto const& child: generators)
randomisedChildren.push_back(child); randomisedChildren.push_back(child);
shuffle(randomisedChildren.begin(), randomisedChildren.end(), *uRandDist->randomEngine); shuffle(randomisedChildren.begin(), randomisedChildren.end(), *uRandDist->randomEngine);
for (auto child: randomisedChildren) 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{ os << std::visit(GenericVisitor{
[&](auto const& _item) { return _item->generate(); } [&](auto const& _item) { return _item->generate(); }
}, child); }, child.first);
return os.str(); return os.str();
} }
void SourceState::print(std::ostream& _os) const
{
for (auto const& import: importedSources)
_os << "Imports: " << import << std::endl;
}
set<string> TestState::sourceUnitPaths() const
{
set<string> keys;
boost::copy(sourceUnitState | boost::adaptors::map_keys, std::inserter(keys, keys.begin()));
return keys;
}
string TestState::randomPath(set<string> const& _sourceUnitPaths) const string TestState::randomPath(set<string> const& _sourceUnitPaths) const
{ {
auto it = _sourceUnitPaths.begin(); auto it = _sourceUnitPaths.begin();
@ -63,14 +84,17 @@ string TestState::randomPath(set<string> const& _sourceUnitPaths) const
string TestState::randomPath() const string TestState::randomPath() const
{ {
solAssert(!empty(), "Solc custom mutator: Null test state"); solAssert(!empty(), "Solc custom mutator: Null test state");
return randomPath(sourceUnitPaths); return randomPath(sourceUnitPaths());
} }
void TestState::print(std::ostream& _os) const void TestState::print(std::ostream& _os) const
{ {
_os << "Printing test state" << std::endl; _os << "Printing test state" << std::endl;
for (auto const& item: sourceUnitPaths) for (auto const& item: sourceUnitState)
_os << "Source path: " << item << std::endl; {
_os << "Source path: " << item.first << std::endl;
item.second->print(_os);
}
} }
string TestState::randomNonCurrentPath() const string TestState::randomNonCurrentPath() const
@ -82,9 +106,10 @@ string TestState::randomNonCurrentPath() const
set<string> filteredSourcePaths; set<string> filteredSourcePaths;
string currentPath = currentSourceUnitPath; string currentPath = currentSourceUnitPath;
set<string> sourcePaths = sourceUnitPaths();
copy_if( copy_if(
sourceUnitPaths.begin(), sourcePaths.begin(),
sourceUnitPaths.end(), sourcePaths.end(),
inserter(filteredSourcePaths, filteredSourcePaths.begin()), inserter(filteredSourcePaths, filteredSourcePaths.begin()),
[currentPath](string const& _item) { [currentPath](string const& _item) {
return _item != currentPath; return _item != currentPath;
@ -96,52 +121,42 @@ string TestState::randomNonCurrentPath() const
void TestCaseGenerator::setup() void TestCaseGenerator::setup()
{ {
addGenerators({ addGenerators({
mutator->generator<SourceUnitGenerator>() {mutator->generator<SourceUnitGenerator>(), s_maxSourceUnits}
}); });
} }
string TestCaseGenerator::visit() string TestCaseGenerator::visit()
{ {
ostringstream os; return visitChildren();
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();
} }
void SourceUnitGenerator::setup() void SourceUnitGenerator::setup()
{ {
addGenerators({ addGenerators({
mutator->generator<ImportGenerator>(), {mutator->generator<ImportGenerator>(), s_maxImports},
mutator->generator<PragmaGenerator>() {mutator->generator<PragmaGenerator>(), 1}
}); });
} }
string SourceUnitGenerator::visit() string SourceUnitGenerator::visit()
{ {
return visitChildren(); state->addSource();
ostringstream os;
os << "\n"
<< "==== Source: "
<< state->currentPath()
<< " ===="
<< "\n";
os << visitChildren();
return os.str();
} }
string PragmaGenerator::visit() string PragmaGenerator::visit()
{ {
static constexpr const char* preamble = R"( set<string> pragmas = uRandDist->subset(s_genericPragmas);
pragma solidity >= 0.0.0; // Choose either abicoder v1 or v2 but not both.
pragma experimental SMTChecker; pragmas.insert(s_abiPragmas[uRandDist->distributionOneToN(s_abiPragmas.size()) - 1]);
)"; return boost::algorithm::join(pragmas, "\n") + "\n";
// Choose equally at random from coder v1 and v2
string abiPragma = "pragma abicoder v" +
to_string(uRandDist->distributionOneToN(2)) +
";\n";
return preamble + abiPragma;
} }
string ImportGenerator::visit() string ImportGenerator::visit()
@ -152,24 +167,19 @@ string ImportGenerator::visit()
* Case 3: At least two source units defined * Case 3: At least two source units defined
*/ */
ostringstream os; ostringstream os;
// Self import with a small probability only if string importPath;
// there is one source unit present in test.
if (state->size() == 1)
{
if (uRandDist->probable(s_selfImportInvProb))
os << "import "
<< "\""
<< state->randomPath()
<< "\";";
}
else
{
// Import a different source unit if at least // Import a different source unit if at least
// two source units available. // 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))
{
os << "import " os << "import "
<< "\"" << "\""
<< state->randomNonCurrentPath() << importPath
<< "\";"; << "\";\n";
state->sourceUnitState[state->currentPath()]->addImportedSourcePath(importPath);
} }
return os.str(); return os.str();
} }

View File

@ -68,57 +68,139 @@ struct UniformRandomDistribution
/// uniformly at random. /// uniformly at random.
[[nodiscard]] size_t distributionOneToN(size_t _n) const [[nodiscard]] size_t distributionOneToN(size_t _n) const
{ {
solAssert(_n > 0, "");
return Distribution(1, _n)(*randomEngine); return Distribution(1, _n)(*randomEngine);
} }
/// @returns true with a probability of 1/(@param _n), false otherwise. /// @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 [[nodiscard]] bool probable(size_t _n) const
{ {
solAssert(_n > 0, ""); solAssert(_n > 1, "");
return distributionOneToN(_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 <typename T>
std::set<T> subset(std::set<T> const& _container)
{
size_t s = _container.size();
solAssert(s > 1, "");
std::set<T> subContainer;
for (auto const& item: _container)
if (probable(s))
subContainer.insert(item);
return subContainer;
}
std::unique_ptr<RandomEngine> randomEngine; std::unique_ptr<RandomEngine> randomEngine;
}; };
struct SourceState
{
explicit SourceState(std::shared_ptr<UniformRandomDistribution> _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<UniformRandomDistribution> uRandDist;
std::set<std::string> importedSources;
};
struct TestState struct TestState
{ {
explicit TestState(std::shared_ptr<UniformRandomDistribution> _urd): explicit TestState(std::shared_ptr<UniformRandomDistribution> _urd):
sourceUnitPaths({}), sourceUnitState({}),
currentSourceUnitPath({}), currentSourceUnitPath({}),
uRandDist(std::move(_urd)) uRandDist(std::move(_urd)),
numSourceUnits(0)
{} {}
/// Adds @param _path to @name sourceUnitPaths updates /// Adds @param _path to @name sourceUnitPaths updates
/// @name currentSourceUnitPath. /// @name currentSourceUnitPath.
void addSourceUnit(std::string const& _path) void addSourceUnit(std::string const& _path)
{ {
sourceUnitPaths.insert(_path); sourceUnitState.emplace(_path, std::make_shared<SourceState>(uRandDist));
currentSourceUnitPath = _path; currentSourceUnitPath = _path;
} }
/// @returns true if @name sourceUnitPaths is empty, /// Returns true if @name sourceUnitPaths is empty,
/// false otherwise. /// false otherwise.
[[nodiscard]] bool empty() const [[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 [[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. /// Prints test state to @param _os.
void print(std::ostream& _os) const; 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<std::string> const& _sourceUnitPaths) const; [[nodiscard]] std::string randomPath(std::set<std::string> const& _sourceUnitPaths) const;
/// @returns a randomly chosen path from @name sourceUnitPaths. [[nodiscard]] std::set<std::string> sourceUnitPaths() const;
/// Returns a randomly chosen path from @name sourceUnitPaths.
[[nodiscard]] std::string randomPath() const; [[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; [[nodiscard]] std::string randomNonCurrentPath() const;
/// List of source paths in test input. /// List of source paths in test input.
std::set<std::string> sourceUnitPaths; std::map<std::string, std::shared_ptr<SourceState>> sourceUnitState;
/// Source path being currently visited. /// Source path being currently visited.
std::string currentSourceUnitPath; std::string currentSourceUnitPath;
/// Uniform random distribution. /// Uniform random distribution.
std::shared_ptr<UniformRandomDistribution> uRandDist; std::shared_ptr<UniformRandomDistribution> uRandDist;
/// Number of source units in test input
size_t numSourceUnits;
/// String prefix of source unit names
std::string const sourceUnitNamePrefix = "su";
}; };
struct GeneratorBase struct GeneratorBase
@ -128,8 +210,8 @@ struct GeneratorBase
std::shared_ptr<T> generator() std::shared_ptr<T> generator()
{ {
for (auto& g: generators) for (auto& g: generators)
if (std::holds_alternative<std::shared_ptr<T>>(g)) if (std::holds_alternative<std::shared_ptr<T>>(g.first))
return std::get<std::shared_ptr<T>>(g); return std::get<std::shared_ptr<T>>(g.first);
solAssert(false, ""); solAssert(false, "");
} }
/// @returns test fragment created by this generator. /// @returns test fragment created by this generator.
@ -151,7 +233,7 @@ struct GeneratorBase
std::string visitChildren(); std::string visitChildren();
/// Adds generators for child grammar elements of /// Adds generators for child grammar elements of
/// this grammar element. /// this grammar element.
void addGenerators(std::set<GeneratorPtr> _generators) void addGenerators(std::set<std::pair<GeneratorPtr, unsigned>> _generators)
{ {
generators += _generators; generators += _generators;
} }
@ -168,7 +250,7 @@ struct GeneratorBase
/// Shared pointer to the mutator instance /// Shared pointer to the mutator instance
std::shared_ptr<SolidityGenerator> mutator; std::shared_ptr<SolidityGenerator> mutator;
/// Set of generators used by this generator. /// Set of generators used by this generator.
std::set<GeneratorPtr> generators; std::set<std::pair<GeneratorPtr, unsigned>> generators;
/// Shared ptr to global test state. /// Shared ptr to global test state.
std::shared_ptr<TestState> state; std::shared_ptr<TestState> state;
/// Uniform random distribution /// Uniform random distribution
@ -179,8 +261,7 @@ class TestCaseGenerator: public GeneratorBase
{ {
public: public:
explicit TestCaseGenerator(std::shared_ptr<SolidityGenerator> _mutator): explicit TestCaseGenerator(std::shared_ptr<SolidityGenerator> _mutator):
GeneratorBase(std::move(_mutator)), GeneratorBase(std::move(_mutator))
m_numSourceUnits(0)
{} {}
void setup() override; void setup() override;
std::string visit() override; std::string visit() override;
@ -221,6 +302,8 @@ public:
void setup() override; void setup() override;
std::string visit() override; std::string visit() override;
std::string name() override { return "Source unit generator"; } std::string name() override { return "Source unit generator"; }
private:
static unsigned constexpr s_maxImports = 2;
}; };
class PragmaGenerator: public GeneratorBase class PragmaGenerator: public GeneratorBase
@ -231,6 +314,15 @@ public:
{} {}
std::string visit() override; std::string visit() override;
std::string name() override { return "Pragma generator"; } std::string name() override { return "Pragma generator"; }
private:
std::set<std::string> const s_genericPragmas = {
R"(pragma solidity >= 0.0.0;)",
R"(pragma experimental SMTChecker;)",
};
std::vector<std::string> const s_abiPragmas = {
R"(pragma abicoder v1;)",
R"(pragma abicoder v2;)"
};
}; };
class ImportGenerator: public GeneratorBase class ImportGenerator: public GeneratorBase
@ -241,15 +333,6 @@ public:
{} {}
std::string visit() override; std::string visit() override;
std::string name() override { return "Import generator"; } 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>