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;
 | |
| };
 | |
| }
 |