Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libyul/optimiser/DataFlowAnalyzer.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
 * Base class to perform data flow analysis during AST walks.
20
 * Tracks assignments and is used as base class for both Rematerialiser and
21
 * Common Subexpression Eliminator.
22
 */
23
24
#pragma once
25
26
#include <libyul/optimiser/ASTWalker.h>
27
#include <libyul/optimiser/KnowledgeBase.h>
28
#include <libyul/YulName.h>
29
#include <libyul/AST.h> // Needed for m_zero below.
30
#include <libyul/SideEffects.h>
31
32
#include <libsolutil/Numeric.h>
33
#include <libsolutil/Common.h>
34
35
#include <map>
36
#include <set>
37
38
namespace solidity::yul
39
{
40
class Dialect;
41
struct SideEffects;
42
class KnowledgeBase;
43
44
/// Value assigned to a variable.
45
struct AssignedValue
46
{
47
  Expression const* value{nullptr};
48
  /// Loop nesting depth of the definition of the variable.
49
  size_t loopDepth{0};
50
};
51
52
/**
53
 * Base class to perform data flow analysis during AST walks.
54
 * Tracks assignments and is used as base class for both Rematerialiser and
55
 * Common Subexpression Eliminator.
56
 *
57
 * A special zero constant expression is used for the default value of variables.
58
 *
59
 * The class also tracks contents in storage and memory. Both keys and values
60
 * are names of variables. Whenever such a variable is re-assigned, the knowledge
61
 * is cleared.
62
 *
63
 * For elementary statements, we check if it is an SSTORE(x, y) / MSTORE(x, y)
64
 * If yes, visit the statement. Then record that fact and clear all storage slots t
65
 *   where we cannot prove x != t or y == m_storage[t] using the current values of the variables x and t.
66
 * Otherwise, determine if the statement invalidates storage/memory. If yes, clear all knowledge
67
 * about storage/memory before visiting the statement. Then visit the statement.
68
 *
69
 * For forward-joining control flow, storage/memory information from the branches is combined.
70
 * If the keys or values are different or non-existent in one branch, the key is deleted.
71
 * This works also for memory (where addresses overlap) because one branch is always an
72
 * older version of the other and thus overlapping contents would have been deleted already
73
 * at the point of assignment.
74
 *
75
 * The DataFlowAnalyzer currently does not deal with the ``leave`` statement. This is because
76
 * it only matters at the end of a function body, which is a point in the code a derived class
77
 * can not easily deal with.
78
 *
79
 * Prerequisite: Disambiguator, ForLoopInitRewriter.
80
 */
81
class DataFlowAnalyzer: public ASTModifier
82
{
83
public:
84
  enum class MemoryAndStorage { Analyze, Ignore };
85
  /// @param _functionSideEffects
86
  ///            Side-effects of user-defined functions. Worst-case side-effects are assumed
87
  ///            if this is not provided or the function is not found.
88
  ///            The parameter is mostly used to determine movability of expressions.
89
  explicit DataFlowAnalyzer(
90
    Dialect const& _dialect,
91
    MemoryAndStorage _analyzeStores,
92
    std::map<FunctionHandle, SideEffects> _functionSideEffects = {}
93
  );
94
95
  using ASTModifier::operator();
96
  void operator()(ExpressionStatement& _statement) override;
97
  void operator()(Assignment& _assignment) override;
98
  void operator()(VariableDeclaration& _varDecl) override;
99
  void operator()(If& _if) override;
100
  void operator()(Switch& _switch) override;
101
  void operator()(FunctionDefinition&) override;
102
  void operator()(ForLoop&) override;
103
  void operator()(Block& _block) override;
104
105
  /// @returns the current value of the given variable, if known - always movable.
106
228M
  AssignedValue const* variableValue(YulName _variable) const { return util::valueOrNullptr(m_state.value, _variable); }
107
458k
  std::vector<YulName> const* sortedReferences(YulName _variable) const { return util::valueOrNullptr(m_state.sortedReferences, _variable); }
108
0
  std::map<YulName, AssignedValue> const& allValues() const { return m_state.value; }
109
  std::optional<YulName> storageValue(YulName _key) const;
110
  std::optional<YulName> memoryValue(YulName _key) const;
111
  std::optional<YulName> keccakValue(YulName _start, YulName _length) const;
112
113
protected:
114
  /// Registers the assignment.
115
  void handleAssignment(std::set<YulName> const& _names, Expression* _value, bool _isDeclaration);
116
117
  /// Creates a new inner scope.
118
  void pushScope(bool _functionScope);
119
120
  /// Removes the innermost scope and clears all variables in it.
121
  void popScope();
122
123
  /// Clears information about the values assigned to the given variables,
124
  /// for example at points where control flow is merged.
125
  void clearValues(std::set<YulName> const& _variablesToClear);
126
127
  virtual void assignValue(YulName _variable, Expression const* _value);
128
129
  /// Clears knowledge about storage or memory if they may be modified inside the block.
130
  void clearKnowledgeIfInvalidated(Block const& _block);
131
132
  /// Clears knowledge about storage or memory if they may be modified inside the expression.
133
  void clearKnowledgeIfInvalidated(Expression const& _expression);
134
135
  /// Returns true iff the variable is in scope.
136
  bool inScope(YulName _variableName) const;
137
138
  /// Returns the literal value of the identifier, if it exists.
139
  std::optional<u256> valueOfIdentifier(YulName const& _name) const;
140
141
  enum class StoreLoadLocation {
142
    Memory = 0,
143
    Storage = 1,
144
    Last = Storage
145
  };
146
147
  /// Checks if the statement is sstore(a, b) / mstore(a, b)
148
  /// where a and b are variables and returns these variables in that case.
149
  std::optional<std::pair<YulName, YulName>> isSimpleStore(
150
    StoreLoadLocation _location,
151
    ExpressionStatement const& _statement
152
  ) const;
153
154
  /// Checks if the expression is sload(a) / mload(a)
155
  /// where a is a variable and returns the variable in that case.
156
  std::optional<YulName> isSimpleLoad(
157
    StoreLoadLocation _location,
158
    Expression const& _expression
159
  ) const;
160
161
  /// Checks if the expression is keccak256(s, l)
162
  /// where s and l are variables and returns these variables in that case.
163
  std::optional<std::pair<YulName, YulName>> isKeccak(Expression const& _expression) const;
164
165
  Dialect const& m_dialect;
166
  /// Side-effects of user-defined functions. Worst-case side-effects are assumed
167
  /// if this is not provided or the function is not found.
168
  std::map<FunctionHandle, SideEffects> m_functionSideEffects;
169
170
private:
171
  struct Environment
172
  {
173
    std::unordered_map<YulName, YulName> storage;
174
    std::unordered_map<YulName, YulName> memory;
175
    /// If keccak[s, l] = y then y := keccak256(s, l) occurs in the code.
176
    std::map<std::pair<YulName, YulName>, YulName> keccak;
177
  };
178
  struct State
179
  {
180
    /// Current values of variables, always movable.
181
    std::map<YulName, AssignedValue> value;
182
    /// m_references[a].contains(b) <=> the current expression assigned to a references b
183
    /// The mapped vectors _must always_ be sorted
184
    std::unordered_map<YulName, std::vector<YulName>> sortedReferences;
185
186
    Environment environment;
187
  };
188
189
  /// Joins knowledge about storage and memory with an older point in the control-flow.
190
  /// This only works if the current state is a direct successor of the older point,
191
  /// i.e. `_olderState.storage` and `_olderState.memory` cannot have additional changes.
192
  /// Does nothing if memory and storage analysis is disabled / ignored.
193
  void joinKnowledge(Environment const& _olderEnvironment);
194
195
  static void joinKnowledgeHelper(
196
    std::unordered_map<YulName, YulName>& _thisData,
197
    std::unordered_map<YulName, YulName> const& _olderData
198
  );
199
200
  State m_state;
201
202
protected:
203
  KnowledgeBase m_knowledgeBase;
204
205
  /// If true, analyzes memory and storage content via mload/mstore and sload/sstore.
206
  bool m_analyzeStores = true;
207
  std::optional<BuiltinHandle> m_storeFunctionName[static_cast<unsigned>(StoreLoadLocation::Last) + 1];
208
  std::optional<BuiltinHandle> m_loadFunctionName[static_cast<unsigned>(StoreLoadLocation::Last) + 1];
209
210
  /// Current nesting depth of loops.
211
  size_t m_loopDepth{0};
212
213
  struct Scope
214
  {
215
32.2M
    explicit Scope(bool _isFunction): isFunction(_isFunction) {}
216
    std::set<YulName> variables;
217
    bool isFunction;
218
  };
219
  /// Special expression whose address will be used in m_value.
220
  /// YulName does not need to be reset because DataFlowAnalyzer is short-lived.
221
  Expression const m_zero{Literal{{}, LiteralKind::Number, LiteralValue{0, std::nullopt}}};
222
  /// List of scopes.
223
  std::vector<Scope> m_variableScopes;
224
};
225
226
}