mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add visitors for automatic test case generation.
This commit is contained in:
parent
2f2d2224b1
commit
d7e3d3f75f
32
test/tools/ossfuzz/Generators.h
Normal file
32
test/tools/ossfuzz/Generators.h
Normal 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
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user