/* 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(); } } auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); }; // Create graph for all publicly reachable functions m_currentNode = SpecialNode::Entry; m_currentDispatch = SpecialNode::InternalDispatch; for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | views::transform(getSecondElement)) { auto const* function = dynamic_cast(&functionType->declaration()); auto const* variable = dynamic_cast(&functionType->declaration()); if (function) processFunction(*function); else // If it's not a function, it must be a getter of a public variable; we ignore those solAssert(variable, ""); } // 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()); m_currentNode.reset(); } 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(FunctionCall const& _functionCall) { solAssert(m_currentNode.has_value(), ""); solAssert(holds_alternative(*m_currentNode) || get(*m_currentNode) != nullptr, ""); FunctionType const* functionType = dynamic_cast(_functionCall.expression().annotation().type); if ( functionType && functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly ) // If it's not a direct call, we don't really know which function will be called (it may even // change at runtime). All we can do is to add an edge to the dispatch which in turn has // edges to all functions could possibly be called. add(*m_currentNode, m_currentDispatch); else if (functionType && functionType->kind() == FunctionType::Kind::Event) { // NOTE: calledDirectly is always false for events, no matter whether it's an actual emit // or just event name used in an expression. Does not matter since this visit(FunctionCall) // won't get triggered in the latter case. solAssert(!_functionCall.expression().annotation().calledDirectly, ""); EventDefinition const* event = nullptr; if (auto memberAccess = dynamic_cast(&_functionCall.expression())) { solAssert(*memberAccess->annotation().requiredLookup == VirtualLookup::Static, ""); event = dynamic_cast(memberAccess->annotation().referencedDeclaration); } else if (auto identifier = dynamic_cast(&_functionCall.expression())) { // TMP: Why is the lookup type virtual for when we refer to the event just by name // and static when we qualify it with the contract name? solAssert(*identifier->annotation().requiredLookup == VirtualLookup::Virtual, ""); event = dynamic_cast(identifier->annotation().referencedDeclaration); } else solAssert(false, ""); solAssert(event, ""); add(*m_currentNode, event); } return true; } 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); } 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); } } void FunctionCallGraphBuilder::visitCallable(CallableDeclaration const* _callable) { solAssert(!m_graph->edges.count(_callable), ""); optional previousNode = m_currentNode; m_currentNode = _callable; _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) { solAssert(m_currentNode.has_value(), ""); if (_calledDirectly) add(*m_currentNode, &_callable); else add(m_currentDispatch, &_callable); if (!m_graph->edges.count(&_callable)) visitCallable(&_callable); }