Skip to content

Commit 8cbdfb5

Browse files
committed
Detect circular references for library and free functions
1 parent 78acbe9 commit 8cbdfb5

19 files changed

+978
-68
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Language Features:
66
Compiler Features:
77
* Command Line Interface: Drop experimental support for ``--machine evm15``.
88
* Optimizer: Try to move ``and`` with constant inside ``or`` to improve storage writes of small types.
9+
* References Resolver: Detect circular references to other contracts across libraries and free functions
910

1011

1112
Bugfixes:

libsolidity/analysis/FunctionCallGraph.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocati
212212
bool FunctionCallGraphBuilder::visit(NewExpression const& _newExpression)
213213
{
214214
if (ContractType const* contractType = dynamic_cast<ContractType const*>(_newExpression.typeName().annotation().type))
215-
m_graph.createdContracts.emplace(&contractType->contractDefinition());
215+
m_graph.createdContracts.emplace(&contractType->contractDefinition(), &_newExpression);
216216

217217
return true;
218218
}

libsolidity/analysis/NameAndTypeResolver.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,17 +379,21 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
379379
// "push_front" has the effect that bases mentioned later can overwrite members of bases
380380
// mentioned earlier
381381
input.back().push_front(base);
382+
_contract.annotation().contractDependencies.emplace(base, baseSpecifier.get());
383+
382384
vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts;
383385
if (basesBases.empty())
384386
m_errorReporter.fatalTypeError(2449_error, baseName.location(), "Definition of base has to precede definition of derived contract");
385387
input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end()));
388+
389+
auto const& basesDependencies = base->annotation().contractDependencies;
390+
_contract.annotation().contractDependencies.insert(basesDependencies.begin(), basesDependencies.end());
386391
}
387392
input.back().push_front(&_contract);
388393
vector<ContractDefinition const*> result = cThreeMerge(input);
389394
if (result.empty())
390395
m_errorReporter.fatalTypeError(5005_error, _contract.location(), "Linearization of inheritance graph impossible");
391396
_contract.annotation().linearizedBaseContracts = result;
392-
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
393397
}
394398

395399
template <class T>

libsolidity/analysis/PostTypeChecker.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
#include <libsolidity/analysis/PostTypeChecker.h>
2020

2121
#include <libsolidity/ast/AST.h>
22+
#include <libsolidity/ast/CallGraph.h>
2223
#include <libsolidity/interface/Version.h>
2324
#include <liblangutil/ErrorReporter.h>
2425
#include <liblangutil/SemVerHandler.h>
2526
#include <libsolutil/Algorithms.h>
2627

2728
#include <boost/range/adaptor/map.hpp>
2829
#include <memory>
30+
#include <set>
2931

3032
using namespace std;
3133
using namespace solidity;
@@ -362,6 +364,58 @@ struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
362364
/// Flag indicating whether we are currently inside a StructDefinition.
363365
int m_insideStruct = 0;
364366
};
367+
368+
struct CircularContractDependencies: public PostTypeChecker::Checker
369+
{
370+
CircularContractDependencies(ErrorReporter& _errorReporter):
371+
Checker(_errorReporter),
372+
m_cycleDetector(m_cycleVisitor)
373+
{}
374+
375+
void endVisit(ContractDefinition const& _contract) override
376+
{
377+
ContractDefinition const* cycleContract = m_cycleDetector.run(_contract);
378+
379+
if (cycleContract)
380+
m_foundCycles.emplace(cycleContract, _contract.annotation().contractDependencies[cycleContract]);
381+
}
382+
383+
void finalize() override
384+
{
385+
for (auto const& [cycle, referencee]: m_foundCycles)
386+
{
387+
SecondarySourceLocation secondaryLocation{};
388+
secondaryLocation.append("Referenced contract"s, cycle->location());
389+
390+
m_errorReporter.typeError(
391+
7813_error,
392+
referencee->location(),
393+
secondaryLocation,
394+
"Circular reference found."
395+
);
396+
}
397+
398+
m_foundCycles.clear();
399+
m_cycleDetector.reset();
400+
}
401+
402+
private:
403+
/// Visitor for the cycle detector
404+
util::CycleDetector<ContractDefinition>::Visitor const m_cycleVisitor = [&](ContractDefinition const& _contract, util::CycleDetector<ContractDefinition>& _cycleDetector, size_t _depth)
405+
{
406+
if (_depth >= 256)
407+
m_errorReporter.fatalTypeError(7864_error, _contract.location(), "Contract dependencies exhausting cyclic dependency validator");
408+
409+
for (auto& [dependencyContract, referencee]: _contract.annotation().contractDependencies)
410+
if (_cycleDetector.run(*dependencyContract))
411+
return;
412+
};
413+
414+
/// Cycles we found, used to avoid duplicate reports for the same dependency
415+
map<ContractDefinition const*, ASTNode const*, ASTNode::CompareByID> m_foundCycles;
416+
/// Cycle detector is a class member as it should remember already visited contracts
417+
util::CycleDetector<ContractDefinition> m_cycleDetector;
418+
};
365419
}
366420

