From c977c0cfd0f0f6bb38ffc29dbb31e71620b1b794 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Wed, 18 Nov 2020 18:24:33 +0100 Subject: [PATCH] Implement a Function Call Graph --- libsolidity/CMakeLists.txt | 2 + libsolidity/analysis/FunctionCallGraph.cpp | 207 ++++++++++++++++++ libsolidity/analysis/FunctionCallGraph.h | 98 +++++++++ libsolidity/ast/AST.h | 2 +- .../codegen/MultiUseYulFunctionCollector.cpp | 19 +- .../codegen/MultiUseYulFunctionCollector.h | 5 +- libsolidity/codegen/ir/Common.cpp | 5 + libsolidity/codegen/ir/IRGenerator.cpp | 68 ++++++ libsolidity/codegen/ir/IRGenerator.h | 6 + libsolidity/interface/CompilerStack.cpp | 13 ++ libsolidity/interface/CompilerStack.h | 2 + 11 files changed, 422 insertions(+), 5 deletions(-) create mode 100644 libsolidity/analysis/FunctionCallGraph.cpp create mode 100644 libsolidity/analysis/FunctionCallGraph.h 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 +#include #include namespace solidity::frontend @@ -48,9 +49,11 @@ public: /// empty return value. std::string requestedFunctions(); + /// Helper function to get the names of all requested functions + std::set requestedFunctionsNames(); + /// @returns true IFF a function with the specified name has already been collected. bool contains(std::string const& _name) const { return m_requestedFunctions.count(_name) > 0; } - private: /// Map from function name to code for a multi-use function. std::map m_requestedFunctions; diff --git a/libsolidity/codegen/ir/Common.cpp b/libsolidity/codegen/ir/Common.cpp index 321becc51..ff99afab1 100644 --- a/libsolidity/codegen/ir/Common.cpp +++ b/libsolidity/codegen/ir/Common.cpp @@ -35,6 +35,11 @@ YulArity YulArity::fromType(FunctionType const& _functionType) string IRNames::function(FunctionDefinition const& _function) { + if (_function.isConstructor()) + return implicitConstructor(*_function.annotation().contract); + + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? return "fun_" + _function.name() + "_" + to_string(_function.id()); } diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 2f817d5db..eb7b7f833 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -48,6 +48,47 @@ using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; +namespace { + +void verifyCallGraph(set const& _nodes, set& _functionList) +{ + for (auto const& node: _nodes) + if (auto const* functionDef = dynamic_cast(node)) + solAssert(_functionList.erase(IRNames::function(*functionDef)) == 1, "Function not found in generated code"); + + static string const funPrefix = "fun_"; + + for (string const& name: _functionList) + solAssert(name.substr(0, funPrefix.size()) != funPrefix, "Functions found in code gen that were not in the call graph"); +} + + +void collectCalls(FunctionCallGraphBuilder::ContractCallGraph const& _graph, ASTNode const* _root, set& _functions) +{ + if (_functions.count(_root) > 0) + return; + + set toVisit{_root}; + + _functions.emplace(_root); + + while (!toVisit.empty()) + { + ASTNode const* function = *toVisit.begin(); + toVisit.erase(toVisit.begin()); + + auto callees = _graph.edges.find(function); + if (callees == _graph.edges.end()) + continue; + + for (auto& callee: callees->second) + if (_functions.emplace(callee).second) + toVisit.emplace(callee); + } +} + +} + pair IRGenerator::run( ContractDefinition const& _contract, map const& _otherYulSources @@ -76,6 +117,29 @@ pair IRGenerator::run( return {warning + ir, warning + asmStack.print()}; } +void IRGenerator::verifyCallGraph(FunctionCallGraphBuilder::ContractCallGraph const& _graph) +{ + set functions; + + auto collectFromNode = [&](FunctionCallGraphBuilder::SpecialNode _node) + { + auto callees = _graph.edges.find(_node); + + if (callees != _graph.edges.end()) + for (auto callee: callees->second) + collectCalls(_graph, callee, functions); + }; + + collectFromNode(FunctionCallGraphBuilder::SpecialNode::CreationRoot); + collectFromNode(FunctionCallGraphBuilder::SpecialNode::CreationDispatch); + ::verifyCallGraph(functions, m_creationFunctionList); + + functions.clear(); + collectFromNode(FunctionCallGraphBuilder::SpecialNode::ExternalDispatch); + collectFromNode(FunctionCallGraphBuilder::SpecialNode::InternalDispatch); + ::verifyCallGraph(functions, m_deployedFunctionList); +} + string IRGenerator::generate( ContractDefinition const& _contract, map const& _otherYulSources @@ -144,6 +208,8 @@ string IRGenerator::generate( generateImplicitConstructors(_contract); generateQueuedFunctions(); InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions(); + + m_creationFunctionList = m_context.functionCollector().requestedFunctionsNames(); t("functions", m_context.functionCollector().requestedFunctions()); t("subObjects", subObjectSources(m_context.subObjectsCreated())); @@ -164,12 +230,14 @@ string IRGenerator::generate( t("dispatch", dispatchRoutine(_contract)); generateQueuedFunctions(); generateInternalDispatchFunctions(); + m_deployedFunctionList = m_context.functionCollector().requestedFunctionsNames(); t("deployedFunctions", m_context.functionCollector().requestedFunctions()); t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated())); // This has to be called only after all other code generation for the deployed object is complete. bool deployedInvolvesAssembly = m_context.inlineAssemblySeen(); t("memoryInitDeployed", memoryInit(!deployedInvolvesAssembly)); + return t.render(); } diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 7b7c58bd4..bbf19752d 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,8 @@ public: std::map const& _otherYulSources ); + void verifyCallGraph(FunctionCallGraphBuilder::ContractCallGraph const& _graph); + private: std::string generate( ContractDefinition const& _contract, @@ -115,6 +118,9 @@ private: langutil::EVMVersion const m_evmVersion; OptimiserSettings const m_optimiserSettings; + std::set m_creationFunctionList; + std::set m_deployedFunctionList; + IRGenerationContext m_context; YulUtilFunctions m_utils; }; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3512a93ca..bfcc0fefd 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -217,6 +217,7 @@ void CompilerStack::reset(bool _keepSettings) m_sources.clear(); m_smtlib2Responses.clear(); m_unhandledSMTLib2Queries.clear(); + m_contractCallGraphs.clear(); if (!_keepSettings) { m_remappings.clear(); @@ -399,6 +400,16 @@ bool CompilerStack::analyze() if (source->ast && !typeChecker.checkTypeRequirements(*source->ast)) noErrors = false; + if (noErrors) + { + FunctionCallGraphBuilder builder; + for (Source const* source: m_sourceOrder) + if (source->ast) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + m_contractCallGraphs.emplace(contract, builder.create(*contract)); + } + if (noErrors) { // Checks that can only be done when all types of all AST nodes are known. @@ -1275,6 +1286,8 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings); tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources); + + generator.verifyCallGraph(*m_contractCallGraphs[&_contract]); } void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract) diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 5049561c4..ba0c794b0 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -24,6 +24,7 @@ #pragma once +#include #include #include #include @@ -475,6 +476,7 @@ private: bool m_generateIR = false; bool m_generateEwasm = false; std::map m_libraries; + std::map const> m_contractCallGraphs; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" std::vector m_remappings;