Skip to content

Commit 75e7e77

Browse files
committed
Detect circular references for library and free functions
1 parent ad2ecf1 commit 75e7e77

19 files changed

+200
-65
lines changed

Changelog.md

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

55

66
Compiler Features:
7+
* References Resolver: Detect circular references for libraries and free functions
78

89

910
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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,17 +379,22 @@ 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());
397+
//_contract.annotation().contractDependencies.emplace(result.begin() + 1, result.end());
393398
}
394399

395400
template <class T>

libsolidity/analysis/PostTypeChecker.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
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>
29+
#include <boost/range/join.hpp>
2830
#include <memory>
31+
#include <set>
2932

3033
using namespace std;
3134
using namespace solidity;
@@ -55,6 +58,16 @@ void PostTypeChecker::endVisit(ContractDefinition const& _contractDefinition)
5558
callEndVisit(_contractDefinition);
5659
}
5760

61+
bool PostTypeChecker::visit(FunctionDefinition const& _function)
62+
{
63+
return callVisit(_function);
64+
}
65+
66+
void PostTypeChecker::endVisit(FunctionDefinition const& _function)
67+
{
68+
callEndVisit(_function);
69+
}
70+
5871
void PostTypeChecker::endVisit(OverrideSpecifier const& _overrideSpecifier)
5972
{
6073
callEndVisit(_overrideSpecifier);
@@ -362,6 +375,66 @@ struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
362375
/// Flag indicating whether we are currently inside a StructDefinition.
363376
int m_insideStruct = 0;
364377
};
378+
379+
struct CircularContractDependencies: public PostTypeChecker::Checker
380+
{
381+
CircularContractDependencies(ErrorReporter& _errorReporter):
382+
Checker(_errorReporter)
383+
{}
384+
385+
bool visit(ContractDefinition const& _contract) override
386+
{
387+
auto joined = boost::join(
388+
_contract.annotation().creationCallGraph->get()->createdContracts,
389+
_contract.annotation().deployedCallGraph->get()->createdContracts
390+
);
391+
392+
for (auto const& [contract, referencee]: joined)
393+
if (contract != &_contract)
394+
_contract.annotation().contractDependencies.emplace(contract, referencee);
395+
396+
return true;
397+
}
398+
void endVisit(ContractDefinition const& _contract) override
399+
{
400+
auto const& [contract, referencee] = dependenciesAreCyclic(_contract);
401+
402+
if (contract)
403+
{
404+
SecondarySourceLocation ssl{};
405+
ssl.append(string{"Referenced contract"}, contract->location());
406+
407+
m_errorReporter.typeError(
408+
7813_error,
409+
referencee->location(),
410+
ssl,
411+
"Circular reference found."
412+
);
413+
}
414+
}
415+
416+
private:
417+
std::pair<ContractDefinition const*, ASTNode const*> dependenciesAreCyclic(
418+
ContractDefinition const& _currentContract,
419+
ASTNode const* _referencee = nullptr,
420+
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
421+
) const
422+
{
423+
// Naive depth-first search that remembers nodes already seen.
424+
if (_seenContracts.count(&_currentContract))
425+
return {&_currentContract, _referencee};
426+
set<ContractDefinition const*> seen(_seenContracts);
427+
seen.insert(&_currentContract);
428+
for (auto const& [itContract, itReferencee]: _currentContract.annotation().contractDependencies)
429+
{
430+
auto const& [reference, referencee] = dependenciesAreCyclic(*itContract, itReferencee, seen);
431+
432+
if (reference)
433+
return {reference, referencee};
434+
}
435+
return {nullptr, nullptr};
436+
}
437+
};
365438
}
366439

367440
PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter)
@@ -371,4 +444,5 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
371444
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
372445
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
373446
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
447+
m_checkers.push_back(make_shared<CircularContractDependencies>(_errorReporter));
374448
}

libsolidity/analysis/PostTypeChecker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class PostTypeChecker: private ASTConstVisitor
7272
private:
7373
bool visit(ContractDefinition const& _contract) override;
7474
void endVisit(ContractDefinition const& _contract) override;
75+
bool visit(FunctionDefinition const& _function) override;
76+
void endVisit(FunctionDefinition const& _function) override;
7577
void endVisit(OverrideSpecifier const& _overrideSpecifier) override;
7678

7779
bool visit(VariableDeclaration const& _variable) override;

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+
currentContractDependencies()->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+
currentContractDependencies()->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 typeType = dynamic_cast<TypeType const*>(exprType))
2950+
if (auto contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
2951+
if (&contractType->contractDefinition() != m_currentContract)
2952+
currentContractDependencies()->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: 12 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,19 @@ class TypeChecker: private ASTConstVisitor
180176
return m_currentSourceUnit;
181177
}
182178

179+
SharedContractDependenciesAnnotation* currentContractDependencies()
180+
{
181+
if (m_currentFreeFunction)
182+
return &m_currentFreeFunction->annotation();
183+
else if (m_currentContract)
184+
return &m_currentContract->annotation();
185+
186+
return nullptr;
187+
}
188+
183189
SourceUnit const* m_currentSourceUnit = nullptr;
184190
ContractDefinition const* m_currentContract = nullptr;
191+
FunctionDefinition const* m_currentFreeFunction = nullptr;
185192

186193
langutil::EVMVersion m_evmVersion;
187194

libsolidity/ast/ASTAnnotations.h

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

155-
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
155+
struct SharedContractDependenciesAnnotation
156+
{
157+
/// List of contracts this contract or function creates, i.e. which need to be compiled first.
158+
/// For ContractDefinitions: Also includes all contracts from linearizedBaseContracts.
159+
/// Only set for contracts or free functions
160+
std::map<ContractDefinition const*, ASTNode const*> contractDependencies;
161+
};
162+
163+
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation, SharedContractDependenciesAnnotation
156164
{
157165
/// List of functions and modifiers without a body. Can also contain functions from base classes.
158166
std::optional<std::vector<Declaration const*>> unimplementedDeclarations;
159167
/// List of all (direct and indirect) base contracts in order from derived to
160168
/// base, including the contract itself.
161169
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;
165170
/// Mapping containing the nodes that define the arguments for base constructors.
166171
/// These can either be inheritance specifiers or modifier invocations.
167172
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
@@ -177,7 +182,7 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
177182
std::set<CallableDeclaration const*> baseFunctions;
178183
};
179184

180-
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
185+
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation, SharedContractDependenciesAnnotation
181186
{
182187
};
183188

libsolidity/ast/ASTJsonConverter.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
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>
@@ -269,7 +270,10 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
269270
make_pair("contractKind", contractKind(_node.contractKind())),
270271
make_pair("abstract", _node.abstract()),
271272
make_pair("baseContracts", toJson(_node.baseContracts())),
272-
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
273+
make_pair("contractDependencies", getContainerIds(
274+
_node.annotation().contractDependencies | boost::adaptors::map_keys,
275+
true
276+
)),
273277
make_pair("nodes", toJson(_node.subNodes())),
274278
make_pair("scope", idOrNull(_node.scope()))
275279
};

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*, ASTNode 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)