mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
9a7c364c71
- This makes `-` for stdin work. - `--ignore-missing` now works with `--standard-json`, though it's not very useful because there can be at most one input file. - Separate errors for situations where there are no input files on the command line (this can be detected in the parser) and where they are not present on disk.
2166 lines
58 KiB
C++
2166 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.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
|