Coverage Report

Created: 2026-07-13 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/Semantics.h
Line
Count
Source
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
 * Specific AST walkers that collect semantical facts.
20
 */
21
22
#pragma once
23
24
#include <libyul/AST.h>
25
#include <libyul/Object.h>
26
#include <libyul/SideEffects.h>
27
#include <libyul/optimiser/ASTWalker.h>
28
#include <libyul/optimiser/CallGraphGenerator.h>
29
30
#include <set>
31
32
namespace solidity::yul
33
{
34
class Dialect;
35
36
/**
37
 * Specific AST walker that determines side-effect free-ness and movability of code.
38
 * Enters into function definitions.
39
 */
40
class SideEffectsCollector: public ASTWalker
41
{
42
public:
43
  explicit SideEffectsCollector(
44
    Dialect const& _dialect,
45
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr
46
427M
  ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {}
47
  SideEffectsCollector(
48
    Dialect const& _dialect,
49
    Expression const& _expression,
50
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr
51
  );
52
  SideEffectsCollector(Dialect const& _dialect, Statement const& _statement);
53
  SideEffectsCollector(
54
    Dialect const& _dialect,
55
    Block const& _ast,
56
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr
57
  );
58
  SideEffectsCollector(
59
    Dialect const& _dialect,
60
    ForLoop const& _ast,
61
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr
62
  );
63
64
  using ASTWalker::operator();
65
  void operator()(FunctionCall const& _functionCall) override;
66
67
263M
  bool movable() const { return m_sideEffects.movable; }
68
69
  bool movableRelativeTo(SideEffects const& _other, bool _codeContainsMSize)
70
2.93M
  {
71
2.93M
    if (!m_sideEffects.cannotLoop)
72
1.88k
      return false;
73
74
2.93M
    if (m_sideEffects.movable)
75
2.65M
      return true;
76
77
279k
    if (
78
279k
      !m_sideEffects.movableApartFromEffects ||
79
194k
      m_sideEffects.storage == SideEffects::Write ||
80
194k
      m_sideEffects.otherState == SideEffects::Write ||
81
194k
      m_sideEffects.memory == SideEffects::Write ||
82
194k
      m_sideEffects.transientStorage == SideEffects::Write
83
279k
    )
84
85.1k
      return false;
85
86
194k
    if (m_sideEffects.otherState == SideEffects::Read)
87
97.7k
      if (_other.otherState == SideEffects::Write)
88
5.43k
        return false;
89
90
188k
    if (m_sideEffects.storage == SideEffects::Read)
91
53.0k
      if (_other.storage == SideEffects::Write)
92
45.2k
        return false;
93
94
143k
    if (m_sideEffects.memory == SideEffects::Read)
95
34.6k
      if (_codeContainsMSize || _other.memory == SideEffects::Write)
96
24.7k
        return false;
97
98
118k
    if (m_sideEffects.transientStorage == SideEffects::Read)
99
8.82k
      if (_other.transientStorage == SideEffects::Write)
100
4.96k
        return false;
101
102
114k
    return true;
103
118k
  }
104
105
  bool canBeRemoved(bool _allowMSizeModification = false) const
106
34.0M
  {
107
34.0M
    if (_allowMSizeModification)
108
30.2M
      return m_sideEffects.canBeRemovedIfNoMSize;
109
3.76M
    else
110
3.76M
      return m_sideEffects.canBeRemoved;
111
34.0M
  }
112
0
  bool cannotLoop() const { return m_sideEffects.cannotLoop; }
113
35.5M
  bool invalidatesStorage() const { return m_sideEffects.storage == SideEffects::Write; }
114
35.5M
  bool invalidatesMemory() const { return m_sideEffects.memory == SideEffects::Write; }
115
116
722k
  SideEffects sideEffects() { return m_sideEffects; }
117
118
private:
119
  Dialect const& m_dialect;
120
  std::map<FunctionHandle, SideEffects> const* m_functionSideEffects = nullptr;
121
  SideEffects m_sideEffects;
122
};
123
124
/**
125
 * This class can be used to determine the side-effects of user-defined functions.
126
 *
127
 * It is given a dialect and a mapping that represents the direct calls from user-defined
128
 * functions to other user-defined functions and built-in functions.
129
 */
130
class SideEffectsPropagator
131
{
132
public:
133
  static std::map<FunctionHandle, SideEffects> sideEffects(
134
    Dialect const& _dialect,
135
    CallGraph const& _directCallGraph
136
  );
137
};
138
139
/**
140
 * Class that can be used to find out if certain code contains the MSize instruction
141
 * or a verbatim bytecode builtin (which is always assumed that it could contain MSize).
142
 *
143
 * Note that this is a purely syntactic property meaning that even if this is false,
144
 * the code can still contain calls to functions that contain the msize instruction.
145
 *
146
 * The only safe way to determine this is by passing the full AST.
147
 */
148
class MSizeFinder: public ASTWalker
149
{
150
public:
151
  static bool containsMSize(Dialect const& _dialect, Block const& _ast);
152
  static bool containsMSize(Object const& _object);
153
154
  using ASTWalker::operator();
155
  void operator()(FunctionCall const& _funCall) override;
156
157
private:
158
2.14M
  MSizeFinder(Dialect const& _dialect): m_dialect(_dialect) {}
159
  Dialect const& m_dialect;
160
  bool m_msizeFound = false;
161
};
162
163
/**
164
 * Class that can be used to find out if the given function contains the ``leave`` statement.
165
 *
166
 * Returns true even in the case where the function definition contains another function definition
167
 * that contains the leave statement.
168
 */
169
class LeaveFinder: public ASTWalker
170
{
171
public:
172
  static bool containsLeave(FunctionDefinition const& _fun)
173
582k
  {
174
582k
    LeaveFinder f;
175
582k
    f(_fun);
176
582k
    return f.m_leaveFound;
177
582k
  }
178
179
  using ASTWalker::operator();
180
76.9k
  void operator()(Leave const&) override { m_leaveFound = true; }
181
182
private:
183
582k
  LeaveFinder() = default;
184
185
  bool m_leaveFound = false;
186
};
187
188
/**
189
 * Specific AST walker that determines whether an expression is movable
190
 * and collects the referenced variables.
191
 * Can only be used on expressions.
192
 */
193
class MovableChecker: public SideEffectsCollector
194
{
195
public:
196
  explicit MovableChecker(
197
    Dialect const& _dialect,
198
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr
199
350M
  ): SideEffectsCollector(_dialect, _functionSideEffects) {}
200
  MovableChecker(Dialect const& _dialect, Expression const& _expression);
201
202
  void operator()(Identifier const& _identifier) override;
203
204
  /// Disallow visiting anything apart from Expressions (this throws).
205
  void visit(Statement const&) override;
206
  using ASTWalker::visit;
207
208
850M
  std::set<YulName> const& referencedVariables() const { return m_variableReferences; }
209
210
private:
211
  /// Which variables the current expression references.
212
  std::set<YulName> m_variableReferences;
213
};
214
215
struct ControlFlowSideEffects;
216
217
/**
218
 * Helper class to find "irregular" control flow.
219
 * This includes termination, break, continue and leave.
220
 * In general, it is applied only to "simple" statements. The control-flow
221
 * of loops, switches and if statements is always "FlowOut" with the assumption
222
 * that the caller will descend into them.
223
 */
224
class TerminationFinder
225
{
226
public:
227
  /// "Terminate" here means that there is no continuing control-flow.
228
  /// If this is applied to a function that can revert or stop, but can also
229
  /// exit regularly, the property is set to "FlowOut".
230
  enum class ControlFlow { FlowOut, Break, Continue, Terminate, Leave };
231
232
  TerminationFinder(
233
    Dialect const& _dialect,
234
    std::map<YulName, ControlFlowSideEffects> const* _functionSideEffects = nullptr
235
8.73M
  ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {}
236
237
  /// @returns the index of the first statement in the provided sequence
238
  /// that is an unconditional ``break``, ``continue``, ``leave`` or a
239
  /// call to a terminating function.
240
  /// If control flow can continue at the end of the list,
241
  /// returns `FlowOut` and ``size_t(-1)``.
242
  /// The function might return ``FlowOut`` even though control
243
  /// flow cannot actually continue.
244
  std::pair<ControlFlow, size_t> firstUnconditionalControlFlowChange(
245
    std::vector<Statement> const& _statements
246
  );
247
248
  /// @returns the control flow type of the given statement.
249
  /// This function could return FlowOut even if control flow never continues.
250
  ControlFlow controlFlowKind(Statement const& _statement);
251
252
  /// @returns true if the expression contains a
253
  /// call to a terminating function, i.e. a function that does not have
254
  /// a regular "flow out" control-flow (it might also be recursive).
255
  bool containsNonContinuingFunctionCall(Expression const& _expr);
256
257
private:
258
  Dialect const& m_dialect;
259
  std::map<YulName, ControlFlowSideEffects> const* m_functionSideEffects;
260
};
261
262
}