mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Co-authored-by: Leonardo <leo@ethereum.org> Co-authored-by: Daniel Kirchner <daniel@ekpyron.org>
		
			
				
	
	
		
			298 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
	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
 | 
						|
/**
 | 
						|
 * Implements generators for synthesizing mostly syntactically valid
 | 
						|
 * Solidity test programs.
 | 
						|
 */
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include <test/tools/ossfuzz/Generators.h>
 | 
						|
 | 
						|
#include <liblangutil/Exceptions.h>
 | 
						|
 | 
						|
#include <memory>
 | 
						|
#include <random>
 | 
						|
#include <set>
 | 
						|
#include <variant>
 | 
						|
 | 
						|
namespace solidity::test::fuzzer::mutator
 | 
						|
{
 | 
						|
/// 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<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 UniformRandomDistribution
 | 
						|
{
 | 
						|
	explicit UniformRandomDistribution(std::unique_ptr<RandomEngine> _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> randomEngine;
 | 
						|
};
 | 
						|
 | 
						|
struct TestState
 | 
						|
{
 | 
						|
	explicit TestState(std::shared_ptr<UniformRandomDistribution> _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<std::string> 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<std::string> sourceUnitPaths;
 | 
						|
	/// Source path being currently visited.
 | 
						|
	std::string currentSourceUnitPath;
 | 
						|
	/// Uniform random distribution.
 | 
						|
	std::shared_ptr<UniformRandomDistribution> uRandDist;
 | 
						|
};
 | 
						|
 | 
						|
struct GeneratorBase
 | 
						|
{
 | 
						|
	explicit 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, "");
 | 
						|
	}
 | 
						|
	/// @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<GeneratorPtr> _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<SolidityGenerator> mutator;
 | 
						|
	/// Set of generators used by this generator.
 | 
						|
	std::set<GeneratorPtr> generators;
 | 
						|
	/// Shared ptr to global test state.
 | 
						|
	std::shared_ptr<TestState> state;
 | 
						|
	/// Uniform random distribution
 | 
						|
	std::shared_ptr<UniformRandomDistribution> uRandDist;
 | 
						|
};
 | 
						|
 | 
						|
class TestCaseGenerator: public GeneratorBase
 | 
						|
{
 | 
						|
public:
 | 
						|
	explicit TestCaseGenerator(std::shared_ptr<SolidityGenerator> _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<SolidityGenerator> _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<SolidityGenerator> _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<SolidityGenerator> _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<SolidityGenerator>
 | 
						|
{
 | 
						|
public:
 | 
						|
	explicit SolidityGenerator(unsigned _seed);
 | 
						|
 | 
						|
	/// @returns the generator of type @param T.
 | 
						|
	template <typename T>
 | 
						|
	std::shared_ptr<T> generator();
 | 
						|
	/// @returns a shared ptr to underlying random
 | 
						|
	/// number distribution.
 | 
						|
	std::shared_ptr<UniformRandomDistribution> 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> testState()
 | 
						|
	{
 | 
						|
		return m_state;
 | 
						|
	}
 | 
						|
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();
 | 
						|
	}
 | 
						|
	/// Sub generators
 | 
						|
	std::set<GeneratorPtr> m_generators;
 | 
						|
	/// Shared global test state
 | 
						|
	std::shared_ptr<TestState> m_state;
 | 
						|
	/// Uniform random distribution
 | 
						|
	std::shared_ptr<UniformRandomDistribution> m_urd;
 | 
						|
};
 | 
						|
}
 |