Coverage Report

Created: 2026-10-26 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsolidity/analysis/ControlFlowAnalyzer.cpp
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
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
20
21
#include <liblangutil/SourceLocation.h>
22
#include <libsolutil/Algorithms.h>
23
24
#include <range/v3/algorithm/sort.hpp>
25
26
#include <functional>
27
28
using namespace std::placeholders;
29
using namespace solidity::langutil;
30
using namespace solidity::frontend;
31
32
33
bool ControlFlowAnalyzer::run()
34
16.0k
{
35
16.0k
  for (auto& [pair, flow]: m_cfg.allFunctionFlows())
36
22.6k
    analyze(*pair.function, pair.contract, *flow);
37
38
16.0k
  return !Error::containsErrors(m_errorReporter.errors());
39
16.0k
}
40
41
void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow)
42
22.6k
{
43
22.6k
  if (!_function.isImplemented())
44
0
    return;
45
46
22.6k
  std::optional<std::string> mostDerivedContractName;
47
48
  // The name of the most derived contract only required if it differs from
49
  // the functions contract
50
22.6k
  if (_contract && _contract != _function.annotation().contract)
51
1.34k
    mostDerivedContractName = _contract->name();
52
53
22.6k
  checkUninitializedAccess(
54
22.6k
    _flow.entry,
55
22.6k
    _flow.exit,
56
22.6k
    _function.body().statements().empty(),
57
22.6k
    mostDerivedContractName
58
22.6k
  );
59
22.6k
  checkUnreachable(_flow.entry, _flow.exit, _flow.revert, _flow.transactionReturn);
60
22.6k
}
61
62
63
void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody, std::optional<std::string> _contractName)
64
22.6k
{
65
22.6k
  struct NodeInfo
66
22.6k
  {
67
22.6k
    std::set<VariableDeclaration const*> unassignedVariablesAtEntry;
68
22.6k
    std::set<VariableDeclaration const*> unassignedVariablesAtExit;
69
22.6k
    std::set<VariableOccurrence const*> uninitializedVariableAccesses;
70
    /// Propagate the information from another node to this node.
71
    /// To be used to propagate information from a node to its exit nodes.
72
    /// Returns true, if new variables were added and thus the current node has
73
    /// to be traversed again.
74
22.6k
    bool propagateFrom(NodeInfo const& _entryNode)
75
338k
    {
76
338k
      size_t previousUnassignedVariablesAtEntry = unassignedVariablesAtEntry.size();
77
338k
      size_t previousUninitializedVariableAccesses = uninitializedVariableAccesses.size();
78
338k
      unassignedVariablesAtEntry += _entryNode.unassignedVariablesAtExit;
79
338k
      uninitializedVariableAccesses += _entryNode.uninitializedVariableAccesses;
80
338k
      return
81
338k
        unassignedVariablesAtEntry.size() > previousUnassignedVariablesAtEntry ||
82
136k
        uninitializedVariableAccesses.size() > previousUninitializedVariableAccesses
83
338k
      ;
84
338k
    }
85
22.6k
  };
86
22.6k
  std::map<CFGNode const*, NodeInfo> nodeInfos;
87
22.6k
  std::set<CFGNode const*> nodesToTraverse;
88
22.6k
  nodesToTraverse.insert(_entry);
89
90
  // Walk all paths starting from the nodes in ``nodesToTraverse`` until ``NodeInfo::propagateFrom``
91
  // returns false for all exits, i.e. until all paths have been walked with maximal sets of unassigned
92
  // variables and accesses.
93
289k
  while (!nodesToTraverse.empty())
94
266k
  {
95
266k
    CFGNode const* currentNode = *nodesToTraverse.begin();
96
266k
    nodesToTraverse.erase(nodesToTraverse.begin());
97
98
266k
    auto& nodeInfo = nodeInfos[currentNode];
99
266k
    auto unassignedVariables = nodeInfo.unassignedVariablesAtEntry;
100
266k
    for (auto const& variableOccurrence: currentNode->variableOccurrences)
101
376k
    {
102
376k
      switch (variableOccurrence.kind())
103
376k
      {
104
133k
        case VariableOccurrence::Kind::Assignment:
105
133k
          unassignedVariables.erase(&variableOccurrence.declaration());
106
133k
          break;
107
0
        case VariableOccurrence::Kind::InlineAssembly:
108
          // We consider all variables referenced in inline assembly as accessed.
109
          // So far any reference is enough, but we might want to actually analyze
110
          // the control flow in the assembly at some point.
111
180k
        case VariableOccurrence::Kind::Access:
112
199k
        case VariableOccurrence::Kind::Return:
113
199k
          if (unassignedVariables.count(&variableOccurrence.declaration()))
114
30.2k
          {
115
            // Merely store the unassigned access. We do not generate an error right away, since this
116
            // path might still always revert. It is only an error if this is propagated to the exit
117
            // node of the function (i.e. there is a path with an uninitialized access).
118
30.2k
            nodeInfo.uninitializedVariableAccesses.insert(&variableOccurrence);
119
30.2k
          }
120
199k
          break;
121
43.1k
        case VariableOccurrence::Kind::Declaration:
122
43.1k
          unassignedVariables.insert(&variableOccurrence.declaration());
123
43.1k
          break;
124
376k
      }
125
376k
    }
126
266k
    nodeInfo.unassignedVariablesAtExit = std::move(unassignedVariables);
127
128
    // Propagate changes to all exits and queue them for traversal, if needed.
129
266k
    for (auto const& exit: currentNode->exits)
130
338k
      if (
131
338k
        auto exists = util::valueOrNullptr(nodeInfos, exit);
132
338k
        nodeInfos[exit].propagateFrom(nodeInfo) || !exists
133
338k
      )
134
246k
        nodesToTraverse.insert(exit);
135
266k
  }
136
137
22.6k
  auto const& exitInfo = nodeInfos[_exit];
138
22.6k
  if (!exitInfo.uninitializedVariableAccesses.empty())
139
2.67k
  {
140
2.67k
    std::vector<VariableOccurrence const*> uninitializedAccessesOrdered(
141
2.67k
      exitInfo.uninitializedVariableAccesses.begin(),
142
2.67k
      exitInfo.uninitializedVariableAccesses.end()
143
2.67k
    );
144
2.67k
    ranges::sort(
145
2.67k
      uninitializedAccessesOrdered,
146
2.67k
      [](VariableOccurrence const* lhs, VariableOccurrence const* rhs) -> bool
147
206k
      {
148
206k
        return *lhs < *rhs;
149
206k
      }
150
2.67k
    );
151
152
2.67k
    for (auto const* variableOccurrence: uninitializedAccessesOrdered)
153
20.8k
    {
154
20.8k
      VariableDeclaration const& varDecl = variableOccurrence->declaration();
155
156
20.8k
      SecondarySourceLocation ssl;
157
20.8k
      if (variableOccurrence->occurrence())
158
17.3k
        ssl.append("The variable was declared here.", varDecl.location());
159
160
20.8k
      bool isStorage = varDecl.type()->dataStoredIn(DataLocation::Storage);
161
20.8k
      bool isCalldata = varDecl.type()->dataStoredIn(DataLocation::CallData);
162
20.8k
      if (isStorage || isCalldata)
163
25
        m_errorReporter.typeError(
164
25
          3464_error,
165
25
          variableOccurrence->occurrence() ?
166
10
            *variableOccurrence->occurrence() :
167
25
            varDecl.location(),
168
25
          ssl,
169
25
          "This variable is of " +
170
25
          std::string(isStorage ? "storage" : "calldata") +
171
25
          " pointer type and can be " +
172
25
          (variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") +
173
25
          " without prior assignment, which would lead to undefined behaviour."
174
25
        );
175
20.7k
      else if (!_emptyBody && varDecl.name().empty())
176
1.11k
      {
177
1.11k
        if (!m_unassignedReturnVarsAlreadyWarnedFor.emplace(&varDecl).second)
178
22
          continue;
179
180
1.09k
        m_errorReporter.warning(
181
1.09k
          6321_error,
182
1.09k
          varDecl.location(),
183
1.09k
          "Unnamed return variable can remain unassigned" +
184
1.09k
          (
185
1.09k
            _contractName.has_value() ?
186
2
            " when the function is called when \"" + _contractName.value() + "\" is the most derived contract." :
187
1.09k
            "."
188
1.09k
          ) +
189
1.09k
          " Add an explicit return with value to all non-reverting code paths or name the variable."
190
1.09k
        );
191
1.09k
      }
192
20.8k
    }
193
2.67k
  }
194
22.6k
}
195
196
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn)
197
22.6k
{
198
  // collect all nodes reachable from the entry point
199
22.6k
  std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
200
254k
    [](CFGNode const* _node, auto&& _addChild) {
201
254k
      for (CFGNode const* exit: _node->exits)
202
321k
        _addChild(exit);
203
254k
    }
204
22.6k
  ).visited;
205
206
  // traverse all paths backwards from exit, revert and transaction return
207
  // and extract (valid) source locations of unreachable nodes into sorted set
208
22.6k
  std::set<SourceLocation> unreachable;
209
22.6k
  util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
210
389k
    [&](CFGNode const* _node, auto&& _addChild) {
211
389k
      if (!reachable.count(_node) && _node->location.isValid())
212
2.70k
        unreachable.insert(_node->location);
213
389k
      for (CFGNode const* entry: _node->entries)
214
414k
        _addChild(entry);
215
389k
    }
216
22.6k
  );
217
218
24.1k
  for (auto it = unreachable.begin(); it != unreachable.end();)
219
1.46k
  {
220
1.46k
    SourceLocation location = *it++;
221
    // Extend the location, as long as the next location overlaps (unreachable is sorted).
222
2.60k
    for (; it != unreachable.end() && it->start <= location.end; ++it)
223
1.13k
      location.end = std::max(location.end, it->end);
224
225
1.46k
    if (m_unreachableLocationsAlreadyWarnedFor.emplace(location).second)
226
1.46k
      m_errorReporter.warning(5740_error, location, "Unreachable code.");
227
1.46k
  }
228
22.6k
}