Coverage Report

Created: 2025-09-04 07:34

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