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