mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
2167 lines
58 KiB
C++
2167 lines
58 KiB
C++
/*
|
|
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
|
|
|
|
/// Unit tests for libsolidity/analysis/FunctionCallGraph.h
|
|
|
|
#include <libsolidity/analysis/FunctionCallGraph.h>
|
|
|
|
#include <test/Common.h>
|
|
#include <test/libsolidity/util/SoltestErrors.h>
|
|
|
|
#include <libsolutil/CommonData.h>
|
|
|
|
#include <libsolidity/ast/AST.h>
|
|
#include <libsolidity/interface/CompilerStack.h>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <range/v3/action/sort.hpp>
|
|
#include <range/v3/algorithm/all_of.hpp>
|
|
#include <range/v3/range/conversion.hpp>
|
|
#include <range/v3/view/join.hpp>
|
|
#include <range/v3/view/map.hpp>
|
|
#include <range/v3/view/remove_if.hpp>
|
|
#include <range/v3/view/set_algorithm.hpp>
|
|
#include <range/v3/view/transform.hpp>
|
|
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
using namespace solidity::langutil;
|
|
using namespace solidity::frontend;
|
|
|
|
using EdgeMap = map<
|
|
CallGraph::Node,
|
|
set<CallGraph::Node, CallGraph::CompareByID>,
|
|
CallGraph::CompareByID
|
|
>;
|
|
using EdgeNames = set<tuple<string, string>>;
|
|
using CallGraphMap = map<string, CallGraph const*>;
|
|
|
|
namespace
|
|
{
|
|
|
|
unique_ptr<CompilerStack> 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<CompilerStack>(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<ImportDirective const*>(_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<CallGraphMap, CallGraphMap> collectGraphs(CompilerStack const& _compilerStack)
|
|
{
|
|
soltestAssert(!_compilerStack.hasError(), "");
|
|
|
|
tuple<CallGraphMap, CallGraphMap> 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<string, EdgeNames> const& _expectedEdges,
|
|
map<string, set<string>> const& _expectedCreatedContractSets = {},
|
|
map<string, set<string>> const& _expectedEmittedEventSets = {}
|
|
)
|
|
{
|
|
auto getContractName = [](ContractDefinition const* _contract){ return _contract->name(); };
|
|
auto eventToString = [](EventDefinition const* _event){ return toString(CallGraph::Node(_event)); };
|
|
auto notEmpty = [](set<string> 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<set>()) == (_callGraphs | ranges::views::keys | ranges::to<set>()) &&
|
|
(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<string, EdgeNames> edges;
|
|
map<string, set<string>> createdContractSets;
|
|
map<string, set<string>> 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<set<string>>();
|
|
if (!callGraph.emittedEvents.empty())
|
|
emittedEventSets[contractName] = callGraph.emittedEvents |
|
|
ranges::views::transform(eventToString) |
|
|
ranges::to<set<string>>();
|
|
}
|
|
|
|
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<string> const& _set)
|
|
{
|
|
_out << "{" << (_set | ranges::views::join(", ") | ranges::to<string>()) << "}";
|
|
return _out;
|
|
}
|
|
|
|
ostream& operator<<(ostream& _out, map<string, EdgeNames> 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<string, set<string>> 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 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<EdgeNames>
|
|
{
|
|
void operator()(std::ostream& _output, EdgeNames const& _edgeNames) { ::operator<<(_output, _edgeNames); }
|
|
};
|
|
|
|
template<>
|
|
struct print_log_value<set<string>>
|
|
{
|
|
void operator()(std::ostream& _output, set<string> const& _set) { ::operator<<(_output, _set); }
|
|
};
|
|
|
|
template<>
|
|
struct print_log_value<map<string, EdgeNames>>
|
|
{
|
|
void operator()(std::ostream& _output, map<string, EdgeNames> const& _edgeSets) { ::operator<<(_output, _edgeSets); }
|
|
};
|
|
|
|
template<>
|
|
struct print_log_value<map<string, set<string>>>
|
|
{
|
|
void operator()(std::ostream& _output, map<string, set<string>> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {
|
|
{"Entry", "constructor of C"},
|
|
}},
|
|
{"D", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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<string, set<string>> expectedCreatedContractsAtCreation = {
|
|
{"C", {"D"}},
|
|
};
|
|
map<string, set<string>> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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<string, set<string>> expectedCreatedContractsAtCreation = {};
|
|
map<string, set<string>> expectedCreatedContractsAfterDeployment = {
|
|
{"G", {"F"}},
|
|
};
|
|
|
|
checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, expectedCreatedContractsAtCreation);
|
|
checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, expectedCreatedContractsAfterDeployment);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inheritance_specifiers)
|
|
{
|
|
unique_ptr<CompilerStack> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
{"E", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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<string, set<string>> expectedCreatedContractsAtCreation = {{"E", {"C"}}};
|
|
map<string, set<string>> expectedCreatedContractsAfterDeployment = {{"E", {"C"}}};
|
|
|
|
checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges, expectedCreatedContractsAtCreation);
|
|
checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges, expectedCreatedContractsAfterDeployment);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(events)
|
|
{
|
|
unique_ptr<CompilerStack> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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<string, set<string>> expectedCreationEvents = {};
|
|
map<string, set<string>> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"L", {}},
|
|
{"C", {}},
|
|
{"D", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"I", {}},
|
|
{"J", {}},
|
|
{"C", {}},
|
|
{"D", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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<string, set<string>> expectedCreationEvents = {};
|
|
map<string, set<string>> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> 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<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"I", {}},
|
|
{"C", {}},
|
|
{"D", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> compilerStack = parseAndAnalyzeContracts(R"(
|
|
contract C {
|
|
function f() public returns (bool ret) {
|
|
return f == f;
|
|
}
|
|
}
|
|
)"s);
|
|
tuple<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> compilerStack = parseAndAnalyzeContracts(R"(
|
|
contract C {
|
|
function () ptr = f;
|
|
|
|
function f() internal { ptr(); }
|
|
|
|
function test() public {
|
|
ptr();
|
|
}
|
|
}
|
|
)"s);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {
|
|
{"InternalDispatch", "function C.f()"},
|
|
{"function C.f()", "InternalDispatch"},
|
|
}},
|
|
};
|
|
|
|
map<string, EdgeNames> expectedDeployedEdges = {
|
|
{"C", {
|
|
{"InternalDispatch", "function C.f()"},
|
|
{"Entry", "function C.test()"},
|
|
{"function C.test()", "InternalDispatch"},
|
|
{"function C.f()", "InternalDispatch"},
|
|
}},
|
|
};
|
|
tuple<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges);
|
|
checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(using_for)
|
|
{
|
|
unique_ptr<CompilerStack> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"L", {}},
|
|
{"C", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
{"E", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"C", {}},
|
|
{"D", {}},
|
|
{"E", {}},
|
|
{"F", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> compilerStack = parseAndAnalyzeContracts(R"(
|
|
interface I {}
|
|
|
|
contract C {
|
|
function accessBuiltin() public payable {
|
|
abi.decode;
|
|
abi.encode;
|
|
abi.encodePacked;
|
|
abi.encodeWithSelector;
|
|
abi.encodeWithSignature;
|
|
block.basefee;
|
|
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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"I", {}},
|
|
{"C", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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> 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<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"I", {}},
|
|
{"C", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> 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_CASE(immutable_initialization)
|
|
{
|
|
unique_ptr<CompilerStack> compilerStack = parseAndAnalyzeContracts(R"(
|
|
function free() pure returns (uint) { return 42; }
|
|
|
|
contract Base {
|
|
function ext() external pure returns (uint) { free(); }
|
|
function inr() internal pure returns (uint) { free(); }
|
|
}
|
|
|
|
contract C is Base {
|
|
uint immutable extImmutable = this.ext();
|
|
uint immutable inrImmutable = inr();
|
|
}
|
|
|
|
contract D is Base {
|
|
uint immutable extImmutable;
|
|
uint immutable inrImmutable;
|
|
|
|
constructor () {
|
|
extImmutable = this.ext();
|
|
inrImmutable = inr();
|
|
}
|
|
}
|
|
)"s);
|
|
tuple<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"Base", {}},
|
|
{"C", {
|
|
{"Entry", "function Base.inr()"},
|
|
{"function Base.inr()", "function free()"},
|
|
}},
|
|
{"D", {
|
|
{"Entry", "constructor of D"},
|
|
{"constructor of D", "function Base.inr()"},
|
|
{"function Base.inr()", "function free()"},
|
|
}},
|
|
};
|
|
|
|
map<string, EdgeNames> expectedDeployedEdges = {
|
|
{"Base", {
|
|
{"Entry", "function Base.ext()"},
|
|
{"function Base.ext()", "function free()"},
|
|
}},
|
|
{"C", {
|
|
{"Entry", "function Base.ext()"},
|
|
{"function Base.ext()", "function free()"},
|
|
}},
|
|
{"D", {
|
|
{"Entry", "function Base.ext()"},
|
|
{"function Base.ext()", "function free()"},
|
|
}},
|
|
};
|
|
|
|
checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges);
|
|
checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(function_selector_access)
|
|
{
|
|
unique_ptr<CompilerStack> compilerStack = parseAndAnalyzeContracts(R"(
|
|
function free() pure {}
|
|
|
|
bytes4 constant extFreeConst = Base.ext.selector;
|
|
bytes4 constant pubFreeConst = Base.pub.selector;
|
|
|
|
contract Base {
|
|
function ext() external pure { free(); extFreeConst; }
|
|
function pub() public pure { free(); pubFreeConst; }
|
|
}
|
|
|
|
contract C is Base {
|
|
bytes4 constant extConst = Base.ext.selector;
|
|
bytes4 constant pubConst = Base.pub.selector;
|
|
}
|
|
|
|
contract D is Base {
|
|
bytes4 immutable extImmutable = Base.ext.selector;
|
|
bytes4 immutable pubImmutable = Base.pub.selector;
|
|
}
|
|
|
|
contract E is Base {
|
|
bytes4 extVar = Base.ext.selector;
|
|
bytes4 pubVar = Base.pub.selector;
|
|
}
|
|
|
|
contract F is Base {
|
|
function f() public pure returns (bytes4, bytes4) {
|
|
return (Base.ext.selector, Base.pub.selector);
|
|
}
|
|
}
|
|
|
|
library L {
|
|
bytes4 constant extConst = Base.ext.selector;
|
|
bytes4 constant pubConst = Base.pub.selector;
|
|
}
|
|
)"s);
|
|
tuple<CallGraphMap, CallGraphMap> graphs = collectGraphs(*compilerStack);
|
|
|
|
map<string, EdgeNames> expectedCreationEdges = {
|
|
{"Base", {}},
|
|
{"C", {}},
|
|
{"D", {
|
|
{"InternalDispatch", "function Base.pub()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"E", {
|
|
{"InternalDispatch", "function Base.pub()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"F", {}},
|
|
{"L", {}},
|
|
};
|
|
|
|
map<string, EdgeNames> expectedDeployedEdges = {
|
|
{"Base", {
|
|
{"Entry", "function Base.ext()"},
|
|
{"Entry", "function Base.pub()"},
|
|
{"function Base.ext()", "function free()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"C", {
|
|
{"Entry", "function Base.ext()"},
|
|
{"Entry", "function Base.pub()"},
|
|
{"function Base.ext()", "function free()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"D", {
|
|
{"InternalDispatch", "function Base.pub()"},
|
|
{"Entry", "function Base.ext()"},
|
|
{"Entry", "function Base.pub()"},
|
|
{"function Base.ext()", "function free()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"E", {
|
|
{"InternalDispatch", "function Base.pub()"},
|
|
{"Entry", "function Base.ext()"},
|
|
{"Entry", "function Base.pub()"},
|
|
{"function Base.ext()", "function free()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"F", {
|
|
{"InternalDispatch", "function Base.pub()"},
|
|
{"Entry", "function Base.ext()"},
|
|
{"Entry", "function Base.pub()"},
|
|
{"Entry", "function F.f()"},
|
|
{"function Base.ext()", "function free()"},
|
|
{"function Base.pub()", "function free()"},
|
|
}},
|
|
{"L", {}},
|
|
};
|
|
|
|
checkCallGraphExpectations(get<0>(graphs), expectedCreationEdges);
|
|
checkCallGraphExpectations(get<1>(graphs), expectedDeployedEdges);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|
|
|
|
} // namespace solidity::frontend::test
|