/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ // SPDX-License-Identifier: GPL-3.0 #include #include #include using namespace std; using namespace ranges; using namespace solidity::frontend; FunctionCallGraphBuilder::FunctionCallGraphBuilder(ContractDefinition const& _contract): m_contract(&_contract), m_graph(make_unique(_contract)) { // Create graph for constructor, state vars, etc m_currentNode = SpecialNode::EntryCreation; m_currentDispatch = SpecialNode::InternalCreationDispatch; for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts | views::reverse) { for (auto const* stateVar: contract->stateVariables()) stateVar->accept(*this); for (auto arg: contract->baseContracts()) arg->accept(*this); if (contract->constructor()) { add(*m_currentNode, contract->constructor()); contract->constructor()->accept(*this); m_currentNode = contract->constructor(); } } m_currentNode.reset(); m_currentDispatch = SpecialNode::InternalDispatch; auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); }; // Create graph for all publicly reachable functions for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | views::transform(getSecondElement)) { if (auto const* funcDef = dynamic_cast(&functionType->declaration())) if (!m_graph->edges.count(funcDef)) visitCallable(funcDef); // Add all external functions to the RuntimeDispatch add(SpecialNode::Entry, &functionType->declaration()); } // Add all InternalCreationDispatch calls to the InternalDispatch as well add(SpecialNode::InternalDispatch, SpecialNode::InternalCreationDispatch); if (_contract.fallbackFunction()) add(SpecialNode::Entry, _contract.fallbackFunction()); if (_contract.receiveFunction()) add(SpecialNode::Entry, _contract.receiveFunction()); } bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const { if (_lhs.index() != _rhs.index()) return _lhs.index() < _rhs.index(); if (holds_alternative(_lhs)) return get(_lhs) < get(_rhs); return get(_lhs)->id() < get(_rhs)->id(); } bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const { solAssert(!holds_alternative(_lhs), ""); return get(_lhs)->id() < _rhs; } bool FunctionCallGraphBuilder::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const { solAssert(!holds_alternative(_rhs), ""); return _lhs < get(_rhs)->id(); } unique_ptr FunctionCallGraphBuilder::create(ContractDefinition const& _contract) { return FunctionCallGraphBuilder(_contract).m_graph; } bool FunctionCallGraphBuilder::visit(Identifier const& _identifier) { if (auto const* callable = dynamic_cast(_identifier.annotation().referencedDeclaration)) { solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, ""); auto funType = dynamic_cast(_identifier.annotation().type); // For events kind() == Event, so we have an extra check here if (funType && funType->kind() == FunctionType::Kind::Internal) { processFunction(callable->resolveVirtual(*m_contract), _identifier.annotation().calledDirectly); solAssert(m_currentNode.has_value(), ""); } } return true; } bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression) { if (ContractType const* contractType = dynamic_cast(_newExpression.typeName().annotation().type)) m_graph->createdContracts.emplace(&contractType->contractDefinition()); return true; } void FunctionCallGraphBuilder::endVisit(MemberAccess const& _memberAccess) { auto functionType = dynamic_cast(_memberAccess.annotation().type); auto functionDef = dynamic_cast(_memberAccess.annotation().referencedDeclaration); if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal) return; // Super functions if (*_memberAccess.annotation().requiredLookup == VirtualLookup::Super) { if (auto const* typeType = dynamic_cast(_memberAccess.expression().annotation().type)) if (auto const contractType = dynamic_cast(typeType->actualType())) { solAssert(contractType->isSuper(), ""); functionDef = &functionDef->resolveVirtual( *m_contract, contractType->contractDefinition().superContract(*m_contract) ); } } else solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); processFunction(*functionDef, _memberAccess.annotation().calledDirectly); return; } void FunctionCallGraphBuilder::endVisit(ModifierInvocation const& _modifierInvocation) { VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup; if (auto const* modifier = dynamic_cast(_modifierInvocation.name().annotation().referencedDeclaration)) { if (requiredLookup == VirtualLookup::Virtual) modifier = &modifier->resolveVirtual(*m_contract); else solAssert(requiredLookup == VirtualLookup::Static, ""); processFunction(*modifier, requiredLookup == VirtualLookup::Static); } } void FunctionCallGraphBuilder::visitCallable(CallableDeclaration const* _callable, bool _directCall) { solAssert(!m_graph->edges.count(_callable), ""); optional previousNode = m_currentNode; m_currentNode = _callable; if (previousNode.has_value() && _directCall) add(*previousNode, _callable); if (!_directCall) add(*m_currentNode, m_currentDispatch); _callable->accept(*this); m_currentNode = previousNode; } bool FunctionCallGraphBuilder::add(Node _caller, Node _callee) { return m_graph->edges[_caller].insert(_callee).second; } void FunctionCallGraphBuilder::processFunction(CallableDeclaration const& _callable, bool _calledDirectly) { if (m_graph->edges.count(&_callable)) return; // Create edge to creation dispatch if (!_calledDirectly) add(m_currentDispatch, &_callable); visitCallable(&_callable, _calledDirectly); }