/*
	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
/// Unit tests for libsolidity/analysis/FunctionCallGraph.h
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace solidity::langutil;
using namespace solidity::frontend;
using EdgeMap = map<
	CallGraph::Node,
	set,
	CallGraph::CompareByID
>;
using EdgeNames = set>;
using CallGraphMap = map;
namespace
{
unique_ptr parseAndAnalyzeContracts(string _sourceCode)
{
	ReadCallback::Callback fileReader = [](string const&, string const&)
	{
		soltestAssert(false, "For simplicity this test suite supports only files without imports.");
		return ReadCallback::Result{true, ""};
	};
	auto compilerStack = make_unique(fileReader);
	compilerStack->setSources({{"", _sourceCode}});
	// NOTE: The code in test cases is expected to be correct so we can keep error handling simple
	// here and just assert that there are no errors.
	bool success = compilerStack->parseAndAnalyze();
	soltestAssert(success, "");
	soltestAssert(
		ranges::all_of(
			compilerStack->ast("").nodes(),
			[](auto const& _node){ return !dynamic_cast(_node.get()); }
		),
		"For simplicity this test suite supports only files without imports."
	);
	return compilerStack;
}
EdgeNames edgeNames(EdgeMap const& _edgeMap)
{
	EdgeNames names;
	for (auto const& [edgeStart, allEnds]: _edgeMap)
		for (auto const& edgeEnd: allEnds)
			names.emplace(toString(edgeStart), toString(edgeEnd));
	return names;
}
tuple collectGraphs(CompilerStack const& _compilerStack)
{
	soltestAssert(!_compilerStack.hasError(), "");
	tuple graphs;
	for (string const& fullyQualifiedContractName: _compilerStack.contractNames())
	{
		soltestAssert(get<0>(graphs).count(fullyQualifiedContractName) == 0 && get<1>(graphs).count(fullyQualifiedContractName) == 0, "");
		// This relies on two assumptions: (1) CompilerStack received an empty string as a path for
		// the contract and (2) contracts used in test cases have no imports.
		soltestAssert(fullyQualifiedContractName.size() > 0 && fullyQualifiedContractName[0] == ':', "");
		string contractName = fullyQualifiedContractName.substr(1);
		get<0>(graphs).emplace(contractName, _compilerStack.contractDefinition(fullyQualifiedContractName).annotation().creationCallGraph->get());
		get<1>(graphs).emplace(contractName, _compilerStack.contractDefinition(fullyQualifiedContractName).annotation().deployedCallGraph->get());
	}
	return graphs;
}
void checkCallGraphExpectations(
	CallGraphMap const& _callGraphs,
	map const& _expectedEdges,
	map> const& _expectedCreatedContractSets = {},
	map> const& _expectedEmittedEventSets = {}
)
{
	auto getContractName = [](ContractDefinition const* _contract){ return _contract->name(); };
	auto eventToString = [](EventDefinition const* _event){ return toString(CallGraph::Node(_event)); };
	auto notEmpty = [](set const& _set){ return !_set.empty(); };
	soltestAssert(
		(_expectedCreatedContractSets | ranges::views::values | ranges::views::remove_if(notEmpty)).empty(),
		"Contracts that are not expected to create other contracts should not be included in _expectedCreatedContractSets."
	);
	soltestAssert(
		(_expectedEdges | ranges::views::keys | ranges::to()) == (_callGraphs | ranges::views::keys | ranges::to()) &&
		(ranges::views::set_difference(_expectedCreatedContractSets | ranges::views::keys, _expectedEdges | ranges::views::keys)).empty(),
		"Contracts listed in expectations do not match contracts actually found in the source file or in other expectations."
	);
	for (string const& contractName: _expectedEdges | ranges::views::keys)
	{
		soltestAssert(
			(ranges::views::set_difference(valueOrDefault(_expectedCreatedContractSets, contractName, {}), _expectedEdges | ranges::views::keys)).empty(),
			"Inconsistent expectations: contract expected to be created but not to be present in the source file."
		);
	}
	map edges;
	map> createdContractSets;
	map> emittedEventSets;
	for (string const& contractName: _expectedEdges | ranges::views::keys)
	{
		soltestAssert(_callGraphs.at(contractName) != nullptr, "");
		CallGraph const& callGraph = *_callGraphs.at(contractName);
		edges[contractName] = edgeNames(callGraph.edges);
		if (!callGraph.bytecodeDependency.empty())
			createdContractSets[contractName] = callGraph.bytecodeDependency |
				ranges::views::keys |
				ranges::views::transform(getContractName) |
				ranges::to>();
		if (!callGraph.emittedEvents.empty())
			emittedEventSets[contractName] = callGraph.emittedEvents |
				 ranges::views::transform(eventToString) |
				 ranges::to>();
	}
	BOOST_CHECK_EQUAL(edges, _expectedEdges);
	BOOST_CHECK_EQUAL(createdContractSets, _expectedCreatedContractSets);
	BOOST_CHECK_EQUAL(emittedEventSets, _expectedEmittedEventSets);
}
ostream& operator<<(ostream& _out, EdgeNames const& _edgeNames)
{
	for (auto const& [from, to]: _edgeNames)
		_out << "    " << from << " -> " << to << endl;
	return _out;
}
ostream& operator<<(ostream& _out, set const& _set)
{
	_out << "{" << (_set | ranges::views::join(", ") | ranges::to()) << "}";
	return _out;
}
ostream& operator<<(ostream& _out, map const& _edgeSets)
{
	// Extra newline for error report readability. Otherwise the first line does not start at the first column.
	_out << endl;
	for (auto const &[contractName, edges]: _edgeSets)
	{
		_out << contractName << ":" << endl;
		_out << edges;
	}
	return _out;
}
ostream& operator<<(ostream& _out, map> const& _map)
{
	// Extra newline for error report readability. Otherwise the first line does not start at the first column.
	_out << endl;
	for (auto const &[key, value]: _map)
		_out << key << ": " << value << endl;
	return _out;
}
} // namespace
namespace boost::test_tools::tt_detail
{
// Boost won't find find the << operator unless we put it in the std namespace which is illegal.
// The recommended solution is to overload print_log_value<> struct and make it use our operator.
template<>
struct print_log_value
{
	void operator()(std::ostream& _output, EdgeNames const& _edgeNames) { ::operator<<(_output, _edgeNames); }
};
template<>
struct print_log_value>
{
	void operator()(std::ostream& _output, set const& _set) { ::operator<<(_output, _set); }
};
template<>
struct print_log_value