367421
PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter)
@@ -371,4 +425,5 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
371425
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
372426
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
373427
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
428+
m_checkers.push_back(make_shared<CircularContractDependencies>(_errorReporter));
374429
}

libsolidity/analysis/TypeChecker.cpp

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ void TypeChecker::endVisit(ModifierDefinition const& _modifier)
329329

330330
bool TypeChecker::visit(FunctionDefinition const& _function)
331331
{
332+
if (_function.isFree())
333+
{
334+
solAssert(m_currentFreeFunction == nullptr, "");
335+
m_currentFreeFunction = &_function;
336+
}
337+
332338
if (_function.markedVirtual())
333339
{
334340
if (_function.isFree())
@@ -496,6 +502,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
496502
return false;
497503
}
498504

505+
void TypeChecker::endVisit(FunctionDefinition const& _function)
506+
{
507+
if (_function.isFree())
508+
{
509+
solAssert(m_currentFreeFunction == &_function, "");
510+
m_currentFreeFunction = nullptr;
511+
}
512+
}
513+
499514
bool TypeChecker::visit(VariableDeclaration const& _variable)
500515
{
501516
_variable.typeName().accept(*this);
@@ -2627,22 +2642,14 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
26272642
if (contract->abstract())
26282643
m_errorReporter.typeError(4614_error, _newExpression.location(), "Cannot instantiate an abstract contract.");
26292644

2630-
if (m_currentContract)
2645+
if (m_currentContract || m_currentFreeFunction)
26312646
{
2632-
// TODO this is not properly detecting creation-cycles if they go through
2633-
// internal library functions or free functions. It will be caught at
2634-
// code generation time, but it would of course be better to catch it here.
2635-
m_currentContract->annotation().contractDependencies.insert(contract);
26362647
solAssert(
26372648
!contract->annotation().linearizedBaseContracts.empty(),
26382649
"Linearized base contracts not yet available."
26392650
);
2640-
if (contractDependenciesAreCyclic(*m_currentContract))
2641-
m_errorReporter.typeError(
2642-
4579_error,
2643-
_newExpression.location(),
2644-
"Circular reference for contract creation (cannot create instance of derived or same contract)."
2645-
);
2651+
2652+
currentDependencyAnnotation()->contractDependencies.emplace(contract, &_newExpression);
26462653
}
26472654

26482655
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
@@ -2918,21 +2925,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
29182925
_memberAccess.location(),
29192926
"\"runtimeCode\" is not available for contracts containing immutable variables."
29202927
);
2921-
if (m_currentContract)
2922-
{
2923-
// TODO in the same way as with ``new``,
2924-
// this is not properly detecting creation-cycles if they go through
2925-
// internal library functions or free functions. It will be caught at
2926-
// code generation time, but it would of course be better to catch it here.
29272928

2928-
m_currentContract->annotation().contractDependencies.insert(&accessedContractType.contractDefinition());
2929-
if (contractDependenciesAreCyclic(*m_currentContract))
2930-
m_errorReporter.typeError(
2931-
4224_error,
2932-
_memberAccess.location(),
2933-
"Circular reference for contract code access."
2934-
);
2935-
}
2929+
if (m_currentContract || m_currentFreeFunction)
2930+
currentDependencyAnnotation()->contractDependencies.emplace(&accessedContractType.contractDefinition(), &_memberAccess);
29362931
}
29372932
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
29382933
annotation.isPure = true;
@@ -2950,6 +2945,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
29502945
"\"chainid\" is not supported by the VM version."
29512946
);
29522947
}
2948+
else if (m_currentContract || m_currentFreeFunction)
2949+
if (auto const* typeType = dynamic_cast<TypeType const*>(exprType))
2950+
if (auto const* contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
2951+
if (&contractType->contractDefinition() != m_currentContract)
2952+
currentDependencyAnnotation()->contractDependencies.emplace(&contractType->contractDefinition(), &_memberAccess);
29532953

29542954
if (
29552955
_memberAccess.expression().annotation().type->category() == Type::Category::Address &&
@@ -3394,22 +3394,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
33943394
);
33953395
}
33963396

