Skip to content

Commit 5e84dd4

Browse files
committed
Detect circular references for library and free functions
1 parent c37bf89 commit 5e84dd4

36 files changed

+1157
-288
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Language Features:
55

66
Compiler Features:
77
* Low-Level Inliner: Inline ordinary jumps to small blocks and jumps to small blocks that terminate.
8+
* References Resolver: Detect circular references to other contracts across libraries and free functions
89

910

1011
Bugfixes:

libsolidity/analysis/NameAndTypeResolver.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
389389
if (result.empty())
390390
m_errorReporter.fatalTypeError(5005_error, _contract.location(), "Linearization of inheritance graph impossible");
391391
_contract.annotation().linearizedBaseContracts = result;
392-
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
393392
}
394393

395394
template <class T>

libsolidity/analysis/TypeChecker.cpp

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,24 +2659,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
26592659
if (contract->abstract())
26602660
m_errorReporter.typeError(4614_error, _newExpression.location(), "Cannot instantiate an abstract contract.");
26612661

2662-
if (m_currentContract)
2663-
{
2664-
// TODO this is not properly detecting creation-cycles if they go through
2665-
// internal library functions or free functions. It will be caught at
2666-
// code generation time, but it would of course be better to catch it here.
2667-
m_currentContract->annotation().contractDependencies.insert(contract);
2668-
solAssert(
2669-
!contract->annotation().linearizedBaseContracts.empty(),
2670-
"Linearized base contracts not yet available."
2671-
);
2672-
if (contractDependenciesAreCyclic(*m_currentContract))
2673-
m_errorReporter.typeError(
2674-
4579_error,
2675-
_newExpression.location(),
2676-
"Circular reference for contract creation (cannot create instance of derived or same contract)."
2677-
);
2678-
}
2679-
26802662
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
26812663
_newExpression.annotation().isPure = false;
26822664
}
@@ -2950,21 +2932,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
29502932
_memberAccess.location(),
29512933
"\"runtimeCode\" is not available for contracts containing immutable variables."
29522934
);
2953-
if (m_currentContract)
2954-
{
2955-
// TODO in the same way as with ``new``,
2956-
// this is not properly detecting creation-cycles if they go through
2957-
// internal library functions or free functions. It will be caught at
2958-
// code generation time, but it would of course be better to catch it here.
2959-
2960-
m_currentContract->annotation().contractDependencies.insert(&accessedContractType.contractDefinition());
2961-
if (contractDependenciesAreCyclic(*m_currentContract))
2962-
m_errorReporter.typeError(
2963-
4224_error,
2964-
_memberAccess.location(),
2965-
"Circular reference for contract code access."
2966-
);
2967-
}
29682935
}
29692936
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
29702937
annotation.isPure = true;
@@ -3426,22 +3393,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
34263393
);
34273394
}
34283395

