mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Solidity fuzzer: Add simple import statements and test/source state.
This commit is contained in:
parent
98e7b61a37
commit
fa9328cb0e
@ -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);
|
||||
|
@ -21,6 +21,12 @@
|
||||
#include <libsolutil/Whiskers.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::util;
|
||||
using namespace std;
|
||||
@ -36,17 +42,32 @@ string GeneratorBase::visitChildren()
|
||||
{
|
||||
ostringstream os;
|
||||
// Randomise visit order
|
||||
vector<GeneratorPtr> randomisedChildren;
|
||||
vector<std::pair<GeneratorPtr, unsigned>> 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<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
|
||||
{
|
||||
auto it = _sourceUnitPaths.begin();
|
||||
@ -63,14 +84,17 @@ string TestState::randomPath(set<string> 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<string> filteredSourcePaths;
|
||||
string currentPath = currentSourceUnitPath;
|
||||
set<string> 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<SourceUnitGenerator>()
|
||||
{mutator->generator<SourceUnitGenerator>(), 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<ImportGenerator>(),
|
||||
mutator->generator<PragmaGenerator>()
|
||||
{mutator->generator<ImportGenerator>(), s_maxImports},
|
||||
{mutator->generator<PragmaGenerator>(), 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<string> 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();
|
||||
}
|
||||
|
@ -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 <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;
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
explicit TestState(std::shared_ptr<UniformRandomDistribution> _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<SourceState>(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<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;
|
||||
/// @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<std::string> sourceUnitPaths;
|
||||
std::map<std::string, std::shared_ptr<SourceState>> sourceUnitState;
|
||||
/// Source path being currently visited.
|
||||
std::string currentSourceUnitPath;
|
||||
/// Uniform random distribution.
|
||||
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
|
||||
@ -128,8 +210,8 @@ struct GeneratorBase
|
||||
std::shared_ptr<T> generator()
|
||||
{
|
||||
for (auto& g: generators)
|
||||
if (std::holds_alternative<std::shared_ptr<T>>(g))
|
||||
return std::get<std::shared_ptr<T>>(g);
|
||||
if (std::holds_alternative<std::shared_ptr<T>>(g.first))
|
||||
return std::get<std::shared_ptr<T>>(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<GeneratorPtr> _generators)
|
||||
void addGenerators(std::set<std::pair<GeneratorPtr, unsigned>> _generators)
|
||||
{
|
||||
generators += _generators;
|
||||
}
|
||||
@ -168,7 +250,7 @@ struct GeneratorBase
|
||||
/// Shared pointer to the mutator instance
|
||||
std::shared_ptr<SolidityGenerator> mutator;
|
||||
/// Set of generators used by this generator.
|
||||
std::set<GeneratorPtr> generators;
|
||||
std::set<std::pair<GeneratorPtr, unsigned>> generators;
|
||||
/// Shared ptr to global test state.
|
||||
std::shared_ptr<TestState> state;
|
||||
/// Uniform random distribution
|
||||
@ -179,8 +261,7 @@ class TestCaseGenerator: public GeneratorBase
|
||||
{
|
||||
public:
|
||||
explicit TestCaseGenerator(std::shared_ptr<SolidityGenerator> _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<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
|
||||
@ -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<SolidityGenerator>
|
||||
|
Loading…
Reference in New Issue
Block a user