3397-
bool TypeChecker::contractDependenciesAreCyclic(
3398-
ContractDefinition const& _contract,
3399-
std::set<ContractDefinition const*> const& _seenContracts
3400-
) const
3401-
{
3402-
// Naive depth-first search that remembers nodes already seen.
3403-
if (_seenContracts.count(&_contract))
3404-
return true;
3405-
set<ContractDefinition const*> seen(_seenContracts);
3406-
seen.insert(&_contract);
3407-
for (auto const* c: _contract.annotation().contractDependencies)
3408-
if (contractDependenciesAreCyclic(*c, seen))
3409-
return true;
3410-
return false;
3411-
}
3412-
34133397
Declaration const& TypeChecker::dereference(Identifier const& _identifier) const
34143398
{
34153399
solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored.");

libsolidity/analysis/TypeChecker.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class TypeChecker: private ASTConstVisitor
114114
void endVisit(InheritanceSpecifier const& _inheritance) override;
115115
void endVisit(ModifierDefinition const& _modifier) override;
116116
bool visit(FunctionDefinition const& _function) override;
117+
void endVisit(FunctionDefinition const& _function) override;
117118
bool visit(VariableDeclaration const& _variable) override;
118119
/// We need to do this manually because we want to pass the bases of the current contract in
119120
/// case this is a base constructor call.
@@ -147,11 +148,6 @@ class TypeChecker: private ASTConstVisitor
147148
void endVisit(Literal const& _literal) override;
148149
void endVisit(UsingForDirective const& _usingForDirective) override;
149150

150-
bool contractDependenciesAreCyclic(
151-
ContractDefinition const& _contract,
152-
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
153-
) const;
154-
155151
/// @returns the referenced declaration and throws on error.
156152
Declaration const& dereference(Identifier const& _identifier) const;
157153
/// @returns the referenced declaration and throws on error.
@@ -180,8 +176,21 @@ class TypeChecker: private ASTConstVisitor
180176
return m_currentSourceUnit;
181177
}
182178

179+
SharedContractDependenciesAnnotation* currentDependencyAnnotation()
180+
{
181+
solAssert(!m_currentFreeFunction || !m_currentContract, "Both variables are set?!");
182+
183+
if (m_currentFreeFunction)
184+
return &m_currentFreeFunction->annotation();
185+
else if (m_currentContract)
186+
return &m_currentContract->annotation();
187+
188+
return nullptr;
189+
}
190+
183191
SourceUnit const* m_currentSourceUnit = nullptr;
184192
ContractDefinition const* m_currentContract = nullptr;
193+
FunctionDefinition const* m_currentFreeFunction = nullptr;
185194

186195
langutil::EVMVersion m_evmVersion;
187196

libsolidity/ast/ASTAnnotations.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,22 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
152152
std::optional<bool> containsNestedMapping;
153153
};
154154

155-
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
155+
struct SharedContractDependenciesAnnotation
156+
{
157+
/// List of contracts that this contract or function creates, i.e. contracts which need to be compiled first.
158+
/// For ContractDefinitions: Also includes all contracts from linearizedBaseContracts.
159+
/// Only relevant for contracts or free functions
160+
/// The Value represents the ast node that referenced the contract
161+
std::map<ContractDefinition const*, ASTNode const*> contractDependencies;
162+
};
163+
164+
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation, SharedContractDependenciesAnnotation
156165
{
157166
/// List of functions and modifiers without a body. Can also contain functions from base classes.
158167
std::optional<std::vector<Declaration const*>> unimplementedDeclarations;
159168
/// List of all (direct and indirect) base contracts in order from derived to
160169
/// base, including the contract itself.
161170
std::vector<ContractDefinition const*> linearizedBaseContracts;
162-
/// List of contracts this contract creates, i.e. which need to be compiled first.
163-
/// Also includes all contracts from @a linearizedBaseContracts.
164-
std::set<ContractDefinition const*> contractDependencies;
165171
/// Mapping containing the nodes that define the arguments for base constructors.
166172
/// These can either be inheritance specifiers or modifier invocations.
167173
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
@@ -177,7 +183,7 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
177183
std::set<CallableDeclaration const*> baseFunctions;
178184
};
179185

180-
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
186+
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation, SharedContractDependenciesAnnotation
181187
{
182188
};
183189

libsolidity/ast/ASTJsonConverter.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@
3535

3636
#include <boost/algorithm/string/join.hpp>
3737
#include <boost/range/algorithm/sort.hpp>
38+
#include <boost/range/adaptors.hpp>
3839

3940
#include <utility>
4041
#include <vector>
4142
#include <algorithm>
4243
#include <limits>
4344
#include <type_traits>
45+
#include <range/v3/view/map.hpp>
4446

47+
using namespace ranges;
4548
using namespace std;
4649
using namespace solidity::langutil;
4750

@@ -269,7 +272,10 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
269272
make_pair("contractKind", contractKind(_node.contractKind())),
270273
make_pair("abstract", _node.abstract()),
271274
make_pair("baseContracts", toJson(_node.baseContracts())),
272-
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
275+
make_pair("contractDependencies", getContainerIds(
276+
_node.annotation().contractDependencies | views::keys,
277+
true
278+
)),
273279
make_pair("nodes", toJson(_node.subNodes())),
274280
make_pair("scope", idOrNull(_node.scope()))
275281
};

libsolidity/ast/CallGraph.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ struct CallGraph
6262
std::map<Node, std::set<Node, CompareByID>, CompareByID> edges;
6363

6464
/// Contracts that may get created with `new` by functions present in the graph.
65-
std::set<ContractDefinition const*, ASTNode::CompareByID> createdContracts;
65+
std::map<ContractDefinition const*, NewExpression const*, ASTNode::CompareByID> createdContracts;
6666

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

0 commit comments

Comments
 (0)