/*
	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 
#include 
#include 
using namespace std;
using namespace solidity::frontend;
using namespace solidity::util;
CallGraph FunctionCallGraphBuilder::buildCreationGraph(ContractDefinition const& _contract)
{
	FunctionCallGraphBuilder builder(_contract);
	solAssert(builder.m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), "");
	// Create graph for constructor, state vars, etc
	for (ContractDefinition const* base: _contract.annotation().linearizedBaseContracts | ranges::views::reverse)
	{
		// The constructor and functions called in state variable initial assignments should have
		// an edge from Entry
		builder.m_currentNode = CallGraph::SpecialNode::Entry;
		for (auto const* stateVar: base->stateVariables())
			if (!stateVar->isConstant())
				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 = CallGraph::SpecialNode::Entry;
	builder.processQueue();
	return std::move(builder.m_graph);
}
CallGraph FunctionCallGraphBuilder::buildDeployedGraph(
	ContractDefinition const& _contract,
	CallGraph const& _creationGraph
)
{
	FunctionCallGraphBuilder builder(_contract);
	solAssert(builder.m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), "");
	auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); };
	// Create graph for all publicly reachable functions
	for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | ranges::views::transform(getSecondElement))
	{
		auto const* function = dynamic_cast(&functionType->declaration());
		auto const* variable = dynamic_cast(&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 = CallGraph::SpecialNode::InternalDispatch;
	set defaultNode;
	for (CallGraph::Node const& dispatchTarget: util::valueOrDefault(_creationGraph.edges, CallGraph::SpecialNode::InternalDispatch, defaultNode))
	{
		solAssert(!holds_alternative(dispatchTarget), "");
		solAssert(get(dispatchTarget) != nullptr, "");
		// Visit the callable to add not only it but also everything it calls too
		builder.functionReferenced(*get(dispatchTarget), false);
	}
	builder.m_currentNode = CallGraph::SpecialNode::Entry;
	builder.processQueue();
	return std::move(builder.m_graph);
}
bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall)
{
	if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
		return true;
	auto const* functionType = dynamic_cast(_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, CallGraph::SpecialNode::InternalDispatch);
	else if (functionType->kind() == FunctionType::Kind::Error)
		m_graph.usedErrors.insert(&dynamic_cast(functionType->declaration()));
	return true;
}
bool FunctionCallGraphBuilder::visit(EmitStatement const& _emitStatement)
{
	auto const* functionType = dynamic_cast(_emitStatement.eventCall().expression().annotation().type);
	solAssert(functionType, "");
	m_graph.emittedEvents.insert(&dynamic_cast(functionType->declaration()));
	return true;
}
bool FunctionCallGraphBuilder::visit(Identifier const& _identifier)
{
	if (auto const* variable = dynamic_cast(_identifier.annotation().referencedDeclaration))
	{
		if (variable->isConstant())
		{
			solAssert(variable->isStateVariable() || variable->isFileLevelVariable(), "");
			variable->accept(*this);
		}
	}
	else 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)
			functionReferenced(callable->resolveVirtual(m_contract), _identifier.annotation().calledDirectly);
	}
	return true;
}
bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
{
	Type const* exprType = _memberAccess.expression().annotation().type;
	ASTString const& memberName = _memberAccess.memberName();
	if (auto magicType = dynamic_cast(exprType))
		if (magicType->kind() == MagicType::Kind::MetaType && (
			memberName == "creationCode" || memberName == "runtimeCode"
		))
		{
			ContractType const& accessedContractType = dynamic_cast(*magicType->typeArgument());
			m_graph.bytecodeDependency.emplace(&accessedContractType.contractDefinition(), &_memberAccess);
		}
	auto functionType = dynamic_cast(_memberAccess.annotation().type);
	auto functionDef = dynamic_cast(_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(exprType))
			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, "");
	functionReferenced(*functionDef, _memberAccess.annotation().calledDirectly);
	return true;
}
bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation)
{
	if (auto const* modifier = dynamic_cast(_modifierInvocation.name().annotation().referencedDeclaration))
	{
		VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup;
		if (requiredLookup == VirtualLookup::Virtual)
			functionReferenced(modifier->resolveVirtual(m_contract));
		else
		{
			solAssert(requiredLookup == VirtualLookup::Static, "");
			functionReferenced(*modifier);
		}
	}
	return true;
}
bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression)
{
	if (ContractType const* contractType = dynamic_cast(_newExpression.typeName().annotation().type))
		m_graph.bytecodeDependency.emplace(&contractType->contractDefinition(), &_newExpression);
	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({CallGraph::Node(&_callable), {}});
	}
}
void FunctionCallGraphBuilder::processQueue()
{
	solAssert(m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), "Visit queue is already being processed.");
	while (!m_visitQueue.empty())
	{
		m_currentNode = m_visitQueue.front();
		solAssert(holds_alternative(m_currentNode), "");
		m_visitQueue.pop_front();
		get(m_currentNode)->accept(*this);
	}
	m_currentNode = CallGraph::SpecialNode::Entry;
}
void FunctionCallGraphBuilder::add(CallGraph::Node _caller, CallGraph::Node _callee)
{
	m_graph.edges[_caller].insert(_callee);
}
void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _callable, bool _calledDirectly)
{
	if (_calledDirectly)
	{
		solAssert(
			holds_alternative(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(CallGraph::SpecialNode::InternalDispatch, &_callable);
	enqueueCallable(_callable);
}
ostream& solidity::frontend::operator<<(ostream& _out, CallGraph::Node const& _node)
{
	if (holds_alternative(_node))
		switch (get(_node))
		{
		case CallGraph::SpecialNode::InternalDispatch:
			_out << "InternalDispatch";
			break;
		case CallGraph::SpecialNode::Entry:
			_out << "Entry";
			break;
		default:
			solAssert(false, "Invalid SpecialNode type");
		}
	else
	{
		solAssert(holds_alternative(_node), "");
		auto const* callableDeclaration = get(_node);
		solAssert(callableDeclaration, "");
		auto const* function = dynamic_cast(callableDeclaration);
		auto const* event = dynamic_cast(callableDeclaration);
		auto const* modifier = dynamic_cast(callableDeclaration);
		auto typeToString = [](auto const& _var) -> string { return _var->type()->toString(true); };
		vector parameters = callableDeclaration->parameters() | ranges::views::transform(typeToString) | ranges::to>();
		string scopeName;
		if (!function || !function->isFree())
		{
			solAssert(callableDeclaration->annotation().scope, "");
			auto const* parentContract = dynamic_cast(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;
}