3429-
bool TypeChecker::contractDependenciesAreCyclic(
3430-
ContractDefinition const& _contract,
3431-
std::set<ContractDefinition const*> const& _seenContracts
3432-
) const
3433-
{
3434-
// Naive depth-first search that remembers nodes already seen.
3435-
if (_seenContracts.count(&_contract))
3436-
return true;
3437-
set<ContractDefinition const*> seen(_seenContracts);
3438-
seen.insert(&_contract);
3439-
for (auto const* c: _contract.annotation().contractDependencies)
3440-
if (contractDependenciesAreCyclic(*c, seen))
3441-
return true;
3442-
return false;
3443-
}
3444-
34453396
Declaration const& TypeChecker::dereference(Identifier const& _identifier) const
34463397
{
34473398
solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored.");

libsolidity/analysis/TypeChecker.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,6 @@ class TypeChecker: private ASTConstVisitor
153153
void endVisit(Literal const& _literal) override;
154154
void endVisit(UsingForDirective const& _usingForDirective) override;
155155

156-
bool contractDependenciesAreCyclic(
157-
ContractDefinition const& _contract,
158-
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
159-
) const;
160-
161156
/// @returns the referenced declaration and throws on error.
162157
Declaration const& dereference(Identifier const& _identifier) const;
163158
/// @returns the referenced declaration and throws on error.

libsolidity/ast/AST.h

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,7 @@ class ASTConstVisitor;
6565
class ASTNode: private boost::noncopyable
6666
{
6767
public:
68-
struct CompareByID
69-
{
70-
using is_transparent = void;
71-
72-
bool operator()(ASTNode const* _lhs, ASTNode const* _rhs) const
73-
{
74-
return _lhs->id() < _rhs->id();
75-
}
76-
bool operator()(ASTNode const* _lhs, int64_t _rhs) const
77-
{
78-
return _lhs->id() < _rhs;
79-
}
80-
bool operator()(int64_t _lhs, ASTNode const* _rhs) const
81-
{
82-
return _lhs < _rhs->id();
83-
}
84-
};
85-
68+
using CompareByID = frontend::ASTCompareByID<ASTNode>;
8669
using SourceLocation = langutil::SourceLocation;
8770

8871
explicit ASTNode(int64_t _id, SourceLocation _location);

libsolidity/ast/ASTAnnotations.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,17 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu
158158
/// List of all (direct and indirect) base contracts in order from derived to
159159
/// base, including the contract itself.
160160
std::vector<ContractDefinition const*> linearizedBaseContracts;
161-
/// List of contracts this contract creates, i.e. which need to be compiled first.
162-
/// Also includes all contracts from @a linearizedBaseContracts.
163-
std::set<ContractDefinition const*> contractDependencies;
164161
/// Mapping containing the nodes that define the arguments for base constructors.
165162
/// These can either be inheritance specifiers or modifier invocations.
166163
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
167164
/// A graph with edges representing calls between functions that may happen during contract construction.
168165
SetOnce<std::shared_ptr<CallGraph const>> creationCallGraph;
169166
/// A graph with edges representing calls between functions that may happen in a deployed contract.
170167
SetOnce<std::shared_ptr<CallGraph const>> deployedCallGraph;
168+
169+
/// List of contracts whose bytecode is referenced by this contract, e.g. through "new".
170+
/// The Value represents the ast node that referenced the contract.
171+
std::map<ContractDefinition const*, ASTNode const*, ASTCompareByID<ContractDefinition>> contractDependencies;
171172
};
172173

173174
struct CallableDeclarationAnnotation: DeclarationAnnotation

libsolidity/ast/ASTForward.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ class StructuredDocumentation;
9999

100100
class VariableScope;
101101

102+
template <class T>
103+
struct ASTCompareByID
104+
{
105+
using is_transparent = void;
106+
107+
bool operator()(T const* _lhs, T const* _rhs) const
108+
{
109+
return _lhs->id() < _rhs->id();
110+
}
111+
bool operator()(T const* _lhs, int64_t _rhs) const
112+
{
113+
return _lhs->id() < _rhs;
114+
}
115+
bool operator()(int64_t _lhs, T const* _rhs) const
116+
{
117+
return _lhs < _rhs->id();
118+
}
119+
};
120+
102121
// Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do
103122
// not do reference counting but point to a special memory area that is completely released
104123
// explicitly.

libsolidity/ast/ASTJsonConverter.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
#include <algorithm>
4242
#include <limits>
4343
#include <type_traits>
44+
#include <range/v3/view/map.hpp>
4445

46+
using namespace ranges;
4547
using namespace std;
4648
using namespace solidity::langutil;
4749

@@ -269,7 +271,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
269271
make_pair("contractKind", contractKind(_node.contractKind())),
270272
make_pair("abstract", _node.abstract()),
271273
make_pair("baseContracts", toJson(_node.baseContracts())),
272-
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
274+
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies | views::keys)),
273275
make_pair("nodes", toJson(_node.subNodes())),
274276
make_pair("scope", idOrNull(_node.scope()))
275277
};

libsolidity/ast/CallGraph.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ struct CallGraph
6363

6464
/// Contracts that need to be compiled before this one can be compiled.
6565
/// The value is the ast node that created the dependency.
66-
std::map<ContractDefinition const*, ASTNode const*, ASTNode::CompareByID> bytecodeDependency;
66+
std::map<ContractDefinition const*, ASTNode const*, ASTCompareByID<ContractDefinition>> bytecodeDependency;
6767

6868
/// Events that may get emitted by functions present in the graph.
6969
std::set<EventDefinition const*, ASTNode::CompareByID> emittedEvents;

libsolidity/interface/CompilerStack.cpp

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,15 @@
7070
#include <libsolutil/SwarmHash.h>
7171
#include <libsolutil/IpfsHash.h>
7272
#include <libsolutil/JSON.h>
73+
#include <libsolutil/Algorithms.h>
7374

7475
#include <json/json.h>
7576

76-
#include <boost/algorithm/string/replace.hpp>
7777
#include <utility>
78+
#include <map>
79+
#include <range/v3/view/concat.hpp>
80+
81+
#include <boost/algorithm/string/replace.hpp>
7882

7983
using namespace std;
8084
using namespace solidity;
@@ -103,6 +107,99 @@ CompilerStack::~CompilerStack()
103107
TypeProvider::reset();
104108
}
105109

