mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10973 from ethereum/function-call-graph-v2
Function Call Graph v2
This commit is contained in:
commit
907bde6e17
@ -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
|
||||
|
343
libsolidity/analysis/FunctionCallGraph.cpp
Normal file
343
libsolidity/analysis/FunctionCallGraph.cpp
Normal file
@ -0,0 +1,343 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/analysis/FunctionCallGraph.h>
|
||||
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace ranges;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::util;
|
||||
|
||||
bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const
|
||||
{
|
||||
if (_lhs.index() != _rhs.index())
|
||||
return _lhs.index() < _rhs.index();
|
||||
|
||||
if (holds_alternative<SpecialNode>(_lhs))
|
||||
return get<SpecialNode>(_lhs) < get<SpecialNode>(_rhs);
|
||||
return get<CallableDeclaration const*>(_lhs)->id() < get<CallableDeclaration const*>(_rhs)->id();
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const
|
||||
{
|
||||
solAssert(!holds_alternative<SpecialNode>(_lhs), "");
|
||||
|
||||
return get<CallableDeclaration const*>(_lhs)->id() < _rhs;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const
|
||||
{
|
||||
solAssert(!holds_alternative<SpecialNode>(_rhs), "");
|
||||
|
||||
return _lhs < get<CallableDeclaration const*>(_rhs)->id();
|
||||
}
|
||||
|
||||
FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildCreationGraph(ContractDefinition const& _contract)
|
||||
{
|
||||
FunctionCallGraphBuilder builder(_contract);
|
||||
solAssert(builder.m_currentNode == Node(SpecialNode::Entry), "");
|
||||
|
||||
// Create graph for constructor, state vars, etc
|
||||
for (ContractDefinition const* base: _contract.annotation().linearizedBaseContracts | views::reverse)
|
||||
{
|
||||
// The constructor and functions called in state variable initial assignments should have
|
||||
// an edge from Entry
|
||||
builder.m_currentNode = SpecialNode::Entry;
|
||||
for (auto const* stateVar: base->stateVariables())
|
||||
stateVar->accept(builder);
|
||||
|
||||
if (base->constructor())
|
||||
{
|
||||
builder.functionReferenced(*base->constructor());
|
||||
|
||||
// Constructors and functions called in state variable initializers have an edge either from
|
||||
// the previous class in linearized order or from Entry if there's no class before.
|
||||
builder.m_currentNode = base->constructor();
|
||||
}
|
||||
|
||||
// Functions called from the inheritance specifier should have an edge from the constructor
|
||||
// for consistency with functions called from constructor modifiers.
|
||||
for (auto const& inheritanceSpecifier: base->baseContracts())
|
||||
inheritanceSpecifier->accept(builder);
|
||||
}
|
||||
|
||||
builder.m_currentNode = SpecialNode::Entry;
|
||||
builder.processQueue();
|
||||
|
||||
return move(builder.m_graph);
|
||||
}
|
||||
|
||||
FunctionCallGraphBuilder::ContractCallGraph FunctionCallGraphBuilder::buildDeployedGraph(
|
||||
ContractDefinition const& _contract,
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph
|
||||
)
|
||||
{
|
||||
solAssert(&_creationGraph.contract == &_contract, "");
|
||||
|
||||
FunctionCallGraphBuilder builder(_contract);
|
||||
solAssert(builder.m_currentNode == Node(SpecialNode::Entry), "");
|
||||
|
||||
auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); };
|
||||
|
||||
// Create graph for all publicly reachable functions
|
||||
for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | views::transform(getSecondElement))
|
||||
{
|
||||
auto const* function = dynamic_cast<FunctionDefinition const*>(&functionType->declaration());
|
||||
auto const* variable = dynamic_cast<VariableDeclaration const*>(&functionType->declaration());
|
||||
|
||||
if (function)
|
||||
builder.functionReferenced(*function);
|
||||
else
|
||||
// If it's not a function, it must be a getter of a public variable; we ignore those
|
||||
solAssert(variable, "");
|
||||
}
|
||||
|
||||
if (_contract.fallbackFunction())
|
||||
builder.functionReferenced(*_contract.fallbackFunction());
|
||||
|
||||
if (_contract.receiveFunction())
|
||||
builder.functionReferenced(*_contract.receiveFunction());
|
||||
|
||||
// All functions present in internal dispatch at creation time could potentially be pointers
|
||||
// assigned to state variables and as such may be reachable after deployment as well.
|
||||
builder.m_currentNode = SpecialNode::InternalDispatch;
|
||||
for (Node const& dispatchTarget: valueOrDefault(_creationGraph.edges, SpecialNode::InternalDispatch, {}))
|
||||
{
|
||||
solAssert(!holds_alternative<SpecialNode>(dispatchTarget), "");
|
||||
solAssert(get<CallableDeclaration const*>(dispatchTarget) != nullptr, "");
|
||||
|
||||
// Visit the callable to add not only it but also everything it calls too
|
||||
builder.functionReferenced(*get<CallableDeclaration const*>(dispatchTarget), false);
|
||||
}
|
||||
|
||||
builder.m_currentNode = SpecialNode::Entry;
|
||||
builder.processQueue();
|
||||
|
||||
return move(builder.m_graph);
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
return true;
|
||||
|
||||
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||
solAssert(functionType, "");
|
||||
|
||||
if (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, SpecialNode::InternalDispatch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement)
|
||||
{
|
||||
auto const* functionType = dynamic_cast<FunctionType const*>(_emitStatement.eventCall().expression().annotation().type);
|
||||
solAssert(functionType, "");
|
||||
|
||||
m_graph.emittedEvents.insert(&dynamic_cast<EventDefinition const&>(functionType->declaration()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(Identifier const& _identifier)
|
||||
{
|
||||
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
||||
{
|
||||
solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, "");
|
||||
|
||||
auto funType = dynamic_cast<FunctionType const*>(_identifier.annotation().type);
|
||||
|
||||
// For events kind() == Event, so we have an extra check here
|
||||
if (funType && funType->kind() == FunctionType::Kind::Internal)
|
||||
functionReferenced(callable->resolveVirtual(m_graph.contract), _identifier.annotation().calledDirectly);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
auto functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
|
||||
auto functionDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
|
||||
if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal)
|
||||
return true;
|
||||
|
||||
// Super functions
|
||||
if (*_memberAccess.annotation().requiredLookup == VirtualLookup::Super)
|
||||
{
|
||||
if (auto const* typeType = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type))
|
||||
if (auto const contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
|
||||
{
|
||||
solAssert(contractType->isSuper(), "");
|
||||
functionDef = &functionDef->resolveVirtual(
|
||||
m_graph.contract,
|
||||
contractType->contractDefinition().superContract(m_graph.contract)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
|
||||
|
||||
functionReferenced(*functionDef, _memberAccess.annotation().calledDirectly);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation)
|
||||
{
|
||||
if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration))
|
||||
{
|
||||
VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup;
|
||||
|
||||
if (requiredLookup == VirtualLookup::Virtual)
|
||||
functionReferenced(modifier->resolveVirtual(m_graph.contract));
|
||||
else
|
||||
{
|
||||
solAssert(requiredLookup == VirtualLookup::Static, "");
|
||||
functionReferenced(*modifier);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression)
|
||||
{
|
||||
if (ContractType const* contractType = dynamic_cast<ContractType const*>(_newExpression.typeName().annotation().type))
|
||||
m_graph.createdContracts.emplace(&contractType->contractDefinition());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FunctionCallGraphBuilder::enqueueCallable(CallableDeclaration const& _callable)
|
||||
{
|
||||
if (!m_graph.edges.count(&_callable))
|
||||
{
|
||||
m_visitQueue.push_back(&_callable);
|
||||
|
||||
// Insert the callable to the graph (with no edges coming out of it) to mark it as visited.
|
||||
m_graph.edges.insert({Node(&_callable), {}});
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionCallGraphBuilder::processQueue()
|
||||
{
|
||||
solAssert(m_currentNode == Node(SpecialNode::Entry), "Visit queue is already being processed.");
|
||||
|
||||
while (!m_visitQueue.empty())
|
||||
{
|
||||
m_currentNode = m_visitQueue.front();
|
||||
solAssert(holds_alternative<CallableDeclaration const*>(m_currentNode), "");
|
||||
|
||||
m_visitQueue.pop_front();
|
||||
get<CallableDeclaration const*>(m_currentNode)->accept(*this);
|
||||
}
|
||||
|
||||
m_currentNode = SpecialNode::Entry;
|
||||
}
|
||||
|
||||
void FunctionCallGraphBuilder::add(Node _caller, Node _callee)
|
||||
{
|
||||
m_graph.edges[_caller].insert(_callee);
|
||||
}
|
||||
|
||||
void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly)
|
||||
{
|
||||
if (_calledDirectly)
|
||||
{
|
||||
solAssert(
|
||||
holds_alternative<SpecialNode>(m_currentNode) || m_graph.edges.count(m_currentNode) > 0,
|
||||
"Adding an edge from a node that has not been visited yet."
|
||||
);
|
||||
|
||||
add(m_currentNode, &_callable);
|
||||
}
|
||||
else
|
||||
add(SpecialNode::InternalDispatch, &_callable);
|
||||
|
||||
enqueueCallable(_callable);
|
||||
}
|
||||
|
||||
ostream& solidity::frontend::operator<<(ostream& _out, FunctionCallGraphBuilder::Node const& _node)
|
||||
{
|
||||
using SpecialNode = FunctionCallGraphBuilder::SpecialNode;
|
||||
|
||||
if (holds_alternative<SpecialNode>(_node))
|
||||
switch (get<SpecialNode>(_node))
|
||||
{
|
||||
case SpecialNode::InternalDispatch:
|
||||
_out << "InternalDispatch";
|
||||
break;
|
||||
case SpecialNode::Entry:
|
||||
_out << "Entry";
|
||||
break;
|
||||
default: solAssert(false, "Invalid SpecialNode type");
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(holds_alternative<CallableDeclaration const*>(_node), "");
|
||||
|
||||
auto const* callableDeclaration = get<CallableDeclaration const*>(_node);
|
||||
solAssert(callableDeclaration, "");
|
||||
|
||||
auto const* function = dynamic_cast<FunctionDefinition const *>(callableDeclaration);
|
||||
auto const* event = dynamic_cast<EventDefinition const *>(callableDeclaration);
|
||||
auto const* modifier = dynamic_cast<ModifierDefinition const *>(callableDeclaration);
|
||||
|
||||
auto typeToString = [](auto const& _var) -> string { return _var->type()->toString(true); };
|
||||
vector<string> parameters = callableDeclaration->parameters() | views::transform(typeToString) | to<vector<string>>();
|
||||
|
||||
string scopeName;
|
||||
if (!function || !function->isFree())
|
||||
{
|
||||
solAssert(callableDeclaration->annotation().scope, "");
|
||||
auto const* parentContract = dynamic_cast<ContractDefinition const*>(callableDeclaration->annotation().scope);
|
||||
solAssert(parentContract, "");
|
||||
scopeName = parentContract->name();
|
||||
}
|
||||
|
||||
if (function && function->isFree())
|
||||
_out << "function " << function->name() << "(" << joinHumanReadable(parameters, ",") << ")";
|
||||
else if (function && function->isConstructor())
|
||||
_out << "constructor of " << scopeName;
|
||||
else if (function && function->isFallback())
|
||||
_out << "fallback of " << scopeName;
|
||||
else if (function && function->isReceive())
|
||||
_out << "receive of " << scopeName;
|
||||
else if (function)
|
||||
_out << "function " << scopeName << "." << function->name() << "(" << joinHumanReadable(parameters, ",") << ")";
|
||||
else if (event)
|
||||
_out << "event " << scopeName << "." << event->name() << "(" << joinHumanReadable(parameters, ",") << ")";
|
||||
else if (modifier)
|
||||
_out << "modifier " << scopeName << "." << modifier->name();
|
||||
else
|
||||
solAssert(false, "Unexpected AST node type in function call graph");
|
||||
}
|
||||
|
||||
return _out;
|
||||
}
|
125
libsolidity/analysis/FunctionCallGraph.h
Normal file
125
libsolidity/analysis/FunctionCallGraph.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <deque>
|
||||
#include <ostream>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a Function call graph for a contract at the granularity of Solidity
|
||||
* functions and modifiers. The graph can represent the situation either at contract creation
|
||||
* or after deployment. The graph does not preserve temporal relations between calls - edges
|
||||
* coming out of the same node show which calls were performed but not in what order.
|
||||
*
|
||||
* Includes the following special nodes:
|
||||
* - Entry: represents a call from the outside of the contract.
|
||||
* After deployment this is the node that connects to all the functions exposed through the
|
||||
* external interface. At contract creation it connects to the constructors and variable
|
||||
* initializers, which are not explicitly called from within another function.
|
||||
* - InternalDispatch: Represents the internal dispatch function, which calls internal functions
|
||||
* determined at runtime by values of variables and expressions. Functions that are not called
|
||||
* right away get an edge from this node.
|
||||
*
|
||||
* Nodes are a variant of either the enum SpecialNode or a CallableDeclaration which currently
|
||||
* can be a function or a modifier. There are no nodes representing event calls. Instead all
|
||||
* emitted events and created contracts are gathered in separate sets included in the graph just
|
||||
* for that purpose.
|
||||
*
|
||||
* Auto-generated getter functions for public state variables are ignored, but function calls
|
||||
* inside initial assignments are included in the creation graph.
|
||||
*
|
||||
* Only calls reachable from an Entry node are included in the graph. The map representing edges
|
||||
* is also guaranteed to contain keys representing all the reachable functions and modifiers, even
|
||||
* if they have no outgoing edges.
|
||||
*/
|
||||
class FunctionCallGraphBuilder: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
enum class SpecialNode
|
||||
{
|
||||
InternalDispatch,
|
||||
Entry,
|
||||
};
|
||||
|
||||
using Node = std::variant<CallableDeclaration const*, SpecialNode>;
|
||||
|
||||
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
|
||||
{
|
||||
/// Contract for which this is the graph
|
||||
ContractDefinition const& contract;
|
||||
|
||||
/// Graph edges. Edges are directed and lead from the caller to the callee.
|
||||
/// The map contains a key for every possible caller, even if does not actually perform
|
||||
/// any calls.
|
||||
std::map<Node, std::set<Node, CompareByID>, CompareByID> edges;
|
||||
|
||||
/// Contracts that may get created with `new` by functions present in the graph.
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID> createdContracts;
|
||||
|
||||
/// Events that may get emitted by functions present in the graph.
|
||||
std::set<EventDefinition const*, ASTNode::CompareByID> emittedEvents;
|
||||
};
|
||||
|
||||
static ContractCallGraph buildCreationGraph(ContractDefinition const& _contract);
|
||||
static ContractCallGraph buildDeployedGraph(
|
||||
ContractDefinition const& _contract,
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph
|
||||
);
|
||||
|
||||
private:
|
||||
FunctionCallGraphBuilder(ContractDefinition const& _contract):
|
||||
m_graph{_contract, {}, {}, {}} {}
|
||||
|
||||
bool visit(FunctionCall const& _functionCall) override;
|
||||
bool visit(EmitStatement const& _emitStatement) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(MemberAccess const& _memberAccess) override;
|
||||
bool visit(ModifierInvocation const& _modifierInvocation) override;
|
||||
bool visit(NewExpression const& _newExpression) override;
|
||||
|
||||
void enqueueCallable(CallableDeclaration const& _callable);
|
||||
void processQueue();
|
||||
|
||||
void add(Node _caller, Node _callee);
|
||||
void functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly = true);
|
||||
|
||||
Node m_currentNode = SpecialNode::Entry;
|
||||
ContractCallGraph m_graph;
|
||||
std::deque<CallableDeclaration const*> m_visitQueue;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& _out, FunctionCallGraphBuilder::Node const& _node);
|
||||
|
||||
}
|
@ -2632,11 +2632,13 @@ FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind):
|
||||
|
||||
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
||||
{
|
||||
solAssert(var->annotation().type, "Parameter type is not yet available in the AST.");
|
||||
m_parameterNames.push_back(var->name());
|
||||
m_parameterTypes.push_back(var->annotation().type);
|
||||
}
|
||||
for (ASTPointer<VariableDeclaration> const& var: _function.returnParameters())
|
||||
{
|
||||
solAssert(var->annotation().type, "Return parameter type is not yet available in the AST.");
|
||||
m_returnParameterNames.push_back(var->name());
|
||||
m_returnParameterTypes.push_back(var->annotation().type);
|
||||
}
|
||||
|
@ -32,11 +32,11 @@ 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 != "<<STUB<<", "");
|
||||
solAssert(code != "<<STUB<<", "");
|
||||
// std::map guarantees ascending order when iterating through its keys.
|
||||
result += f.second;
|
||||
result += code;
|
||||
}
|
||||
m_requestedFunctions.clear();
|
||||
return result;
|
||||
|
@ -35,6 +35,9 @@ YulArity YulArity::fromType(FunctionType const& _functionType)
|
||||
|
||||
string IRNames::function(FunctionDefinition const& _function)
|
||||
{
|
||||
if (_function.isConstructor())
|
||||
return implicitConstructor(*_function.annotation().contract);
|
||||
|
||||
return "fun_" + _function.name() + "_" + to_string(_function.id());
|
||||
}
|
||||
|
||||
|
@ -36,18 +36,61 @@
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Whiskers.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <range/v3/view/map.hpp>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <variant>
|
||||
|
||||
using namespace std;
|
||||
using namespace ranges;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void verifyCallGraph(
|
||||
set<CallableDeclaration const*, ASTNode::CompareByID> const& _expectedCallables,
|
||||
set<FunctionDefinition const*> _generatedFunctions
|
||||
)
|
||||
{
|
||||
for (auto const& expectedCallable: _expectedCallables)
|
||||
if (auto const* expectedFunction = dynamic_cast<FunctionDefinition const*>(expectedCallable))
|
||||
{
|
||||
solAssert(
|
||||
_generatedFunctions.count(expectedFunction) == 1 || expectedFunction->isConstructor(),
|
||||
"No code generated for function " + expectedFunction->name() + "even though it is not a constructor."
|
||||
);
|
||||
_generatedFunctions.erase(expectedFunction);
|
||||
}
|
||||
|
||||
solAssert(
|
||||
_generatedFunctions.size() == 0,
|
||||
"Of the generated functions " + toString(_generatedFunctions.size()) + " are not in the call graph."
|
||||
);
|
||||
}
|
||||
|
||||
set<CallableDeclaration const*, ASTNode::CompareByID> collectReachableCallables(
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _graph
|
||||
)
|
||||
{
|
||||
set<CallableDeclaration const*, ASTNode::CompareByID> reachableCallables;
|
||||
for (FunctionCallGraphBuilder::Node const& reachableNode: _graph.edges | views::keys)
|
||||
if (holds_alternative<CallableDeclaration const*>(reachableNode))
|
||||
reachableCallables.emplace(get<CallableDeclaration const*>(reachableNode));
|
||||
|
||||
return reachableCallables;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pair<string, string> IRGenerator::run(
|
||||
ContractDefinition const& _contract,
|
||||
map<ContractDefinition const*, string_view const> const& _otherYulSources
|
||||
@ -76,11 +119,31 @@ pair<string, string> IRGenerator::run(
|
||||
return {warning + ir, warning + asmStack.print()};
|
||||
}
|
||||
|
||||
void IRGenerator::verifyCallGraphs(
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph,
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _deployedGraph
|
||||
)
|
||||
{
|
||||
// m_creationFunctionList and m_deployedFunctionList are not used for any other purpose so
|
||||
// we can just destroy them without bothering to make a copy.
|
||||
|
||||
verifyCallGraph(collectReachableCallables(_creationGraph), move(m_creationFunctionList));
|
||||
m_creationFunctionList = {};
|
||||
|
||||
verifyCallGraph(collectReachableCallables(_deployedGraph), move(m_deployedFunctionList));
|
||||
m_deployedFunctionList = {};
|
||||
}
|
||||
|
||||
string IRGenerator::generate(
|
||||
ContractDefinition const& _contract,
|
||||
map<ContractDefinition const*, string_view const> const& _otherYulSources
|
||||
)
|
||||
{
|
||||
// Remember to call verifyCallGraphs() (which clears the list of generated functions) if you
|
||||
// want to reuse the generator.
|
||||
solAssert(m_creationFunctionList.empty(), "");
|
||||
solAssert(m_deployedFunctionList.empty(), "");
|
||||
|
||||
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
|
||||
{
|
||||
std::string subObjectsSources;
|
||||
@ -142,8 +205,9 @@ string IRGenerator::generate(
|
||||
|
||||
t("deploy", deployCode(_contract));
|
||||
generateImplicitConstructors(_contract);
|
||||
generateQueuedFunctions();
|
||||
m_creationFunctionList = generateQueuedFunctions();
|
||||
InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions();
|
||||
|
||||
t("functions", m_context.functionCollector().requestedFunctions());
|
||||
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
@ -162,7 +226,7 @@ string IRGenerator::generate(
|
||||
t("DeployedObject", IRNames::deployedObject(_contract));
|
||||
t("library_address", IRNames::libraryAddressImmutable());
|
||||
t("dispatch", dispatchRoutine(_contract));
|
||||
generateQueuedFunctions();
|
||||
m_deployedFunctionList = generateQueuedFunctions();
|
||||
generateInternalDispatchFunctions();
|
||||
t("deployedFunctions", m_context.functionCollector().requestedFunctions());
|
||||
t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
@ -170,6 +234,7 @@ string IRGenerator::generate(
|
||||
// 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();
|
||||
}
|
||||
|
||||
@ -180,11 +245,20 @@ string IRGenerator::generate(Block const& _block)
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
void IRGenerator::generateQueuedFunctions()
|
||||
set<FunctionDefinition const*> IRGenerator::generateQueuedFunctions()
|
||||
{
|
||||
set<FunctionDefinition const*> functions;
|
||||
|
||||
while (!m_context.functionGenerationQueueEmpty())
|
||||
{
|
||||
FunctionDefinition const& functionDefinition = *m_context.dequeueFunctionForCodeGeneration();
|
||||
|
||||
functions.emplace(&functionDefinition);
|
||||
// NOTE: generateFunction() may modify function generation queue
|
||||
generateFunction(*m_context.dequeueFunctionForCodeGeneration());
|
||||
generateFunction(functionDefinition);
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
|
||||
InternalDispatchMap IRGenerator::generateInternalDispatchFunctions()
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/analysis/FunctionCallGraph.h>
|
||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
@ -56,6 +57,11 @@ public:
|
||||
std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources
|
||||
);
|
||||
|
||||
void verifyCallGraphs(
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _creationGraph,
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& _deployedGraph
|
||||
);
|
||||
|
||||
private:
|
||||
std::string generate(
|
||||
ContractDefinition const& _contract,
|
||||
@ -65,7 +71,8 @@ private:
|
||||
|
||||
/// Generates code for all the functions from the function generation queue.
|
||||
/// The resulting code is stored in the function collector in IRGenerationContext.
|
||||
void generateQueuedFunctions();
|
||||
/// @returns A set of ast nodes of the generated functions.
|
||||
std::set<FunctionDefinition const*> generateQueuedFunctions();
|
||||
/// Generates all the internal dispatch functions necessary to handle any function that could
|
||||
/// possibly be called via a pointer.
|
||||
/// @return The content of the dispatch for reuse in runtime code. Reuse is necessary because
|
||||
@ -115,6 +122,9 @@ private:
|
||||
langutil::EVMVersion const m_evmVersion;
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
|
||||
std::set<FunctionDefinition const*> m_creationFunctionList;
|
||||
std::set<FunctionDefinition const*> m_deployedFunctionList;
|
||||
|
||||
IRGenerationContext m_context;
|
||||
YulUtilFunctions m_utils;
|
||||
};
|
||||
|
@ -341,9 +341,9 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
DocStringTagParser DocStringTagParser(m_errorReporter);
|
||||
DocStringTagParser docStringTagParser(m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !DocStringTagParser.parseDocStrings(*source->ast))
|
||||
if (source->ast && !docStringTagParser.parseDocStrings(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
m_globalContext = make_shared<GlobalContext>();
|
||||
@ -387,7 +387,7 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
// New we run full type checks that go down to the expression level. This
|
||||
// Now we run full type checks that go down to the expression level. This
|
||||
// cannot be done earlier, because we need cross-contract types and information
|
||||
// about whether a contract is abstract for the `new` expression.
|
||||
// This populates the `type` annotation for all expressions.
|
||||
@ -399,6 +399,19 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !typeChecker.checkTypeRequirements(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast)
|
||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
||||
if (auto const* contractDefinition = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
Contract& contractState = m_contracts.at(contractDefinition->fullyQualifiedName());
|
||||
contractState.creationCallGraph.emplace(FunctionCallGraphBuilder::buildCreationGraph(*contractDefinition));
|
||||
contractState.deployedCallGraph.emplace(FunctionCallGraphBuilder::buildDeployedGraph(*contractDefinition, *contractState.creationCallGraph));
|
||||
}
|
||||
}
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
// Checks that can only be done when all types of all AST nodes are known.
|
||||
@ -937,6 +950,24 @@ string const& CompilerStack::metadata(Contract const& _contract) const
|
||||
return _contract.metadata.init([&]{ return createMetadata(_contract); });
|
||||
}
|
||||
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& CompilerStack::creationCallGraph(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(contract(_contractName).creationCallGraph.has_value(), "");
|
||||
return contract(_contractName).creationCallGraph.value();
|
||||
}
|
||||
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& CompilerStack::deployedCallGraph(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(contract(_contractName).deployedCallGraph.has_value(), "");
|
||||
return contract(_contractName).deployedCallGraph.value();
|
||||
}
|
||||
|
||||
Scanner const& CompilerStack::scanner(string const& _sourceName) const
|
||||
{
|
||||
if (m_stackState < SourcesSet)
|
||||
@ -1275,6 +1306,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.verifyCallGraphs(compiledContract.creationCallGraph.value(), compiledContract.deployedCallGraph.value());
|
||||
}
|
||||
|
||||
void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/analysis/FunctionCallGraph.h>
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
@ -342,6 +343,12 @@ public:
|
||||
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
|
||||
Json::Value gasEstimates(std::string const& _contractName) const;
|
||||
|
||||
/// @returns a graph with edges representing calls between functions that may happen during contract construction.
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& creationCallGraph(std::string const& _contractName) const;
|
||||
|
||||
/// @returns a graph with edges representing calls between functions that may happen in a deployed contract.
|
||||
FunctionCallGraphBuilder::ContractCallGraph const& deployedCallGraph(std::string const& _contractName) const;
|
||||
|
||||
/// Changes the format of the metadata appended at the end of the bytecode.
|
||||
/// This is mostly a workaround to avoid bytecode and gas differences between compiler builds
|
||||
/// caused by differences in metadata. Should only be used for testing.
|
||||
@ -383,6 +390,8 @@ private:
|
||||
util::LazyInit<Json::Value const> runtimeGeneratedSources;
|
||||
mutable std::optional<std::string const> sourceMapping;
|
||||
mutable std::optional<std::string const> runtimeSourceMapping;
|
||||
std::optional<FunctionCallGraphBuilder::ContractCallGraph const> creationCallGraph;
|
||||
std::optional<FunctionCallGraphBuilder::ContractCallGraph const> deployedCallGraph;
|
||||
};
|
||||
|
||||
/// Loads the missing sources from @a _ast (named @a _path) using the callback
|
||||
|
@ -97,6 +97,7 @@ set(libsolidity_sources
|
||||
libsolidity/SyntaxTest.cpp
|
||||
libsolidity/SyntaxTest.h
|
||||
libsolidity/ViewPureChecker.cpp
|
||||
libsolidity/analysis/FunctionCallGraph.cpp
|
||||
)
|
||||
detect_stray_source_files("${libsolidity_sources}" "libsolidity/")
|
||||
|
||||
|
2005
test/libsolidity/analysis/FunctionCallGraph.cpp
Normal file
2005
test/libsolidity/analysis/FunctionCallGraph.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user