diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
index 6ca24bc8a..981617a84 100644
--- a/libsolidity/CMakeLists.txt
+++ b/libsolidity/CMakeLists.txt
@@ -18,6 +18,8 @@ set(sources
analysis/DocStringAnalyser.h
analysis/DocStringTagParser.cpp
analysis/DocStringTagParser.h
+ analysis/FunctionCallGraph.cpp
+ analysis/FunctionCallGraph.h
analysis/ImmutableValidator.cpp
analysis/ImmutableValidator.h
analysis/GlobalContext.cpp
diff --git a/libsolidity/analysis/FunctionCallGraph.cpp b/libsolidity/analysis/FunctionCallGraph.cpp
new file mode 100644
index 000000000..594e3e8b8
--- /dev/null
+++ b/libsolidity/analysis/FunctionCallGraph.cpp
@@ -0,0 +1,207 @@
+/*
+ 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
+
+using namespace std;
+using namespace solidity::frontend;
+
+bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const
+{
+ if (_lhs.index() != _rhs.index())
+ return _lhs.index() < _rhs.index();
+
+ if (std::holds_alternative(_lhs))
+ return std::get(_lhs) < std::get(_rhs);
+ return std::get(_lhs)->id() < std::get(_rhs)->id();
+}
+
+bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const
+{
+ solAssert(!std::holds_alternative(_lhs), "");
+
+ return std::get(_lhs)->id() < _rhs;
+}
+
+bool FunctionCallGraphBuilder::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const
+{
+ solAssert(!std::holds_alternative(_rhs), "");
+
+ return _lhs < std::get(_rhs)->id();
+}
+
+shared_ptr FunctionCallGraphBuilder::create(ContractDefinition const& _contract)
+{
+ m_contract = &_contract;
+
+ m_graph = make_shared(_contract);
+
+ // Create graph for constructor, state vars, etc
+ m_currentNode = SpecialNode::CreationRoot;
+ m_currentDispatch = SpecialNode::CreationDispatch;
+ for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
+ visitConstructor(*contract);
+
+ m_currentNode.reset();
+ m_currentDispatch = SpecialNode::InternalDispatch;
+
+ // Create graph for all publicly reachable functions
+ for (auto& [hash, functionType]: _contract.interfaceFunctionList())
+ {
+ (void)hash;
+ if (auto const* funcDef = dynamic_cast(&functionType->declaration()))
+ if (!m_graph->edges.count(funcDef))
+ visitCallable(funcDef);
+ }
+
+ // Add all CreationDispatch calls to the RuntimeDispatch as well
+ for (auto node: m_graph->edges[SpecialNode::CreationDispatch])
+ add(SpecialNode::InternalDispatch, node);
+
+ // Add all external functions to the RuntimeDispatch
+ for (auto& [hash, functionType]: _contract.interfaceFunctionList())
+ {
+ (void)hash;
+ add(SpecialNode::ExternalDispatch, &functionType->declaration());
+ }
+
+ if (_contract.fallbackFunction())
+ add(SpecialNode::ExternalDispatch, _contract.fallbackFunction());
+
+ if (_contract.receiveFunction())
+ add(SpecialNode::ExternalDispatch, _contract.receiveFunction());
+
+ m_contract = nullptr;
+ solAssert(!m_currentNode.has_value(), "Current node not properly reset.");
+
+ return 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());
+
+ 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 (ContractType const* type = dynamic_cast(_memberAccess.expression().annotation().type))
+ {
+ solAssert(type->isSuper(), "");
+ functionDef = &functionDef->resolveVirtual(*m_contract, type->contractDefinition().superContract(*m_contract));
+ }
+
+ processFunction(*functionDef, _memberAccess.annotation());
+ return;
+}
+
+void FunctionCallGraphBuilder::endVisit(FunctionCall const& _functionCall)
+{
+ auto* functionType = dynamic_cast(_functionCall.expression().annotation().type);
+
+ if (
+ functionType &&
+ functionType->kind() == FunctionType::Kind::Internal &&
+ !functionType->hasDeclaration()
+ )
+ add(m_currentDispatch, &_functionCall);
+}
+
+void FunctionCallGraphBuilder::visitCallable(CallableDeclaration const* _callable)
+{
+ solAssert(!m_graph->edges.count(_callable), "");
+
+ auto previousNode = m_currentNode;
+ m_currentNode = _callable;
+
+ if (previousNode.has_value())
+ add(*previousNode, _callable);
+
+ _callable->accept(*this);
+
+ m_currentNode = previousNode;
+}
+
+void FunctionCallGraphBuilder::visitConstructor(ContractDefinition const& _contract)
+{
+ 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);
+ }
+}
+
+bool FunctionCallGraphBuilder::add(Node _caller, ASTNode const* _callee)
+{
+ solAssert(_callee != nullptr, "");
+ auto result = m_graph->edges.find(_caller);
+
+ if (result == m_graph->edges.end())
+ {
+ m_graph->edges.emplace(_caller, std::set{_callee});
+ return true;
+ }
+
+ return result->second.emplace(_callee).second;
+}
+
+void FunctionCallGraphBuilder::processFunction(CallableDeclaration const& _callable, ExpressionAnnotation const& _annotation)
+{
+ if (m_graph->edges.count(&_callable))
+ return;
+
+ // Create edge to creation dispatch
+ if (!_annotation.calledDirectly)
+ add(m_currentDispatch, &_callable);
+ visitCallable(&_callable);
+}
diff --git a/libsolidity/analysis/FunctionCallGraph.h b/libsolidity/analysis/FunctionCallGraph.h
new file mode 100644
index 000000000..b79f2dbdf
--- /dev/null
+++ b/libsolidity/analysis/FunctionCallGraph.h
@@ -0,0 +1,98 @@
+/*
+ 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
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+namespace solidity::frontend
+{
+
+/**
+ * Creates a Function call graph for a contract at the granularity of Solidity
+ * functions and modifiers
+ *
+ * Includes the following special nodes:
+ * - CreationRoot: All calls made at contract creation originate from this node
+ * - CreationDispatch: Represents the internal dispatch function at creation time
+ * - ExternalDispatch: Represents the runtime dispatch for all external functions
+ * - InternalDispatch: Represents the runtime dispatch for internal function pointers and complex expressions
+ *
+ * Nodes are a variant of either the enum SpecialNode or an ASTNode pointer.
+ * ASTNodes are usually inherited from CallableDeclarations
+ * (FunctionDefinition, ModifierDefinition, EventDefinition) but for functions
+ * without declaration it is directly the FunctionCall AST node.
+ *
+ * Functions that are not called right away as well as functions without
+ * declarations have an edge to the internal dispatch node.
+ *
+ * Auto-generated getter functions for public state variables are ignored.
+ */
+class FunctionCallGraphBuilder: private ASTConstVisitor
+{
+public:
+ enum class SpecialNode { CreationRoot, CreationDispatch, InternalDispatch, ExternalDispatch };
+
+ using Node = std::variant;
+
+ struct CompareByID
+ {
+ using is_transparent = void;
+ bool operator()(Node const& _lhs, Node const& _rhs) const;
+ bool operator()(Node const& _lhs, int64_t _rhs) const;
+ bool operator()(int64_t _lhs, Node const& _rhs) const;
+ };
+
+ struct ContractCallGraph
+ {
+ ContractCallGraph(ContractDefinition const& _contract): contract(_contract) {}
+
+ /// Contract for which this is the graph
+ ContractDefinition const& contract;
+
+ std::map, CompareByID> edges;
+
+ /// Set of contracts created
+ std::set createdContracts;
+ };
+
+ std::shared_ptr create(ContractDefinition const& _contract);
+
+private:
+ bool visit(Identifier const& _identifier) override;
+ bool visit(NewExpression const& _newExpression) override;
+ void endVisit(MemberAccess const& _memberAccess) override;
+ void endVisit(FunctionCall const& _functionCall) override;
+
+ void visitCallable(CallableDeclaration const* _callable);
+ void visitConstructor(ContractDefinition const& _contract);
+
+ bool add(Node _caller, ASTNode const* _callee);
+ void processFunction(CallableDeclaration const& _callable, ExpressionAnnotation const& _annotation);
+
+ ContractDefinition const* m_contract = nullptr;
+ std::optional m_currentNode;
+ std::shared_ptr m_graph = nullptr;
+ Node m_currentDispatch = SpecialNode::CreationDispatch;
+};
+
+}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index ab5041049..566acda14 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -899,7 +899,7 @@ public:
(annotation().contract && annotation().contract->isInterface());
}
- FunctionDefinition const& resolveVirtual(
+ [[nodiscard]] FunctionDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp
index 541171818..5b1fd590f 100644
--- a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp
+++ b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp
@@ -32,16 +32,29 @@ using namespace solidity::frontend;
string MultiUseYulFunctionCollector::requestedFunctions()
{
string result;
- for (auto const& f: m_requestedFunctions)
+ for (auto const& [name, code]: m_requestedFunctions)
{
- solAssert(f.second != "< MultiUseYulFunctionCollector::requestedFunctionsNames()
+{
+ set names;
+ for (auto const& [name, code]: m_requestedFunctions)
+ {
+ (void) code;
+ names.emplace(name);
+ }
+
+ return names;
+}
+
string MultiUseYulFunctionCollector::createFunction(string const& _name, function const& _creator)
{
if (!m_requestedFunctions.count(_name))
diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.h b/libsolidity/codegen/MultiUseYulFunctionCollector.h
index 428a0c9a3..7d0afbea6 100644
--- a/libsolidity/codegen/MultiUseYulFunctionCollector.h
+++ b/libsolidity/codegen/MultiUseYulFunctionCollector.h
@@ -24,6 +24,7 @@
#include
#include