Add visitors for automatic test case generation.

This commit is contained in:
Bhargava Shastry 2021-01-21 17:53:22 +01:00
parent 2f2d2224b1
commit d7e3d3f75f
4 changed files with 320 additions and 11 deletions

View File

@ -0,0 +1,32 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Convenience macros for Solidity generator type declarations.
*/
#pragma once
/*
* Alphabetically sorted Generator types.
* SEP must appear between two elements and ENDSEP must
* appear after the last element.
*/
#define GENERATORLIST(MACRO, SEP, ENDSEP) \
MACRO(PragmaGenerator) SEP \
MACRO(SourceUnitGenerator) SEP \
MACRO(TestCaseGenerator) ENDSEP

View File

@ -27,7 +27,8 @@ using namespace solidity::test::fuzzer;
// Prototype as we can't use the FuzzerInterface.h header. // Prototype as we can't use the FuzzerInterface.h header.
extern "C" size_t LLVMFuzzerMutate(uint8_t* _data, size_t _size, size_t _maxSize); extern "C" size_t LLVMFuzzerMutate(uint8_t* _data, size_t _size, size_t _maxSize);
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* _data, size_t size, size_t _maxSize, unsigned int seed); extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* _data, size_t size, size_t _maxSize, unsigned int seed);
namespace
{
/// Define Solidity's custom mutator by implementing libFuzzer's /// Define Solidity's custom mutator by implementing libFuzzer's
/// custom mutator external interface. /// custom mutator external interface.
extern "C" size_t LLVMFuzzerCustomMutator( extern "C" size_t LLVMFuzzerCustomMutator(
@ -37,10 +38,12 @@ extern "C" size_t LLVMFuzzerCustomMutator(
unsigned int _seed unsigned int _seed
) )
{ {
solAssert(_data, "libFuzzerInterface: libFuzzer supplied bad buffer");
if (_maxSize <= _size || _size == 0) if (_maxSize <= _size || _size == 0)
return LLVMFuzzerMutate(_data, _size, _maxSize); return LLVMFuzzerMutate(_data, _size, _maxSize);
return SolidityCustomMutatorInterface{_data, _size, _maxSize, _seed}.generate(); return SolidityCustomMutatorInterface{_data, _size, _maxSize, _seed}.generate();
} }
}
SolidityCustomMutatorInterface::SolidityCustomMutatorInterface( SolidityCustomMutatorInterface::SolidityCustomMutatorInterface(
uint8_t* _data, uint8_t* _data,

View File

@ -23,13 +23,120 @@
using namespace solidity::test::fuzzer; using namespace solidity::test::fuzzer;
using namespace solidity::util; using namespace solidity::util;
using namespace std; using namespace std;
using PrngUtil = solidity::test::fuzzer::GenerationProbability;
GeneratorBase::GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator)
{
mutator = std::move(_mutator);
rand = mutator->randomEngine();
}
string GeneratorBase::visitChildren()
{
ostringstream os;
// Randomise visit order
vector<GeneratorPtr> randomisedChildren;
for (auto child: generators)
randomisedChildren.push_back(child);
shuffle(randomisedChildren.begin(), randomisedChildren.end(), *rand);
for (auto child: randomisedChildren)
os << std::visit(GeneratorVisitor{}, child);
return os.str();
}
void TestCaseGenerator::setup()
{
addGenerators(
{
mutator->generator<SourceUnitGenerator>()
}
);
}
string TestCaseGenerator::visit()
{
ostringstream os;
for (unsigned i = 0; i < PrngUtil{}.distributionOneToN(s_maxSourceUnits, rand); i++)
{
string sourcePath = path();
os << "\n"
<< "==== Source: "
<< sourcePath
<< " ===="
<< "\n";
m_numSourceUnits++;
os << visitChildren();
generator<SourceUnitGenerator>()->reset();
}
return os.str();
}
void SourceUnitGenerator::setup()
{
addGenerators(
{
mutator->generator<PragmaGenerator>(),
}
);
}
string SourceUnitGenerator::visit()
{
string sourceUnit = visitChildren();
reset();
return sourceUnit;
}
void SourceUnitGenerator::reset()
{
for (auto& g: generators)
std::visit(ResetVisitor{}, g);
}
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(PrngUtil{}.distributionOneToN(2, rand)) +
";\n";
return preamble + abiPragma;
}
template <typename T>
shared_ptr<T> SolidityGenerator::generator()
{
for (auto& g: m_generators)
if (holds_alternative<shared_ptr<T>>(g))
return get<shared_ptr<T>>(g);
solAssert(false, "");
}
SolidityGenerator::SolidityGenerator(unsigned _seed)
{
m_rand = make_shared<RandomEngine>(_seed);
m_generators = {};
}
template <size_t I>
void SolidityGenerator::createGenerators()
{
if constexpr (I < std::variant_size_v<Generator>)
{
createGenerator<std::variant_alternative_t<I, Generator>>();
createGenerators<I + 1>();
}
}
string SolidityGenerator::generateTestProgram() string SolidityGenerator::generateTestProgram()
{ {
// TODO: Add generators for grammar elements of createGenerators();
// Solidity antlr4 grammar. Currently, the generated for (auto& g: m_generators)
// test program consists of a version pragma only. std::visit(AddDependenciesVisitor{}, g);
return Whiskers(R"(pragma <directive>;)") string program = generator<TestCaseGenerator>()->visit();
("directive", "solidity >= 0.0.0") destroyGenerators();
.render(); return program;
} }

View File

@ -22,21 +22,188 @@
#pragma once #pragma once
#include <test/tools/ossfuzz/Generators.h>
#include <liblangutil/Exceptions.h>
#include <memory>
#include <random> #include <random>
#include <set>
#include <variant>
namespace solidity::test::fuzzer namespace solidity::test::fuzzer
{ {
using RandomEngine = std::mt19937_64; /// Forward declarations
class SolidityGenerator;
class SolidityGenerator /// Type declarations
#define SEMICOLON() ;
#define FORWARDDECLAREGENERATORS(G) class G
GENERATORLIST(FORWARDDECLAREGENERATORS, SEMICOLON(), SEMICOLON())
#undef FORWARDDECLAREGENERATORS
#undef SEMICOLON
#define COMMA() ,
using GeneratorPtr = std::variant<
#define VARIANTOFSHARED(G) std::shared_ptr<G>
GENERATORLIST(VARIANTOFSHARED, COMMA(), )
>;
#undef VARIANTOFSHARED
using Generator = std::variant<
#define VARIANTOFGENERATOR(G) G
GENERATORLIST(VARIANTOFGENERATOR, COMMA(), )
>;
#undef VARIANTOFGENERATOR
#undef COMMA
using RandomEngine = std::mt19937_64;
using Distribution = std::uniform_int_distribution<size_t>;
struct GenerationProbability
{
static size_t distributionOneToN(size_t _n, std::shared_ptr<RandomEngine> _rand)
{
return Distribution(1, _n)(*_rand);
}
};
struct AddDependenciesVisitor
{
template <typename T>
void operator()(T const& _t)
{
_t->setup();
}
};
struct GeneratorVisitor
{
template <typename T>
std::string operator()(T const& _t)
{
return _t->visit();
}
};
struct ResetVisitor
{
template <typename T>
void operator()(T const& _t)
{
_t->reset();
}
};
struct GeneratorBase
{
GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator);
template <typename T>
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);
solAssert(false, "");
}
/// Generator
virtual std::string visit() = 0;
std::string visitChildren();
void addGenerators(std::set<GeneratorPtr> _generators)
{
for (auto& g: _generators)
generators.insert(g);
}
virtual void reset() = 0;
virtual std::string name() = 0;
virtual void setup() = 0;
virtual ~GeneratorBase()
{
generators.clear();
}
std::shared_ptr<SolidityGenerator> mutator;
/// Random engine shared by Solidity mutators
std::shared_ptr<RandomEngine> rand;
std::set<GeneratorPtr> generators;
};
class TestCaseGenerator: public GeneratorBase
{ {
public: public:
SolidityGenerator(uint64_t _seed): m_rand(_seed) TestCaseGenerator(std::shared_ptr<SolidityGenerator> _mutator):
GeneratorBase(std::move(_mutator)),
m_numSourceUnits(0)
{} {}
void setup() override;
void reset() override {}
std::string visit() override;
std::string name() override
{
return "Test case generator";
}
private:
std::string path() const
{
return m_sourceUnitNamePrefix + std::to_string(m_numSourceUnits) + ".sol";
}
size_t m_numSourceUnits;
std::string const m_sourceUnitNamePrefix = "su";
static constexpr unsigned s_maxSourceUnits = 3;
};
class SourceUnitGenerator: public GeneratorBase
{
public:
SourceUnitGenerator(std::shared_ptr<SolidityGenerator> _mutator):
GeneratorBase(std::move(_mutator))
{}
void setup() override;
std::string visit() override;
void reset() override;
std::string name() override { return "Source unit generator"; }
};
class PragmaGenerator: public GeneratorBase
{
public:
PragmaGenerator(std::shared_ptr<SolidityGenerator> _mutator):
GeneratorBase(std::move(_mutator))
{}
void setup() override {}
void reset() override {}
std::string visit() override;
std::string name() override { return "Pragma generator"; }
};
class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator>
{
public:
explicit SolidityGenerator(unsigned _seed);
std::string visit();
template <typename T>
std::shared_ptr<T> generator();
std::shared_ptr<RandomEngine> randomEngine()
{
return m_rand;
}
/// @returns a pseudo randomly generated test program /// @returns a pseudo randomly generated test program
std::string generateTestProgram(); std::string generateTestProgram();
private: private:
template <typename T>
void createGenerator()
{
m_generators.insert(
std::make_shared<T>(shared_from_this())
);
}
template <std::size_t I = 0>
void createGenerators();
void destroyGenerators()
{
m_generators.clear();
}
/// Random number generator /// Random number generator
RandomEngine const m_rand; std::shared_ptr<RandomEngine> m_rand;
/// Sub generators
std::set<GeneratorPtr> m_generators;
}; };
} }