/*
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 ranges;
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 | views::values | views::remove_if(notEmpty)).empty(),
"Contracts that are not expected to create other contracts should not be included in _expectedCreatedContractSets."
);
soltestAssert(
(_expectedEdges | views::keys | to()) == (_callGraphs | views::keys | to()) &&
(ranges::views::set_difference(_expectedCreatedContractSets | views::keys, _expectedEdges | 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 | views::keys)
{
soltestAssert(
(ranges::views::set_difference(valueOrDefault(_expectedCreatedContractSets, contractName, {}), _expectedEdges | 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 | views::keys)
{
soltestAssert(_callGraphs.at(contractName) != nullptr, "");
CallGraph const& callGraph = *_callGraphs.at(contractName);
edges[contractName] = edgeNames(callGraph.edges);
if (!callGraph.createdContracts.empty())
createdContractSets[contractName] = callGraph.createdContracts | views::transform(getContractName) | to>();
if (!callGraph.emittedEvents.empty())
emittedEventSets[contractName] = callGraph.emittedEvents | views::transform(eventToString) | 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 | views::join(", ") | 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