/src/solidity/test/tools/ossfuzz/SolidityGenerator.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * Implements generators for synthesizing mostly syntactically valid |
20 | | * Solidity test programs. |
21 | | */ |
22 | | |
23 | | #pragma once |
24 | | |
25 | | #include <test/tools/ossfuzz/Generators.h> |
26 | | |
27 | | #include <liblangutil/Exceptions.h> |
28 | | |
29 | | #include <memory> |
30 | | #include <random> |
31 | | #include <set> |
32 | | #include <variant> |
33 | | |
34 | | namespace solidity::test::fuzzer::mutator |
35 | | { |
36 | | /// Forward declarations |
37 | | class SolidityGenerator; |
38 | | |
39 | | /// Type declarations |
40 | | #define SEMICOLON() ; |
41 | | #define FORWARDDECLAREGENERATORS(G) class G |
42 | | GENERATORLIST(FORWARDDECLAREGENERATORS, SEMICOLON(), SEMICOLON()) |
43 | | #undef FORWARDDECLAREGENERATORS |
44 | | #undef SEMICOLON |
45 | | |
46 | | #define COMMA() , |
47 | | using GeneratorPtr = std::variant< |
48 | | #define VARIANTOFSHARED(G) std::shared_ptr<G> |
49 | | GENERATORLIST(VARIANTOFSHARED, COMMA(), ) |
50 | | >; |
51 | | #undef VARIANTOFSHARED |
52 | | using Generator = std::variant< |
53 | | #define VARIANTOFGENERATOR(G) G |
54 | | GENERATORLIST(VARIANTOFGENERATOR, COMMA(), ) |
55 | | >; |
56 | | #undef VARIANTOFGENERATOR |
57 | | #undef COMMA |
58 | | using RandomEngine = std::mt19937_64; |
59 | | using Distribution = std::uniform_int_distribution<size_t>; |
60 | | |
61 | | struct UniformRandomDistribution |
62 | | { |
63 | | explicit UniformRandomDistribution(std::unique_ptr<RandomEngine> _randomEngine): |
64 | | randomEngine(std::move(_randomEngine)) |
65 | 0 | {} |
66 | | |
67 | | /// @returns an unsigned integer in the range [1, @param _n] chosen |
68 | | /// uniformly at random. |
69 | | [[nodiscard]] size_t distributionOneToN(size_t _n) const |
70 | 0 | { |
71 | 0 | return Distribution(1, _n)(*randomEngine); |
72 | 0 | } |
73 | | /// @returns true with a probability of 1/(@param _n), false otherwise. |
74 | | /// @param _n must be non zero. |
75 | | [[nodiscard]] bool probable(size_t _n) const |
76 | 0 | { |
77 | 0 | solAssert(_n > 0, ""); |
78 | 0 | return distributionOneToN(_n) == 1; |
79 | 0 | } |
80 | | std::unique_ptr<RandomEngine> randomEngine; |
81 | | }; |
82 | | |
83 | | struct TestState |
84 | | { |
85 | | explicit TestState(std::shared_ptr<UniformRandomDistribution> _urd): |
86 | | sourceUnitPaths({}), |
87 | | currentSourceUnitPath({}), |
88 | | uRandDist(std::move(_urd)) |
89 | 0 | {} |
90 | | /// Adds @param _path to @name sourceUnitPaths updates |
91 | | /// @name currentSourceUnitPath. |
92 | | void addSourceUnit(std::string const& _path) |
93 | 0 | { |
94 | 0 | sourceUnitPaths.insert(_path); |
95 | 0 | currentSourceUnitPath = _path; |
96 | 0 | } |
97 | | /// @returns true if @name sourceUnitPaths is empty, |
98 | | /// false otherwise. |
99 | | [[nodiscard]] bool empty() const |
100 | 0 | { |
101 | 0 | return sourceUnitPaths.empty(); |
102 | 0 | } |
103 | | /// @returns the number of items in @name sourceUnitPaths. |
104 | | [[nodiscard]] size_t size() const |
105 | 0 | { |
106 | 0 | return sourceUnitPaths.size(); |
107 | 0 | } |
108 | | /// Prints test state to @param _os. |
109 | | void print(std::ostream& _os) const; |
110 | | /// @returns a randomly chosen path from @param _sourceUnitPaths. |
111 | | [[nodiscard]] std::string randomPath(std::set<std::string> const& _sourceUnitPaths) const; |
112 | | /// @returns a randomly chosen path from @name sourceUnitPaths. |
113 | | [[nodiscard]] std::string randomPath() const; |
114 | | /// @returns a randomly chosen non current source unit path. |
115 | | [[nodiscard]] std::string randomNonCurrentPath() const; |
116 | | /// List of source paths in test input. |
117 | | std::set<std::string> sourceUnitPaths; |
118 | | /// Source path being currently visited. |
119 | | std::string currentSourceUnitPath; |
120 | | /// Uniform random distribution. |
121 | | std::shared_ptr<UniformRandomDistribution> uRandDist; |
122 | | }; |
123 | | |
124 | | struct GeneratorBase |
125 | | { |
126 | | explicit GeneratorBase(std::shared_ptr<SolidityGenerator> _mutator); |
127 | | template <typename T> |
128 | | std::shared_ptr<T> generator() |
129 | | { |
130 | | for (auto& g: generators) |
131 | | if (std::holds_alternative<std::shared_ptr<T>>(g)) |
132 | | return std::get<std::shared_ptr<T>>(g); |
133 | | solAssert(false, ""); |
134 | | } |
135 | | /// @returns test fragment created by this generator. |
136 | | std::string generate() |
137 | 0 | { |
138 | 0 | std::string generatedCode = visit(); |
139 | 0 | endVisit(); |
140 | 0 | return generatedCode; |
141 | 0 | } |
142 | | /// @returns a string representing the generation of |
143 | | /// the Solidity grammar element. |
144 | | virtual std::string visit() = 0; |
145 | | /// Method called after visiting this generator. Used |
146 | | /// for clearing state if necessary. |
147 | 0 | virtual void endVisit() {} |
148 | | /// Visitor that invokes child grammar elements of |
149 | | /// this grammar element returning their string |
150 | | /// representations. |
151 | | std::string visitChildren(); |
152 | | /// Adds generators for child grammar elements of |
153 | | /// this grammar element. |
154 | | void addGenerators(std::set<GeneratorPtr> _generators) |
155 | 0 | { |
156 | 0 | generators += _generators; |
157 | 0 | } |
158 | | /// Virtual method to obtain string name of generator. |
159 | | virtual std::string name() = 0; |
160 | | /// Virtual method to add generators that this grammar |
161 | | /// element depends on. If not overridden, there are |
162 | | /// no dependencies. |
163 | 0 | virtual void setup() {} |
164 | | virtual ~GeneratorBase() |
165 | 0 | { |
166 | 0 | generators.clear(); |
167 | 0 | } |
168 | | /// Shared pointer to the mutator instance |
169 | | std::shared_ptr<SolidityGenerator> mutator; |
170 | | /// Set of generators used by this generator. |
171 | | std::set<GeneratorPtr> generators; |
172 | | /// Shared ptr to global test state. |
173 | | std::shared_ptr<TestState> state; |
174 | | /// Uniform random distribution |
175 | | std::shared_ptr<UniformRandomDistribution> uRandDist; |
176 | | }; |
177 | | |
178 | | class TestCaseGenerator: public GeneratorBase |
179 | | { |
180 | | public: |
181 | | explicit TestCaseGenerator(std::shared_ptr<SolidityGenerator> _mutator): |
182 | | GeneratorBase(std::move(_mutator)), |
183 | | m_numSourceUnits(0) |
184 | 0 | {} |
185 | | void setup() override; |
186 | | std::string visit() override; |
187 | | std::string name() override |
188 | 0 | { |
189 | 0 | return "Test case generator"; |
190 | 0 | } |
191 | | private: |
192 | | /// @returns a new source path name that is formed by concatenating |
193 | | /// a static prefix @name m_sourceUnitNamePrefix, a monotonically |
194 | | /// increasing counter starting from 0 and the postfix (extension) |
195 | | /// ".sol". |
196 | | [[nodiscard]] std::string path() const |
197 | 0 | { |
198 | 0 | return m_sourceUnitNamePrefix + std::to_string(m_numSourceUnits) + ".sol"; |
199 | 0 | } |
200 | | /// Adds @param _path to list of source paths in global test |
201 | | /// state and increments @name m_numSourceUnits. |
202 | | void updateSourcePath(std::string const& _path) |
203 | 0 | { |
204 | 0 | state->addSourceUnit(_path); |
205 | 0 | m_numSourceUnits++; |
206 | 0 | } |
207 | | /// Number of source units in test input |
208 | | size_t m_numSourceUnits; |
209 | | /// String prefix of source unit names |
210 | | std::string const m_sourceUnitNamePrefix = "su"; |
211 | | /// Maximum number of source units per test input |
212 | | static constexpr unsigned s_maxSourceUnits = 3; |
213 | | }; |
214 | | |
215 | | class SourceUnitGenerator: public GeneratorBase |
216 | | { |
217 | | public: |
218 | | explicit SourceUnitGenerator(std::shared_ptr<SolidityGenerator> _mutator): |
219 | | GeneratorBase(std::move(_mutator)) |
220 | 0 | {} |
221 | | void setup() override; |
222 | | std::string visit() override; |
223 | 0 | std::string name() override { return "Source unit generator"; } |
224 | | }; |
225 | | |
226 | | class PragmaGenerator: public GeneratorBase |
227 | | { |
228 | | public: |
229 | | explicit PragmaGenerator(std::shared_ptr<SolidityGenerator> _mutator): |
230 | | GeneratorBase(std::move(_mutator)) |
231 | 0 | {} |
232 | | std::string visit() override; |
233 | 0 | std::string name() override { return "Pragma generator"; } |
234 | | }; |
235 | | |
236 | | class ImportGenerator: public GeneratorBase |
237 | | { |
238 | | public: |
239 | | explicit ImportGenerator(std::shared_ptr<SolidityGenerator> _mutator): |
240 | | GeneratorBase(std::move(_mutator)) |
241 | 0 | {} |
242 | | std::string visit() override; |
243 | 0 | std::string name() override { return "Import generator"; } |
244 | | private: |
245 | | /// Inverse probability with which a source unit |
246 | | /// imports itself. Keeping this at 17 seems to |
247 | | /// produce self imported source units with a |
248 | | /// frequency small enough so that it does not |
249 | | /// consume too many fuzzing cycles but large |
250 | | /// enough so that the fuzzer generates self |
251 | | /// import statements every once in a while. |
252 | | static constexpr size_t s_selfImportInvProb = 17; |
253 | | }; |
254 | | |
255 | | class SolidityGenerator: public std::enable_shared_from_this<SolidityGenerator> |
256 | | { |
257 | | public: |
258 | | explicit SolidityGenerator(unsigned _seed); |
259 | | |
260 | | /// @returns the generator of type @param T. |
261 | | template <typename T> |
262 | | std::shared_ptr<T> generator(); |
263 | | /// @returns a shared ptr to underlying random |
264 | | /// number distribution. |
265 | | std::shared_ptr<UniformRandomDistribution> uniformRandomDist() |
266 | 0 | { |
267 | 0 | return m_urd; |
268 | 0 | } |
269 | | /// @returns a pseudo randomly generated test case. |
270 | | std::string generateTestProgram(); |
271 | | /// @returns shared ptr to global test state. |
272 | | std::shared_ptr<TestState> testState() |
273 | 0 | { |
274 | 0 | return m_state; |
275 | 0 | } |
276 | | private: |
277 | | template <typename T> |
278 | | void createGenerator() |
279 | 0 | { |
280 | 0 | m_generators.insert( |
281 | 0 | std::make_shared<T>(shared_from_this()) |
282 | 0 | ); |
283 | 0 | } Unexecuted instantiation: void solidity::test::fuzzer::mutator::SolidityGenerator::createGenerator<solidity::test::fuzzer::mutator::ImportGenerator>() Unexecuted instantiation: void solidity::test::fuzzer::mutator::SolidityGenerator::createGenerator<solidity::test::fuzzer::mutator::PragmaGenerator>() Unexecuted instantiation: void solidity::test::fuzzer::mutator::SolidityGenerator::createGenerator<solidity::test::fuzzer::mutator::SourceUnitGenerator>() Unexecuted instantiation: void solidity::test::fuzzer::mutator::SolidityGenerator::createGenerator<solidity::test::fuzzer::mutator::TestCaseGenerator>() |
284 | | template <std::size_t I = 0> |
285 | | void createGenerators(); |
286 | | void destroyGenerators() |
287 | 0 | { |
288 | 0 | m_generators.clear(); |
289 | 0 | } |
290 | | /// Sub generators |
291 | | std::set<GeneratorPtr> m_generators; |
292 | | /// Shared global test state |
293 | | std::shared_ptr<TestState> m_state; |
294 | | /// Uniform random distribution |
295 | | std::shared_ptr<UniformRandomDistribution> m_urd; |
296 | | }; |
297 | | } |