110+
void CompilerStack::createAndAssignCallGraphs()
111+
{
112+
for (Source const* source: m_sourceOrder)
113+
{
114+
if (!source->ast)
115+
continue;
116+
117+
for (ContractDefinition const* contract: ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes()))
118+
{
119+
ContractDefinitionAnnotation& annotation =
120+
m_contracts.at(contract->fullyQualifiedName()).contract->annotation();
121+
122+
annotation.creationCallGraph = make_unique<CallGraph>(
123+
FunctionCallGraphBuilder::buildCreationGraph(*contract)
124+
);
125+
annotation.deployedCallGraph = make_unique<CallGraph>(
126+
FunctionCallGraphBuilder::buildDeployedGraph(
127+
*contract,
128+
**annotation.creationCallGraph
129+
)
130+
);
131+
132+
solAssert(annotation.contractDependencies.empty(), "contractDependencies expected to be empty?!");
133+
134+
annotation.contractDependencies = annotation.creationCallGraph->get()->bytecodeDependency;
135+
136+
for (auto const& [dependencyContract, referencee]: annotation.deployedCallGraph->get()->bytecodeDependency)
137+
annotation.contractDependencies.emplace(dependencyContract, referencee);
138+
}
139+
}
140+
}
141+
142+
void CompilerStack::findAndReportCyclicContractDependencies()
143+
{
144+
// Cycles we found, used to avoid duplicate reports for the same reference
145+
set<ASTNode const*, ASTNode::CompareByID> foundCycles;
146+
147+
for (Source const* source: m_sourceOrder)
148+
{
149+
if (!source->ast)
150+
continue;
151+
152+
for (ContractDefinition const* contractDefinition: ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes()))
153+
{
154+
// Ignore libraries as they are tested as part of the
155+
// contracts that use them.
156+
if (contractDefinition->isLibrary())
157+
continue;
158+
159+
util::CycleDetector<ContractDefinition> cycleDetector{[&](
160+
ContractDefinition const& _contract,
161+
util::CycleDetector<ContractDefinition>& _cycleDetector,
162+
size_t _depth
163+
)
164+
{
165+
// No specific reason for exactly that number, just a limit we're unlikely to hit.
166+
if (_depth >= 256)
167+
m_errorReporter.fatalTypeError(
168+
7864_error,
169+
_contract.location(),
170+
"Contract dependencies exhausting cyclic dependency validator"
171+
);
172+
173+
for (auto& [dependencyContract, referencee]: _contract.annotation().contractDependencies)
174+
if (_cycleDetector.run(*dependencyContract))
175+
return;
176+
}};
177+
178+
ContractDefinition const* cycle = cycleDetector.run(*contractDefinition);
179+
180+
if (!cycle)
181+
continue;
182+
183+
ASTNode const* referencee = contractDefinition->annotation().contractDependencies.at(cycle);
184+
185+
if (foundCycles.find(referencee) != foundCycles.end())
186+
continue;
187+
188+
SecondarySourceLocation secondaryLocation{};
189+
secondaryLocation.append("Referenced contract is here:"s, cycle->location());
190+
191+
m_errorReporter.typeError(
192+
7813_error,
193+
referencee->location(),
194+
secondaryLocation,
195+
"Circular reference found."
196+
);
197+
198+
foundCycles.emplace(referencee);
199+
}
200+
}
201+
}
202+
106203
std::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping)
107204
{
108205
auto eq = find(_remapping.begin(), _remapping.end(), '=');
@@ -399,27 +496,11 @@ bool CompilerStack::analyze()
399496
if (source->ast && !typeChecker.checkTypeRequirements(*source->ast))
400497
noErrors = false;
401498

499+
// Create & assign callgraphs and check for contract dependency cycles
402500
if (noErrors)
403501
{
404-
for (Source const* source: m_sourceOrder)
405-
if (source->ast)
406-
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
407-
if (auto const* contractDefinition = dynamic_cast<ContractDefinition*>(node.get()))
408-
{
409-
Contract& contractState = m_contracts.at(contractDefinition->fullyQualifiedName());
410-
411-
contractState.contract->annotation().creationCallGraph = make_unique<CallGraph>(
412-
FunctionCallGraphBuilder::buildCreationGraph(
413-
*contractDefinition
414-
)
415-
);
416-
contractState.contract->annotation().deployedCallGraph = make_unique<CallGraph>(
417-
FunctionCallGraphBuilder::buildDeployedGraph(
418-
*contractDefinition,
419-
**contractState.contract->annotation().creationCallGraph
420-
)
421-
);
422-
}
502+
createAndAssignCallGraphs();
503+
findAndReportCyclicContractDependencies();
423504
}
424505

425506
if (noErrors)
@@ -1200,7 +1281,7 @@ void CompilerStack::compileContract(
12001281
if (_otherCompilers.count(&_contract))
12011282
return;
12021283

1203-
for (auto const* dependency: _contract.annotation().contractDependencies)
1284+
for (auto const& [dependency, referencee]: _contract.annotation().contractDependencies)
12041285
compileContract(*dependency, _otherCompilers);
12051286

12061287
if (!_contract.canBeDeployed())
@@ -1286,7 +1367,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
12861367
);
12871368

12881369
string dependenciesSource;
1289-
for (auto const* dependency: _contract.annotation().contractDependencies)
1370+
for (auto const& [dependency, referencee]: _contract.annotation().contractDependencies)
12901371
generateIR(*dependency);
12911372

12921373
if (!_contract.canBeDeployed())

0 commit comments

Comments
 (0)