/*
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 .
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Implements generators for synthesizing mostly syntactically valid
* Solidity test programs.
*/
#pragma once
#include
#include
#include
#include
#include
#include
namespace solidity::test::fuzzer
{
/// Forward declarations
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
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;
struct UniformRandomDistribution
{
explicit UniformRandomDistribution(std::unique_ptr _randomEngine):
randomEngine(std::move(_randomEngine))
{}
/// @returns an unsigned integer in the range [1, @param _n] chosen
/// uniformly at random.
[[nodiscard]] size_t distributionOneToN(size_t _n) const
{
return Distribution(1, _n)(*randomEngine);
}
/// @returns true with a probability of 1/(@param _n), false otherwise.
/// @param _n must be non zero.
[[nodiscard]] bool probable(size_t _n) const
{
solAssert(_n > 0, "");
return distributionOneToN(_n) == 1;
}
std::unique_ptr randomEngine;
};
struct TestState
{
explicit TestState(std::shared_ptr _urd):
sourceUnitPaths({}),
currentSourceUnitPath({}),
uRandDist(std::move(_urd))
{}
/// Adds @param _path to @name sourceUnitPaths updates
/// @name currentSourceUnitPath.
void addSourceUnit(std::string const& _path)
{
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 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 sourceUnitPaths;
/// Source path being currently visited.
std::string currentSourceUnitPath;
/// Uniform random distribution.
std::shared_ptr uRandDist;
};
struct GeneratorBase
{
explicit GeneratorBase(std::shared_ptr _mutator);
template
std::shared_ptr generator()
{
for (auto& g: generators)
if (std::holds_alternative>(g))
return std::get>(g);
solAssert(false, "");
}
/// @returns test fragment created by this generator.
std::string generate()
{
std::string generatedCode = visit();
endVisit();
return generatedCode;
}
/// @returns a string representing the generation of
/// the Solidity grammar element.
virtual std::string visit() = 0;
/// Method called after visiting this generator. Used
/// for clearing state if necessary.
virtual void endVisit() {}
/// Visitor that invokes child grammar elements of
/// this grammar element returning their string
/// representations.
std::string visitChildren();
/// Adds generators for child grammar elements of
/// this grammar element.
void addGenerators(std::set _generators)
{
generators += _generators;
}
/// Virtual method to obtain string name of generator.
virtual std::string name() = 0;
/// Virtual method to add generators that this grammar
/// element depends on. If not overridden, there are
/// no dependencies.
virtual void setup() {}
virtual ~GeneratorBase()
{
generators.clear();
}
/// Shared pointer to the mutator instance
std::shared_ptr mutator;
/// Set of generators used by this generator.
std::set generators;
/// Shared ptr to global test state.
std::shared_ptr state;
/// Uniform random distribution
std::shared_ptr uRandDist;
};
class TestCaseGenerator: public GeneratorBase
{
public:
explicit TestCaseGenerator(std::shared_ptr _mutator):
GeneratorBase(std::move(_mutator)),
m_numSourceUnits(0)
{}
void setup() override;
std::string visit() override;
std::string name() override
{
return "Test case generator";
}
private:
/// @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 path() const
{
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
size_t m_numSourceUnits;
/// String prefix of source unit names
std::string const m_sourceUnitNamePrefix = "su";
/// Maximum number of source units per test input
static constexpr unsigned s_maxSourceUnits = 3;
};
class SourceUnitGenerator: public GeneratorBase
{
public:
explicit SourceUnitGenerator(std::shared_ptr _mutator):
GeneratorBase(std::move(_mutator))
{}
void setup() override;
std::string visit() override;
std::string name() override { return "Source unit generator"; }
};
class PragmaGenerator: public GeneratorBase
{
public:
explicit PragmaGenerator(std::shared_ptr _mutator):
GeneratorBase(std::move(_mutator))
{}
std::string visit() override;
std::string name() override { return "Pragma generator"; }
};
class ImportGenerator: public GeneratorBase
{
public:
explicit ImportGenerator(std::shared_ptr _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
{
public:
explicit SolidityGenerator(unsigned _seed);
/// @returns the generator of type @param T.
template
std::shared_ptr generator();
/// @returns a shared ptr to underlying random
/// number distribution.
std::shared_ptr uniformRandomDist()
{
return m_urd;
}
/// @returns a pseudo randomly generated test case.
std::string generateTestProgram();
/// @returns shared ptr to global test state.
std::shared_ptr testState()
{
return m_state;
}
private:
template
void createGenerator()
{
m_generators.insert(
std::make_shared(shared_from_this())
);
}
template
void createGenerators();
void destroyGenerators()
{
m_generators.clear();
}
/// Sub generators
std::set m_generators;
/// Shared global test state
std::shared_ptr m_state;
/// Uniform random distribution
std::shared_ptr m_urd;
};
}