From 051995a373272b4ac53fa24d9ff86bbecc83d994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 28 Jan 2021 21:11:30 +0100 Subject: [PATCH] Test suite for FunctionCallGraph --- test/CMakeLists.txt | 1 + .../analysis/FunctionCallGraph.cpp | 2005 +++++++++++++++++ 2 files changed, 2006 insertions(+) create mode 100644 test/libsolidity/analysis/FunctionCallGraph.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index efdc3e7bb..2e9fbbc4e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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/") diff --git a/test/libsolidity/analysis/FunctionCallGraph.cpp b/test/libsolidity/analysis/FunctionCallGraph.cpp new file mode 100644 index 000000000..506509ea9 --- /dev/null +++ b/test/libsolidity/analysis/FunctionCallGraph.cpp @@ -0,0 +1,2005 @@ +/* + 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 +#include + +using namespace std; +using namespace ranges; +using namespace solidity::langutil; +using namespace solidity::frontend; + +using EdgeMap = map< + FunctionCallGraphBuilder::Node, + set, + FunctionCallGraphBuilder::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.creationCallGraph(fullyQualifiedContractName)); + get<1>(graphs).emplace(contractName, &_compilerStack.deployedCallGraph(fullyQualifiedContractName)); + } + + 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(FunctionCallGraphBuilder::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, ""); + FunctionCallGraphBuilder::ContractCallGraph 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& edge: _edgeNames | to() | actions::sort(std::less())) + _out << " " << get<0>(edge) << " -> " << get<1>(edge) << 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> +{ + void operator()(std::ostream& _output, map const& _edgeSets) { ::operator<<(_output, _edgeSets); } +}; + +template<> +struct print_log_value>> +{ + void operator()(std::ostream& _output, map> const& _map) { ::operator<<(_output, _map); } +}; + +} // namespace boost::test_tools::tt_detail + +namespace solidity::frontend::test +{ + +BOOST_AUTO_TEST_SUITE(FunctionCallGraphTest) + +BOOST_AUTO_TEST_CASE(only_definitions) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() {} + + library L { + function ext() external {} + function pub() public {} + function inr() internal {} + function prv() private {} + } + + contract C { + function ext() external {} + function pub() public {} + function inr() internal {} + function prv() private {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + {"Entry", "function C.pub()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + {"Entry", "function L.pub()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(ordinary_calls) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() {} + + library L { + function ext() external { pub(); inr(); } + function pub() public { inr(); } + function inr() internal { prv(); } + function prv() private { free(); free(); } + } + + contract C { + function ext() external { pub(); } + function pub() public { inr(); prv(); free(); } + function inr() internal { prv(); L.inr(); } + function prv() private { free(); free(); } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + {"Entry", "function C.pub()"}, + {"function C.ext()", "function C.pub()"}, + {"function C.pub()", "function C.inr()"}, + {"function C.pub()", "function C.prv()"}, + {"function C.pub()", "function free()"}, + {"function C.inr()", "function C.prv()"}, + {"function C.inr()", "function L.inr()"}, + {"function C.prv()", "function free()"}, + {"function L.inr()", "function L.prv()"}, + {"function L.prv()", "function free()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + {"Entry", "function L.pub()"}, + {"function L.ext()", "function L.pub()"}, + {"function L.ext()", "function L.inr()"}, + {"function L.pub()", "function L.inr()"}, + {"function L.inr()", "function L.prv()"}, + {"function L.prv()", "function free()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(call_chains_through_externals) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + library L { + function ext() external { C(address(0x0)).ext(); } + function pub() public {} + function inr() internal {} + function prv() private {} + } + + contract C { + function ext() external {} + function pub() public {} + function inr() internal {} + function prv() private {} + + function ext2() external { this.ext(); this.pub(); L.ext(); L.pub(); } + function pub2() public { this.ext(); this.pub(); L.ext(); L.pub(); } + function pub3() public { C(address(0x0)).ext(); } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + {"Entry", "function C.ext2()"}, + {"Entry", "function C.pub()"}, + {"Entry", "function C.pub2()"}, + {"Entry", "function C.pub3()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + {"Entry", "function L.pub()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(calls_from_constructors) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() returns (uint) {} + + library L { + function ext() external {} + } + + contract C { + constructor() { this.ext(); inr(); L.ext(); free(); } + + function ext() external {} + function inr() internal {} + } + + contract D { + uint a = this.ext(); + uint b = inr(); + uint c = free(); + + function ext() external returns (uint) {} + function inr() internal returns (uint) {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", { + {"Entry", "constructor of C"}, + {"constructor of C", "function C.inr()"}, + {"constructor of C", "function free()"}, + }}, + {"D", { + {"Entry", "function D.inr()"}, + {"Entry", "function free()"}, + }}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + }}, + {"D", { + {"Entry", "function D.ext()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(calls_to_constructors) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() { new D(); } + + library L { + function ext() external { new C(); new D(); inr(); } + function inr() internal { new C(); new D(); free(); } + } + + contract C { + constructor() { new D(); } + + function ext() external { new D(); inr(); } + function inr() internal { new D(); free(); } + } + + contract D {} + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", { + {"Entry", "constructor of C"}, + }}, + {"D", {}}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + {"function C.ext()", "function C.inr()"}, + {"function C.inr()", "function free()"}, + }}, + {"D", {}}, + {"L", { + {"Entry", "function L.ext()"}, + {"function L.ext()", "function L.inr()"}, + {"function L.inr()", "function free()"}, + }}, + }; + + map> expectedCreatedContractsAtCreation = { + {"C", {"D"}}, + }; + map> expectedCreatedContractsAfterDeployment = { + {"C", {"D"}}, + {"L", {"C", "D"}}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, expectedCreatedContractsAtCreation); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, expectedCreatedContractsAfterDeployment); +} + +BOOST_AUTO_TEST_CASE(inherited_constructors) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() {} + + library L { + function ext() external { inr(); } + function inr() internal { free(); } + } + + contract C { + constructor() { inrC(); free(); } + + function extC() external returns (uint) { inrC(); } + function inrC() internal returns (uint) { free(); } + } + + contract D { + constructor() { L.ext(); } + } + + contract E is C { + uint e2 = this.extE(); + uint i2 = inrE(); + + function extE() external returns (uint) { inrE(); } + function inrE() internal returns (uint) { free(); } + } + + contract F is C, D(), E { + uint e3 = this.extF(); + uint i3 = inrF(); + + constructor() E() C() {} + + function extF() external returns (uint) { inrF(); } + function inrF() internal returns (uint) { free(); } + } + + contract G is E() { + function extG() external returns (uint) { new F(); } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", { + {"Entry", "constructor of C"}, + {"constructor of C", "function C.inrC()"}, + {"constructor of C", "function free()"}, + {"function C.inrC()", "function free()"}, + }}, + {"D", { + {"Entry", "constructor of D"}, + }}, + {"E", { + {"Entry", "constructor of C"}, + {"Entry", "function E.inrE()"}, + {"constructor of C", "function C.inrC()"}, + {"constructor of C", "function free()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + }}, + {"F", { + {"Entry", "constructor of C"}, + {"Entry", "constructor of D"}, + {"Entry", "constructor of F"}, + {"Entry", "function E.inrE()"}, + {"Entry", "function F.inrF()"}, + {"constructor of C", "function C.inrC()"}, + {"constructor of C", "function free()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + {"function F.inrF()", "function free()"}, + }}, + {"G", { + {"Entry", "constructor of C"}, + {"Entry", "function E.inrE()"}, + {"constructor of C", "function C.inrC()"}, + {"constructor of C", "function free()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + }}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.extC()"}, + {"function C.extC()", "function C.inrC()"}, + {"function C.inrC()", "function free()"}, + }}, + {"D", {}}, + {"E", { + {"Entry", "function C.extC()"}, + {"Entry", "function E.extE()"}, + {"function C.extC()", "function C.inrC()"}, + {"function E.extE()", "function E.inrE()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + }}, + {"F", { + {"Entry", "function C.extC()"}, + {"Entry", "function E.extE()"}, + {"Entry", "function F.extF()"}, + {"function C.extC()", "function C.inrC()"}, + {"function E.extE()", "function E.inrE()"}, + {"function F.extF()", "function F.inrF()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + {"function F.inrF()", "function free()"}, + }}, + {"G", { + {"Entry", "function C.extC()"}, + {"Entry", "function E.extE()"}, + {"Entry", "function G.extG()"}, + {"function C.extC()", "function C.inrC()"}, + {"function E.extE()", "function E.inrE()"}, + {"function C.inrC()", "function free()"}, + {"function E.inrE()", "function free()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + {"function L.ext()", "function L.inr()"}, + {"function L.inr()", "function free()"}, + }}, + }; + + map> expectedCreatedContractsAtCreation = {}; + map> expectedCreatedContractsAfterDeployment = { + {"G", {"F"}}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, expectedCreatedContractsAtCreation); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, expectedCreatedContractsAfterDeployment); +} + +BOOST_AUTO_TEST_CASE(inheritance_specifiers) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function fD() returns (uint) {} + function fE() returns (uint) {} + function fFD() returns (uint) {} + function fFE() returns (uint) {} + function fG() returns (uint) {} + + function fVarC() returns (uint) {} + function fVarD() returns (uint) {} + function fVarE() returns (uint) {} + function fVarF() returns (uint) {} + + contract C { + uint c = fVarC(); + + constructor (uint) {} + } + + contract D is C(fD()) { + uint d = fVarD(); + + constructor (uint) {} + } + + abstract contract E is C { + uint e = fVarE(); + + constructor (uint) {} + } + + contract F is D(fFD()), E { + uint f = fVarF(); + + constructor (uint) E(fFE()) {} + } + + contract G is D(fG()), E(fG()) {} + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", { + {"Entry", "constructor of C"}, + {"Entry", "function fVarC()"}, + }}, + {"D", { + {"Entry", "constructor of C"}, + {"Entry", "constructor of D"}, + {"Entry", "function fVarC()"}, + {"Entry", "function fVarD()"}, + {"constructor of D", "function fD()"}, + }}, + {"E", { + {"Entry", "constructor of C"}, + {"Entry", "constructor of E"}, + {"Entry", "function fVarC()"}, + {"Entry", "function fVarE()"}, + }}, + {"F", { + {"Entry", "constructor of C"}, + {"Entry", "constructor of D"}, + {"Entry", "constructor of E"}, + {"Entry", "constructor of F"}, + {"Entry", "function fVarC()"}, + {"Entry", "function fVarD()"}, + {"Entry", "function fVarE()"}, + {"Entry", "function fVarF()"}, + {"constructor of D", "function fD()"}, + {"constructor of F", "function fFD()"}, + {"constructor of F", "function fFE()"}, + }}, + {"G", { + {"Entry", "constructor of C"}, + {"Entry", "constructor of D"}, + {"Entry", "constructor of E"}, + // G, unlike F, has no constructor so fG() gets an edge from Entry. + {"Entry", "function fG()"}, + {"Entry", "function fVarC()"}, + {"Entry", "function fVarD()"}, + {"Entry", "function fVarE()"}, + {"constructor of D", "function fD()"}, + }}, + }; + + map expectedDeployedEdges = { + {"C", {}}, + {"D", {}}, + {"E", {}}, + {"F", {}}, + {"G", {}}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(inherited_functions_virtual_and_super) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + function f() internal {} + function g() internal virtual {} + function h() internal virtual {} + + function ext() external virtual {} + } + + contract D { + function h() internal virtual {} + + function ext() external virtual {} + } + + contract E is C, D { + function g() internal override {} + function h() internal override(C, D) {} + function i() internal {} + + function ext() external override(C, D) {} + + function callF() external { f(); } + function callG() external { g(); } + function callH() external { h(); } + function callI() external { i(); } + function callCF() external { C.f(); } + function callCG() external { C.g(); } + function callCH() external { C.h(); } + function callDH() external { D.h(); } + function callEI() external { E.i(); } + function callSuperF() external { super.f(); } + function callSuperG() external { super.g(); } + function callSuperH() external { super.h(); } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + {"E", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + }}, + {"D", { + {"Entry", "function D.ext()"}, + }}, + {"E", { + {"Entry", "function E.callF()"}, + {"Entry", "function E.callG()"}, + {"Entry", "function E.callH()"}, + {"Entry", "function E.callI()"}, + {"Entry", "function E.callCF()"}, + {"Entry", "function E.callCG()"}, + {"Entry", "function E.callCH()"}, + {"Entry", "function E.callDH()"}, + {"Entry", "function E.callEI()"}, + {"Entry", "function E.callSuperF()"}, + {"Entry", "function E.callSuperG()"}, + {"Entry", "function E.callSuperH()"}, + {"Entry", "function E.ext()"}, + {"function E.callF()", "function C.f()"}, + {"function E.callG()", "function E.g()"}, + {"function E.callH()", "function E.h()"}, + {"function E.callI()", "function E.i()"}, + {"function E.callCF()", "function C.f()"}, + {"function E.callCG()", "function C.g()"}, + {"function E.callCH()", "function C.h()"}, + {"function E.callDH()", "function D.h()"}, + {"function E.callEI()", "function E.i()"}, + {"function E.callSuperF()", "function C.f()"}, + {"function E.callSuperG()", "function C.g()"}, + {"function E.callSuperH()", "function D.h()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(overloaded_functions) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + enum E {E1, E2, E3} + + function free() {} + function free(uint) {} + function free(bytes memory) {} + function free(E) {} + + contract C { + function f(E) internal {} + function f(bool) external {} + } + + contract D is C { + function ext1() external { free(); free(123); free("123"); } + function ext2() external { f(); f(123); f("123"); } + function ext3() external { free(E.E2); f(E.E2); } + function ext4() external { this.f(false); } + + function f() internal {} + function f(uint) internal {} + function f(bytes memory) internal {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.f(bool)"}, + }}, + {"D", { + {"Entry", "function C.f(bool)"}, + {"Entry", "function D.ext1()"}, + {"Entry", "function D.ext2()"}, + {"Entry", "function D.ext3()"}, + {"Entry", "function D.ext4()"}, + {"function D.ext1()", "function free()"}, + {"function D.ext1()", "function free(uint256)"}, + {"function D.ext1()", "function free(bytes)"}, + {"function D.ext2()", "function D.f()"}, + {"function D.ext2()", "function D.f(uint256)"}, + {"function D.ext2()", "function D.f(bytes)"}, + {"function D.ext3()", "function free(enum E)"}, + {"function D.ext3()", "function C.f(enum E)"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(modifiers) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + library L { + modifier m() { g(); _; } + + function f() m internal {} + function g() internal {} + } + + contract C { + modifier m1() virtual { _; } + + function q() m1 internal virtual { L.f(); } + } + + contract D is C { + modifier m2() { q(); _; new C(); } + + function p() m2 internal { C.q(); } + function q() m2 internal override virtual {} + } + + contract E is D { + modifier m1() override { _; } + modifier m3() { p(); _; } + + constructor() D() m1 E.m3 {} + function ext() external m1 E.m3 { inr(); } + function inr() internal m1 E.m3 { L.f(); } + + function q() internal override {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + {"L", {}}, + {"E", { + {"Entry", "constructor of E"}, + {"constructor of E", "modifier E.m1"}, + {"constructor of E", "modifier E.m3"}, + {"function C.q()", "modifier E.m1"}, + {"function C.q()", "function L.f()"}, + {"function D.p()", "modifier D.m2"}, + {"function D.p()", "function C.q()"}, + {"function L.f()", "modifier L.m"}, + {"modifier L.m", "function L.g()"}, + {"modifier D.m2", "function E.q()"}, + {"modifier E.m3", "function D.p()"}, + }}, + }; + + map expectedDeployedEdges = { + {"C", {}}, + {"D", {}}, + {"L", {}}, + {"E", { + {"Entry", "function E.ext()"}, + {"function C.q()", "modifier E.m1"}, + {"function C.q()", "function L.f()"}, + {"function D.p()", "modifier D.m2"}, + {"function D.p()", "function C.q()"}, + {"function L.f()", "modifier L.m"}, + {"function E.ext()", "function E.inr()"}, + {"function E.ext()", "modifier E.m1"}, + {"function E.ext()", "modifier E.m3"}, + {"function E.inr()", "modifier E.m1"}, + {"function E.inr()", "modifier E.m3"}, + {"function E.inr()", "function L.f()"}, + {"modifier L.m", "function L.g()"}, + {"modifier D.m2", "function E.q()"}, + {"modifier E.m3", "function D.p()"}, + }}, + }; + + map> expectedCreatedContractsAtCreation = {{"E", {"C"}}}; + map> expectedCreatedContractsAfterDeployment = {{"E", {"C"}}}; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, expectedCreatedContractsAtCreation); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, expectedCreatedContractsAfterDeployment); +} + +BOOST_AUTO_TEST_CASE(events) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free() { emit L.Ev(); } + + library L { + event Ev(); + event Ev(bytes4, string indexed); + + function ext() external { emit Ev(); } + function inr() internal { emit Ev(0x12345678, "a"); emit L.Ev(); } + } + + contract C { + event EvC(uint) anonymous; + + modifier m() { emit EvC(1); _; } + } + + contract D is C { + event EvD1(uint); + event EvD2(uint); + + function ext() m external { emit D.EvD1(1); emit EvC(f()); inr(); } + function inr() m internal { emit EvD1(1); emit C.EvC(f()); L.inr(); free(); EvD2; } + + function f() internal returns (uint) {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + {"L", {}}, + }; + + map expectedDeployedEdges = { + {"C", {}}, + {"D", { + {"Entry", "function D.ext()"}, + {"function D.ext()", "function D.inr()"}, + {"function D.ext()", "modifier C.m"}, + {"function D.ext()", "function D.f()"}, + {"function D.inr()", "function L.inr()"}, + {"function D.inr()", "function free()"}, + {"function D.inr()", "modifier C.m"}, + {"function D.inr()", "function D.f()"}, + }}, + {"L", { + {"Entry", "function L.ext()"}, + }}, + }; + + map> expectedCreationEvents = {}; + map> expectedDeployedEvents = { + {"D", { + "event D.EvD1(uint256)", + "event C.EvC(uint256)", + "event L.Ev(bytes4,string)", + "event L.Ev()", + }}, + {"L", { + "event L.Ev()", + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, {}, expectedCreationEvents); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, {}, expectedDeployedEvents); +} + +BOOST_AUTO_TEST_CASE(cycles) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free1() { free1(); } + function free2() { free3(); } + function free3() { free2(); } + + library L { + function inr1() internal { inr1(); } + function inr2() internal { inr3(); } + function inr3() internal { inr2(); } + } + + contract C { + function virt() internal virtual { virt(); } + } + + contract D is C { + function init() external { this.ext1(); inr1(); inr2(); L.inr1(); L.inr2(); free1(); free2(); virt(); } + + function ext1() external { this.ext1(); } + function ext2() external { this.ext3(); } + function ext3() external { this.ext2(); } + function inr1() internal { inr1(); } + function inr2() internal { inr3(); } + function inr3() internal { inr2(); } + function inr3(uint) internal {} + + function virt() internal override { C.virt(); } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"L", {}}, + {"C", {}}, + {"D", {}}, + }; + + map expectedDeployedEdges = { + {"L", {}}, + {"C", {}}, + {"D", { + {"Entry", "function D.init()"}, + {"Entry", "function D.ext1()"}, + {"Entry", "function D.ext2()"}, + {"Entry", "function D.ext3()"}, + {"function D.init()", "function D.inr1()"}, + {"function D.init()", "function D.inr2()"}, + {"function D.init()", "function D.virt()"}, + {"function D.init()", "function L.inr1()"}, + {"function D.init()", "function L.inr2()"}, + {"function D.init()", "function free1()"}, + {"function D.init()", "function free2()"}, + {"function D.inr1()", "function D.inr1()"}, + {"function D.inr2()", "function D.inr3()"}, + {"function D.inr3()", "function D.inr2()"}, + {"function D.virt()", "function C.virt()"}, + {"function C.virt()", "function D.virt()"}, + {"function L.inr1()", "function L.inr1()"}, + {"function L.inr2()", "function L.inr3()"}, + {"function L.inr3()", "function L.inr2()"}, + {"function free1()", "function free1()"}, + {"function free2()", "function free3()"}, + {"function free3()", "function free2()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(interfaces_and_abstract_contracts) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + interface I { + event Ev(uint); + modifier m() virtual; + + function ext1() external; + function ext2() external; + } + + interface J is I { + function ext2() external override; + function ext3() external; + } + + abstract contract C is J { + function ext3() external override virtual; + function ext4() external { inr2();} + function inr1() internal virtual; + function inr2() m internal { inr1(); this.ext1(); this.ext2(); this.ext3(); } + } + + contract D is C { + function ext1() public override { emit I.Ev(1); inr1(); inr2(); } + function ext2() external override { I(this).ext1(); } + function ext3() external override {} + function inr1() internal override {} + + modifier m() override { _; } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"I", {}}, + {"J", {}}, + {"C", {}}, + {"D", {}}, + }; + + map expectedDeployedEdges = { + {"I", { + {"Entry", "function I.ext1()"}, + {"Entry", "function I.ext2()"}, + }}, + {"J", { + {"Entry", "function I.ext1()"}, + {"Entry", "function J.ext2()"}, + {"Entry", "function J.ext3()"}, + }}, + {"C", { + {"Entry", "function I.ext1()"}, + {"Entry", "function J.ext2()"}, + {"Entry", "function C.ext3()"}, + {"Entry", "function C.ext4()"}, + {"function C.ext4()", "function C.inr2()"}, + {"function C.inr2()", "function C.inr1()"}, + {"function C.inr2()", "modifier I.m"}, + }}, + {"D", { + {"Entry", "function D.ext1()"}, + {"Entry", "function D.ext2()"}, + {"Entry", "function D.ext3()"}, + {"Entry", "function C.ext4()"}, + {"function C.ext4()", "function C.inr2()"}, + {"function C.inr2()", "function D.inr1()"}, + {"function C.inr2()", "modifier D.m"}, + {"function D.ext1()", "function D.inr1()"}, + {"function D.ext1()", "function C.inr2()"}, + }}, + }; + + map> expectedCreationEvents = {}; + map> expectedDeployedEvents = { + {"D", { + "event I.Ev(uint256)", + }}, + }; + + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, {}, expectedCreationEvents); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, {}, expectedDeployedEvents); +} + +BOOST_AUTO_TEST_CASE(indirect_calls) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free1() {} + function free2() {} + function free3() {} + + library L { + function ext() external {} + function inr1() internal {} + function inr2() internal {} + function inr3() internal {} + + function access() public { + free1; + inr1; + L.ext; + } + + function expression() public { + (free2)(); + (inr2)(); + } + } + + contract C { + function ext1() external {} + function ext2() external {} + function ext3() external {} + function inr1() internal {} + function inr2() internal {} + function inr3() internal {} + + function access() public { + this.ext1; + inr1; + free1; + L.inr1; + L.ext; + } + + function expression() public { + (this.ext2)(); + (inr2)(); + (free2)(); + (L.inr2)(); + (L.ext)(); + } + } + + contract D is C { + constructor() { + access(); + expression(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"L", {}}, + {"C", {}}, + {"D", { + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function C.inr1()"}, + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"Entry", "constructor of D"}, + {"constructor of D", "function C.access()"}, + {"constructor of D", "function C.expression()"}, + {"function C.expression()", "InternalDispatch"}, + }}, + }; + + map expectedDeployedEdges = { + {"L", { + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"Entry", "function L.ext()"}, + {"Entry", "function L.access()"}, + {"Entry", "function L.expression()"}, + {"function L.expression()", "InternalDispatch"}, + }}, + {"C", { + {"InternalDispatch", "function C.inr1()"}, + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"Entry", "function C.ext1()"}, + {"Entry", "function C.ext2()"}, + {"Entry", "function C.ext3()"}, + {"Entry", "function C.access()"}, + {"Entry", "function C.expression()"}, + {"function C.expression()", "InternalDispatch"}, + }}, + {"D", { + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function C.inr1()"}, + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"Entry", "function C.ext1()"}, + {"Entry", "function C.ext2()"}, + {"Entry", "function C.ext3()"}, + {"Entry", "function C.access()"}, + {"Entry", "function C.expression()"}, + {"function C.expression()", "InternalDispatch"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(calls_via_pointers) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + function free1() {} + function free2() {} + function free3() {} + + library L { + function inr1() internal {} + function inr2() internal {} + function inr3() internal {} + + function callPtrs( + function () external e, + function () internal i, + function () internal f, + function () internal l + ) internal + { + e(); + i(); + f(); + l(); + } + } + + contract C { + function ext1() external {} + function ext2() external {} + function ext3() external {} + function inr1() internal {} + function inr2() internal {} + function inr3() internal {} + + function getPtrs2() internal returns ( + function () external, + function () internal, + function () internal, + function () internal + ) + { + return (this.ext2, inr2, free2, L.inr2); + } + + function testLocalVars() public { + (function () external e, function () i, function () f, function () l) = getPtrs2(); + L.callPtrs(e, i, f, l); + } + } + + contract D is C { + function () external m_e = this.ext1; + function () internal m_i = inr1; + function () internal m_f = free1; + function () internal m_l = L.inr1; + function () internal immutable m_imm = inr1; + + function callStatePtrs() internal { + m_e(); + m_i(); + m_f(); + m_l(); + } + + function updateStatePtrs( + function () external e, + function () internal i, + function () internal f, + function () internal l + ) internal + { + m_e = e; + m_i = i; + m_f = f; + m_l = l; + } + + function testStateVars() public { + (function () external e, function () i, function () f, function () l) = getPtrs2(); + updateStatePtrs(e, i, f, l); + callStatePtrs(); + } + + function testImmutablePtr() public { + m_imm(); + } + + constructor() { + testStateVars(); + testLocalVars(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"L", {}}, + {"C", {}}, + {"D", { + {"InternalDispatch", "function C.inr1()"}, + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"Entry", "constructor of D"}, + {"constructor of D", "function C.testLocalVars()"}, + {"constructor of D", "function D.testStateVars()"}, + {"function C.testLocalVars()", "function C.getPtrs2()"}, + {"function C.testLocalVars()", "function L.callPtrs(function () external,function (),function (),function ())"}, + {"function D.testStateVars()", "function C.getPtrs2()"}, + {"function D.testStateVars()", "function D.updateStatePtrs(function () external,function (),function (),function ())"}, + {"function D.testStateVars()", "function D.callStatePtrs()"}, + {"function D.callStatePtrs()", "InternalDispatch"}, + {"function L.callPtrs(function () external,function (),function (),function ())", "InternalDispatch"}, + }}, + }; + + map expectedDeployedEdges = { + {"L", {}}, + {"C", { + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function free2()"}, + {"Entry", "function C.ext1()"}, + {"Entry", "function C.ext2()"}, + {"Entry", "function C.ext3()"}, + {"Entry", "function C.testLocalVars()"}, + {"function C.testLocalVars()", "function C.getPtrs2()"}, + {"function C.testLocalVars()", "function L.callPtrs(function () external,function (),function (),function ())"}, + {"function L.callPtrs(function () external,function (),function (),function ())", "InternalDispatch"}, + }}, + {"D", { + {"InternalDispatch", "function C.inr1()"}, + {"InternalDispatch", "function C.inr2()"}, + {"InternalDispatch", "function L.inr1()"}, + {"InternalDispatch", "function L.inr2()"}, + {"InternalDispatch", "function free1()"}, + {"InternalDispatch", "function free2()"}, + {"Entry", "function C.ext1()"}, + {"Entry", "function C.ext2()"}, + {"Entry", "function C.ext3()"}, + {"Entry", "function C.testLocalVars()"}, + {"Entry", "function D.testStateVars()"}, + {"Entry", "function D.testImmutablePtr()"}, + {"function C.testLocalVars()", "function C.getPtrs2()"}, + {"function C.testLocalVars()", "function L.callPtrs(function () external,function (),function (),function ())"}, + {"function D.testStateVars()", "function C.getPtrs2()"}, + {"function D.testStateVars()", "function D.updateStatePtrs(function () external,function (),function (),function ())"}, + {"function D.testStateVars()", "function D.callStatePtrs()"}, + {"function D.testImmutablePtr()", "InternalDispatch"}, + {"function D.callStatePtrs()", "InternalDispatch"}, + {"function L.callPtrs(function () external,function (),function (),function ())", "InternalDispatch"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(pointer_to_overridden_function) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + function f() internal virtual {} + } + + contract D is C { + function f() internal override {} + + function getF() internal returns (function ()) { + return C.f; + } + + function getSuperF() internal returns (function ()) { + return super.f; + } + + function test1() public { + getF()(); + } + + function test2() public { + getSuperF()(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + }; + + map expectedDeployedEdges = { + {"C", {}}, + {"D", { + {"InternalDispatch", "function C.f()"}, + {"Entry", "function D.test1()"}, + {"Entry", "function D.test2()"}, + {"function D.test1()", "function D.getF()"}, + {"function D.test1()", "InternalDispatch"}, + {"function D.test2()", "function D.getSuperF()"}, + {"function D.test2()", "InternalDispatch"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(pointer_to_nonexistent_function) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + interface I { + function f() external; + } + + abstract contract C is I { + function g() internal virtual; + + function getF() internal returns (function () external) { return this.f; } + function getG() internal returns (function () internal) { return g; } + + function testInterface() public { + getF()(); + getG()(); + } + + function testBadPtr() public { + function () ptr; + ptr(); + } + } + + contract D is C { + function f() public override {} + function g() internal override {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"I", {}}, + {"C", {}}, + {"D", {}}, + }; + + map expectedDeployedEdges = { + {"I", { + {"Entry", "function I.f()"}, + }}, + {"C", { + {"InternalDispatch", "function C.g()"}, + {"Entry", "function C.testInterface()"}, + {"Entry", "function C.testBadPtr()"}, + {"Entry", "function I.f()"}, + {"function C.testInterface()", "function C.getF()"}, + {"function C.testInterface()", "function C.getG()"}, + {"function C.testInterface()", "InternalDispatch"}, + {"function C.testBadPtr()", "InternalDispatch"}, + }}, + {"D", { + {"InternalDispatch", "function D.g()"}, + {"Entry", "function C.testInterface()"}, + {"Entry", "function C.testBadPtr()"}, + {"Entry", "function D.f()"}, + {"function C.testInterface()", "function C.getF()"}, + {"function C.testInterface()", "function C.getG()"}, + {"function C.testInterface()", "InternalDispatch"}, + {"function C.testBadPtr()", "InternalDispatch"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(function_self_reference) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + function f() public returns (bool ret) { + return f == f; + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.f()"}, + {"InternalDispatch", "function C.f()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(pointer_cycle) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + function () ptr = f; + + function f() internal { ptr(); } + + function test() public { + ptr(); + } + } + )"s); + + map expectedCreationEdges = { + {"C", { + {"InternalDispatch", "function C.f()"}, + {"function C.f()", "InternalDispatch"}, + }}, + }; + + map expectedDeployedEdges = { + {"C", { + {"InternalDispatch", "function C.f()"}, + {"Entry", "function C.test()"}, + {"function C.test()", "InternalDispatch"}, + {"function C.f()", "InternalDispatch"}, + }}, + }; + tuple graphs = collectGraphs(*compilerStack); + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(using_for) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + struct S { + uint x; + } + + library L { + function ext(S memory _s) external {} + function inr(S memory _s) internal {} + } + + contract C { + using L for S; + + function test() public { + S memory s = S(42); + + s.ext(); + s.inr(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"L", {}}, + {"C", {}}, + }; + + map expectedDeployedEdges = { + {"L", { + {"Entry", "function L.ext(struct S)"}, + }}, + {"C", { + {"Entry", "function C.test()"}, + {"function C.test()", "function L.inr(struct S)"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(getters) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + uint public variable; + uint[][] public array; + mapping(bytes => bytes) public map; + + function test() public { + this.variable(); + this.array(1, 2); + this.map("value"); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + }; + + map expectedDeployedEdges = { + {"C", {{"Entry", "function C.test()"}}}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(fallback_and_receive) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + function ext() external {} + function inr() internal {} + + fallback() external { + this.ext(); + inr(); + } + + receive() external payable { + this.ext(); + inr(); + } + } + + contract D { + fallback(bytes calldata) external returns (bytes memory) {} + + function test() public { + (bool success, bytes memory result) = address(this).call("abc"); + } + } + + contract E is C {} + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + {"E", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "function C.ext()"}, + {"Entry", "receive of C"}, + {"Entry", "fallback of C"}, + {"fallback of C", "function C.inr()"}, + {"receive of C", "function C.inr()"}, + }}, + {"D", { + {"Entry", "function D.test()"}, + {"Entry", "fallback of D"}, + }}, + {"E", { + {"Entry", "function C.ext()"}, + {"Entry", "fallback of C"}, + {"Entry", "receive of C"}, + {"fallback of C", "function C.inr()"}, + {"receive of C", "function C.inr()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(virtual_fallback_and_receive) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + contract C { + fallback() external virtual {} + receive() external payable virtual {} + } + + contract D is C {} + + contract E is D { + fallback() external virtual override {} + receive() external payable virtual override {} + } + + contract F is E { + fallback() external override {} + receive() external payable override {} + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"C", {}}, + {"D", {}}, + {"E", {}}, + {"F", {}}, + }; + + map expectedDeployedEdges = { + {"C", { + {"Entry", "receive of C"}, + {"Entry", "fallback of C"}, + }}, + {"D", { + {"Entry", "receive of C"}, + {"Entry", "fallback of C"}, + }}, + {"E", { + {"Entry", "receive of E"}, + {"Entry", "fallback of E"}, + }}, + {"F", { + {"Entry", "receive of F"}, + {"Entry", "fallback of F"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(builtins) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + interface I {} + + contract C { + function accessBuiltin() public payable { + abi.decode; + abi.encode; + abi.encodePacked; + abi.encodeWithSelector; + abi.encodeWithSignature; + block.chainid; + block.coinbase; + block.difficulty; + block.gaslimit; + block.number; + block.timestamp; + gasleft; + msg.data; + msg.sender; + msg.value; + tx.gasprice; + tx.origin; + blockhash; + keccak256; + sha256; + ripemd160; + ecrecover; + addmod; + mulmod; + this; + super; + selfdestruct; + address(0).balance; + address(0).code; + address(0).codehash; + payable(0).send; + payable(0).transfer; + address(0).call; + address(0).delegatecall; + address(0).staticcall; + type(C).name; + type(I).interfaceId; + type(uint).min; + type(uint).max; + assert; + } + + function callBuiltin() public payable { + bytes memory data; + + abi.decode(data, (uint)); + abi.encode(0); + abi.encodePacked(data); + abi.encodeWithSelector(0x12345678); + abi.encodeWithSignature("abc"); + gasleft(); + blockhash(0); + keccak256(data); + sha256(data); + ripemd160(data); + ecrecover(0x0, 0, 0, 0); + addmod(1, 2, 3); + mulmod(1, 2, 3); + selfdestruct(payable(0)); + payable(0).send(0); + payable(0).transfer(0); + address(0).call(data); + address(0).delegatecall(data); + address(0).staticcall(data); + assert(true); + require(true); + revert(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"I", {}}, + {"C", {}}, + }; + + map expectedDeployedEdges = { + {"I", {}}, + {"C", { + {"Entry", "function C.accessBuiltin()"}, + {"Entry", "function C.callBuiltin()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_CASE(conversions_and_struct_array_constructors) +{ + unique_ptr compilerStack = parseAndAnalyzeContracts(R"( + interface I {} + + enum E {A, B, C} + + struct S { + uint a; + E b; + } + + contract C is I { + uint[] u; + + function convert() public payable { + uint(0); + int(0); + bool(true); + bytes16(0); + payable(address(0)); + E(0); + C(address(C(address(0)))); + I(C(address(0))); + + bytes memory b; + string(b); + bytes(b); + } + + function create() public payable { + S(1, E.A); + + uint[3] memory u3; + uint[3](u3); + uint[](new uint[](3)); + } + + function pushPop() public payable { + u.push(); + u.push(1); + u.pop(); + } + } + )"s); + tuple graphs = collectGraphs(*compilerStack); + + map expectedCreationEdges = { + {"I", {}}, + {"C", {}}, + }; + + map expectedDeployedEdges = { + {"I", {}}, + {"C", { + {"Entry", "function C.convert()"}, + {"Entry", "function C.create()"}, + {"Entry", "function C.pushPop()"}, + }}, + }; + + checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges); + checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace solidity::frontend::test