|
| 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/FunctionCallGraph.h> |
| 20 | + |
| 21 | +#include <libsolutil/StringUtils.h> |
| 22 | + |
| 23 | +#include <range/v3/range/conversion.hpp> |
| 24 | +#include <range/v3/view/reverse.hpp> |
| 25 | +#include <range/v3/view/transform.hpp> |
| 26 | + |
| 27 | +using namespace std; |
| 28 | +using namespace ranges; |
| 29 | +using namespace solidity::frontend; |
| 30 | +using namespace solidity::util; |
| 31 | + |
| 32 | +bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const |
| 33 | +{ |
| 34 | + if (_lhs.index() != _rhs.index()) |
| 35 | + return _lhs.index() < _rhs.index(); |
| 36 | + |
| 37 | + if (holds_alternative<SpecialNode>(_lhs)) |
| 38 | + return get<SpecialNode>(_lhs) < get<SpecialNode>(_rhs); |
| 39 | + return get<CallableDeclaration const*>(_lhs)->id() < get<CallableDeclaration const*>(_rhs)->id(); |
| 40 | +} |
| 41 | + |
| 42 | +bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const |
| 43 | +{ |
| 44 | + solAssert(!holds_alternative<SpecialNode>(_lhs), ""); |
| 45 | + |
| 46 | + return get<CallableDeclaration const*>(_lhs)->id() < _rhs; |
| 47 | +} |
| 48 | + |
| 49 | +bool FunctionCallGraphBuilder::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const |
| 50 | +{ |
| 51 | + solAssert(!holds_alternative<SpecialNode>(_rhs), ""); |
| 52 | + |
| 53 | + return _lhs < get<CallableDeclaration const*>(_rhs)->id(); |
| 54 | +} |
| 55 | + |
| 56 | +FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildCreationGraph(ContractDefinition const& _contract) |
| 57 | +{ |
| 58 | + FunctionCallGraphBuilder builder(_contract); |
| 59 | + solAssert(builder.m_currentNode == Node(SpecialNode::Entry), ""); |
| 60 | + |
| 61 | + // Create graph for constructor, state vars, etc |
| 62 | + for (ContractDefinition const* base: _contract.annotation().linearizedBaseContracts | views::reverse) |
| 63 | + { |
| 64 | + // The constructor and functions called in state variable initial assignments should have |
| 65 | + // an edge from Entry |
| 66 | + builder.m_currentNode = SpecialNode::Entry; |
| 67 | + for (auto const* stateVar: base->stateVariables()) |
| 68 | + stateVar->accept(builder); |
| 69 | + |
| 70 | + if (base->constructor()) |
| 71 | + { |
| 72 | + builder.functionReferenced(*base->constructor()); |
| 73 | + |
| 74 | + // Constructors and functions called in state variable initializers have an edge either from |
| 75 | + // the previous class in linearized order or from Entry if there's no class before. |
| 76 | + builder.m_currentNode = base->constructor(); |
| 77 | + } |
| 78 | + |
| 79 | + // Functions called from the inheritance specifier should have an edge from the constructor |
| 80 | + // for consistency with functions called from constructor modifiers. |
| 81 | + for (auto const& inheritanceSpecifier: base->baseContracts()) |
| 82 | + inheritanceSpecifier->accept(builder); |
| 83 | + } |
| 84 | + |
| 85 | + builder.m_currentNode = SpecialNode::Entry; |
| 86 | + builder.processQueue(); |
| 87 | + |
| 88 | + return move(builder.m_graph); |
| 89 | +} |
| 90 | + |
| 91 | +FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildDeployedGraph( |
| 92 | + ContractDefinition const& _contract, |
| 93 | + FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph |
| 94 | +) |
| 95 | +{ |
| 96 | + solAssert(&_creationGraph.contract == &_contract, ""); |
| 97 | + |
| 98 | + FunctionCallGraphBuilder builder(_contract); |
| 99 | + solAssert(builder.m_currentNode == Node(SpecialNode::Entry), ""); |
| 100 | + |
| 101 | + auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); }; |
| 102 | + |
| 103 | + // Create graph for all publicly reachable functions |
| 104 | + for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | views::transform(getSecondElement)) |
| 105 | + { |
| 106 | + auto const* function = dynamic_cast<FunctionDefinition const*>(&functionType->declaration()); |
| 107 | + auto const* variable = dynamic_cast<VariableDeclaration const*>(&functionType->declaration()); |
| 108 | + |
| 109 | + if (function) |
| 110 | + builder.functionReferenced(*function); |
| 111 | + else |
| 112 | + // If it's not a function, it must be a getter of a public variable; we ignore those |
| 113 | + solAssert(variable, ""); |
| 114 | + } |
| 115 | + |
| 116 | + if (_contract.fallbackFunction()) |
| 117 | + builder.functionReferenced(*_contract.fallbackFunction()); |
| 118 | + |
| 119 | + if (_contract.receiveFunction()) |
| 120 | + builder.functionReferenced(*_contract.receiveFunction()); |
| 121 | + |
| 122 | + // All functions present in internal dispatch at creation time could potentially be pointers |
| 123 | + // assigned to state variables and as such may be reachable after deployment as well. |
| 124 | + builder.m_currentNode = SpecialNode::InternalDispatch; |
| 125 | + for (Node const& dispatchTarget: valueOrDefault(_creationGraph.edges, SpecialNode::InternalDispatch, {})) |
| 126 | + { |
| 127 | + solAssert(!holds_alternative<SpecialNode>(dispatchTarget), ""); |
| 128 | + solAssert(get<CallableDeclaration const*>(dispatchTarget) != nullptr, ""); |
| 129 | + |
| 130 | + // Visit the callable to add not only it but also everything it calls too |
| 131 | + builder.functionReferenced(*get<CallableDeclaration const*>(dispatchTarget), false); |
| 132 | + } |
| 133 | + |
| 134 | + builder.m_currentNode = SpecialNode::Entry; |
| 135 | + builder.processQueue(); |
| 136 | + |
| 137 | + return move(builder.m_graph); |
| 138 | +} |
| 139 | + |
| 140 | +bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall) |
| 141 | +{ |
| 142 | + if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) |
| 143 | + return true; |
| 144 | + |
| 145 | + auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type); |
| 146 | + solAssert(functionType, ""); |
| 147 | + |
| 148 | + if (functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly) |
| 149 | + // If it's not a direct call, we don't really know which function will be called (it may even |
| 150 | + // change at runtime). All we can do is to add an edge to the dispatch which in turn has |
| 151 | + // edges to all functions could possibly be called. |
| 152 | + add(m_currentNode, SpecialNode::InternalDispatch); |
| 153 | + |
| 154 | + return true; |
| 155 | +} |
| 156 | + |
| 157 | +bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement) |
| 158 | +{ |
| 159 | + auto const* functionType = dynamic_cast<FunctionType const*>(_emitStatement.eventCall().expression().annotation().type); |
| 160 | + solAssert(functionType, ""); |
| 161 | + |
| 162 | + m_graph.emittedEvents.insert(&dynamic_cast<EventDefinition const&>(functionType->declaration())); |
| 163 | + |
| 164 | + return true; |
| 165 | +} |
| 166 | + |
| 167 | +bool FunctionCallGraphBuilder::visit(Identifier const& _identifier) |
| 168 | +{ |
| 169 | + if (auto const* callable = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration)) |
| 170 | + { |
| 171 | + solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, ""); |
| 172 | + |
| 173 | + auto funType = dynamic_cast<FunctionType const*>(_identifier.annotation().type); |
| 174 | + |
| 175 | + // For events kind() == Event, so we have an extra check here |
| 176 | + if (funType && funType->kind() == FunctionType::Kind::Internal) |
| 177 | + functionReferenced(callable->resolveVirtual(m_graph.contract), _identifier.annotation().calledDirectly); |
| 178 | + } |
| 179 | + |
| 180 | + return true; |
| 181 | +} |
| 182 | + |
| 183 | +bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess) |
| 184 | +{ |
| 185 | + auto functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type); |
| 186 | + auto functionDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration); |
| 187 | + if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal) |
| 188 | + return true; |
| 189 | + |
| 190 | + // Super functions |
| 191 | + if (*_memberAccess.annotation().requiredLookup == VirtualLookup::Super) |
| 192 | + { |
| 193 | + if (auto const* typeType = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type)) |
| 194 | + if (auto const contractType = dynamic_cast<ContractType const*>(typeType->actualType())) |
| 195 | + { |
| 196 | + solAssert(contractType->isSuper(), ""); |
| 197 | + functionDef = &functionDef->resolveVirtual( |
| 198 | + m_graph.contract, |
| 199 | + contractType->contractDefinition().superContract(m_graph.contract) |
| 200 | + ); |
| 201 | + } |
| 202 | + } |
| 203 | + else |
| 204 | + solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); |
| 205 | + |
| 206 | + functionReferenced(*functionDef, _memberAccess.annotation().calledDirectly); |
| 207 | + |
| 208 | + return true; |
| 209 | +} |
| 210 | + |
| 211 | +bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation) |
| 212 | +{ |
| 213 | + if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration)) |
| 214 | + { |
| 215 | + VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup; |
| 216 | + |
| 217 | + if (requiredLookup == VirtualLookup::Virtual) |
| 218 | + functionReferenced(modifier->resolveVirtual(m_graph.contract)); |
| 219 | + else |
| 220 | + { |
| 221 | + solAssert(requiredLookup == VirtualLookup::Static, ""); |
| 222 | + functionReferenced(*modifier); |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + return true; |
| 227 | +} |
| 228 | + |
| 229 | +bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression) |
| 230 | +{ |
| 231 | + if (ContractType const* contractType = dynamic_cast<ContractType const*>(_newExpression.typeName().annotation().type)) |
| 232 | + m_graph.createdContracts.emplace(&contractType->contractDefinition()); |
| 233 | + |
| 234 | + return true; |
| 235 | +} |
| 236 | + |
| 237 | +void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable) |
| 238 | +{ |
| 239 | + if (!m_graph.edges.count(&_callable)) |
| 240 | + { |
| 241 | + m_visitQueue.push_back(&_callable); |
| 242 | + |
| 243 | + // Insert the callable to the graph (with no edges coming out of it) to mark it as visited. |
| 244 | + m_graph.edges.insert({Node(&_callable), {}}); |
| 245 | + } |
| 246 | +} |
| 247 | + |
| 248 | +void FunctionCallGraphBuilder::processQueue() |
| 249 | +{ |
| 250 | + solAssert(m_currentNode == Node(SpecialNode::Entry), "Visit queue is already being processed."); |
| 251 | + |
| 252 | + while (!m_visitQueue.empty()) |
| 253 | + { |
| 254 | + m_currentNode = m_visitQueue.front(); |
| 255 | + solAssert(holds_alternative<CallableDeclaration const*>(m_currentNode), ""); |
| 256 | + |
| 257 | + m_visitQueue.pop_front(); |
| 258 | + get<CallableDeclaration const*>(m_currentNode)->accept(*this); |
| 259 | + } |
| 260 | + |
| 261 | + m_currentNode = SpecialNode::Entry; |
| 262 | +} |
| 263 | + |
| 264 | +void FunctionCallGraphBuilder::add(Node _caller, Node _callee) |
| 265 | +{ |
| 266 | + m_graph.edges[_caller].insert(_callee); |
| 267 | +} |
| 268 | + |
| 269 | +void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly) |
| 270 | +{ |
| 271 | + if (_calledDirectly) |
| 272 | + { |
| 273 | + solAssert( |
| 274 | + holds_alternative<SpecialNode>(m_currentNode) || m_graph.edges.count(m_currentNode) > 0, |
| 275 | + "Adding an edge from a node that has not been visited yet." |
| 276 | + ); |
| 277 | + |
| 278 | + add(m_currentNode, &_callable); |
| 279 | + } |
| 280 | + else |
| 281 | + add(SpecialNode::InternalDispatch, &_callable); |
| 282 | + |
| 283 | + enqueueCallable(_callable); |
| 284 | +} |
| 285 | + |
| 286 | +ostream& solidity::frontend::operator<<(ostream& _out, FunctionCallGraphBuilder::Node const& _node) |
| 287 | +{ |
| 288 | + using SpecialNode = FunctionCallGraphBuilder::SpecialNode; |
| 289 | + |
| 290 | + if (holds_alternative<SpecialNode>(_node)) |
| 291 | + switch (get<SpecialNode>(_node)) |
| 292 | + { |
| 293 | + case SpecialNode::InternalDispatch: |
| 294 | + _out << "InternalDispatch"; |
| 295 | + break; |
| 296 | + case SpecialNode::Entry: |
| 297 | + _out << "Entry"; |
| 298 | + break; |
| 299 | + default: solAssert(false, "Invalid SpecialNode type"); |
| 300 | + } |
| 301 | + else |
| 302 | + { |
| 303 | + solAssert(holds_alternative<CallableDeclaration const*>(_node), ""); |
| 304 | + |
| 305 | + auto const* callableDeclaration = get<CallableDeclaration const*>(_node); |
| 306 | + solAssert(callableDeclaration, ""); |
| 307 | + |
| 308 | + auto const* function = dynamic_cast<FunctionDefinition const *>(callableDeclaration); |
| 309 | + auto const* event = dynamic_cast<EventDefinition const *>(callableDeclaration); |
| 310 | + auto const* modifier = dynamic_cast<ModifierDefinition const *>(callableDeclaration); |
| 311 | + |
| 312 | + auto typeToString = [](auto const& _var) -> string { return _var->type()->toString(true); }; |
| 313 | + vector<string> parameters = callableDeclaration->parameters() | views::transform(typeToString) | to<vector<string>>(); |
| 314 | + |
| 315 | + string scopeName; |
| 316 | + if (!function || !function->isFree()) |
| 317 | + { |
| 318 | + solAssert(callableDeclaration->annotation().scope, ""); |
| 319 | + auto const* parentContract = dynamic_cast<ContractDefinition const*>(callableDeclaration->annotation().scope); |
| 320 | + solAssert(parentContract, ""); |
| 321 | + scopeName = parentContract->name(); |
| 322 | + } |
| 323 | + |
| 324 | + if (function && function->isFree()) |
| 325 | + _out << "function " << function->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
| 326 | + else if (function && function->isConstructor()) |
| 327 | + _out << "constructor of " << scopeName; |
| 328 | + else if (function && function->isFallback()) |
| 329 | + _out << "fallback of " << scopeName; |
| 330 | + else if (function && function->isReceive()) |
| 331 | + _out << "receive of " << scopeName; |
| 332 | + else if (function) |
| 333 | + _out << "function " << scopeName << "." << function->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
| 334 | + else if (event) |
| 335 | + _out << "event " << scopeName << "." << event->name() << "(" << joinHumanReadable(parameters, ",") << ")"; |
| 336 | + else if (modifier) |
| 337 | + _out << "modifier " << scopeName << "." << modifier->name(); |
| 338 | + else |
| 339 | + solAssert(false, "Unexpected AST node type in function call graph"); |
| 340 | + } |
| 341 | + |
| 342 | + return _out; |
| 343 | +} |
0 commit comments