Skip to content

Commit

Permalink
Detect circular references for library and free functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Marenz authored and chriseth committed Mar 30, 2021
1 parent 15fe07b commit e590a99
Show file tree
Hide file tree
Showing 37 changed files with 1,168 additions and 288 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Language Features:
* Possibility to use ``bytes.concat`` with variable number of ``bytes`` and ``bytesNN`` arguments which behaves as a restricted version of `abi.encodePacked` with a more descriptive name.

Compiler Features:
* Analysis: Properly detect circular references to the bytecode of other contracts across all function calls.
* Commandline Interface: Model checker option ``--model-checker-targets`` also accepts ``outOfBounds``.
* Low-Level Inliner: Inline ordinary jumps to small blocks and jumps to small blocks that terminate.
* SMTChecker: Report local variables in CHC counterexamples.
Expand Down
1 change: 0 additions & 1 deletion libsolidity/analysis/NameAndTypeResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
if (result.empty())
m_errorReporter.fatalTypeError(5005_error, _contract.location(), "Linearization of inheritance graph impossible");
_contract.annotation().linearizedBaseContracts = result;
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
}

template <class T>
Expand Down
49 changes: 0 additions & 49 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2662,24 +2662,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
if (contract->abstract())
m_errorReporter.typeError(4614_error, _newExpression.location(), "Cannot instantiate an abstract contract.");

if (m_currentContract)
{
// TODO this is not properly detecting creation-cycles if they go through
// internal library functions or free functions. It will be caught at
// code generation time, but it would of course be better to catch it here.
m_currentContract->annotation().contractDependencies.insert(contract);
solAssert(
!contract->annotation().linearizedBaseContracts.empty(),
"Linearized base contracts not yet available."
);
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4579_error,
_newExpression.location(),
"Circular reference for contract creation (cannot create instance of derived or same contract)."
);
}

_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
_newExpression.annotation().isPure = false;
}
Expand Down Expand Up @@ -2953,21 +2935,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
_memberAccess.location(),
"\"runtimeCode\" is not available for contracts containing immutable variables."
);
if (m_currentContract)
{
// TODO in the same way as with ``new``,
// this is not properly detecting creation-cycles if they go through
// internal library functions or free functions. It will be caught at
// code generation time, but it would of course be better to catch it here.

m_currentContract->annotation().contractDependencies.insert(&accessedContractType.contractDefinition());
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4224_error,
_memberAccess.location(),
"Circular reference for contract code access."
);
}
}
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
annotation.isPure = true;
Expand Down Expand Up @@ -3455,22 +3422,6 @@ void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _calla
}
}

bool TypeChecker::contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts
) const
{
// Naive depth-first search that remembers nodes already seen.
if (_seenContracts.count(&_contract))
return true;
set<ContractDefinition const*> seen(_seenContracts);
seen.insert(&_contract);
for (auto const* c: _contract.annotation().contractDependencies)
if (contractDependenciesAreCyclic(*c, seen))
return true;
return false;
}

Declaration const& TypeChecker::dereference(Identifier const& _identifier) const
{
solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored.");
Expand Down
5 changes: 0 additions & 5 deletions libsolidity/analysis/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,6 @@ class TypeChecker: private ASTConstVisitor

void checkErrorAndEventParameters(CallableDeclaration const& _callable);

bool contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()
) const;

/// @returns the referenced declaration and throws on error.
Declaration const& dereference(Identifier const& _identifier) const;
/// @returns the referenced declaration and throws on error.
Expand Down
19 changes: 1 addition & 18 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,7 @@ class ASTConstVisitor;
class ASTNode: private boost::noncopyable
{
public:
struct CompareByID
{
using is_transparent = void;

bool operator()(ASTNode const* _lhs, ASTNode const* _rhs) const
{
return _lhs->id() < _rhs->id();
}
bool operator()(ASTNode const* _lhs, int64_t _rhs) const
{
return _lhs->id() < _rhs;
}
bool operator()(int64_t _lhs, ASTNode const* _rhs) const
{
return _lhs < _rhs->id();
}
};

using CompareByID = frontend::ASTCompareByID<ASTNode>;
using SourceLocation = langutil::SourceLocation;

explicit ASTNode(int64_t _id, SourceLocation _location);
Expand Down
7 changes: 4 additions & 3 deletions libsolidity/ast/ASTAnnotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,17 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
/// List of contracts this contract creates, i.e. which need to be compiled first.
/// Also includes all contracts from @a linearizedBaseContracts.
std::set<ContractDefinition const*> contractDependencies;
/// Mapping containing the nodes that define the arguments for base constructors.
/// These can either be inheritance specifiers or modifier invocations.
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
/// A graph with edges representing calls between functions that may happen during contract construction.
SetOnce<std::shared_ptr<CallGraph const>> creationCallGraph;
/// A graph with edges representing calls between functions that may happen in a deployed contract.
SetOnce<std::shared_ptr<CallGraph const>> deployedCallGraph;

/// List of contracts whose bytecode is referenced by this contract, e.g. through "new".
/// The Value represents the ast node that referenced the contract.
std::map<ContractDefinition const*, ASTNode const*, ASTCompareByID<ContractDefinition>> contractDependencies;
};

struct CallableDeclarationAnnotation: DeclarationAnnotation
Expand Down
19 changes: 19 additions & 0 deletions libsolidity/ast/ASTForward.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ class StructuredDocumentation;

class VariableScope;

template <class T>
struct ASTCompareByID
{
using is_transparent = void;

bool operator()(T const* _lhs, T const* _rhs) const
{
return _lhs->id() < _rhs->id();
}
bool operator()(T const* _lhs, int64_t _rhs) const
{
return _lhs->id() < _rhs;
}
bool operator()(int64_t _lhs, T const* _rhs) const
{
return _lhs < _rhs->id();
}
};

// Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do
// not do reference counting but point to a special memory area that is completely released
// explicitly.
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
#include <algorithm>
#include <limits>
#include <type_traits>
#include <range/v3/view/map.hpp>

using namespace ranges;
using namespace std;
using namespace solidity::langutil;

Expand Down Expand Up @@ -269,7 +271,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("baseContracts", toJson(_node.baseContracts())),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies | views::keys)),
make_pair("nodes", toJson(_node.subNodes())),
make_pair("scope", idOrNull(_node.scope()))
};
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/CallGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct CallGraph

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

/// Events that may get emitted by functions present in the graph.
std::set<EventDefinition const*, ASTNode::CompareByID> emittedEvents;
Expand Down
120 changes: 98 additions & 22 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@
#include <libsolutil/SwarmHash.h>
#include <libsolutil/IpfsHash.h>
#include <libsolutil/JSON.h>
#include <libsolutil/Algorithms.h>

#include <json/json.h>

#include <boost/algorithm/string/replace.hpp>
#include <utility>
#include <map>
#include <range/v3/view/concat.hpp>

#include <boost/algorithm/string/replace.hpp>

using namespace std;
using namespace solidity;
Expand Down Expand Up @@ -104,6 +108,94 @@ CompilerStack::~CompilerStack()
TypeProvider::reset();
}

void CompilerStack::createAndAssignCallGraphs()
{
for (Source const* source: m_sourceOrder)
{
if (!source->ast)
continue;

for (ContractDefinition const* contract: ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes()))
{
ContractDefinitionAnnotation& annotation =
m_contracts.at(contract->fullyQualifiedName()).contract->annotation();

annotation.creationCallGraph = make_unique<CallGraph>(
FunctionCallGraphBuilder::buildCreationGraph(*contract)
);
annotation.deployedCallGraph = make_unique<CallGraph>(
FunctionCallGraphBuilder::buildDeployedGraph(
*contract,
**annotation.creationCallGraph
)
);

solAssert(annotation.contractDependencies.empty(), "contractDependencies expected to be empty?!");

annotation.contractDependencies = annotation.creationCallGraph->get()->bytecodeDependency;

for (auto const& [dependencyContract, referencee]: annotation.deployedCallGraph->get()->bytecodeDependency)
annotation.contractDependencies.emplace(dependencyContract, referencee);
}
}
}

void CompilerStack::findAndReportCyclicContractDependencies()
{
// Cycles we found, used to avoid duplicate reports for the same reference
set<ASTNode const*, ASTNode::CompareByID> foundCycles;

for (Source const* source: m_sourceOrder)
{
if (!source->ast)
continue;

for (ContractDefinition const* contractDefinition: ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes()))
{
util::CycleDetector<ContractDefinition> cycleDetector{[&](
ContractDefinition const& _contract,
util::CycleDetector<ContractDefinition>& _cycleDetector,
size_t _depth
)
{
// No specific reason for exactly that number, just a limit we're unlikely to hit.
if (_depth >= 256)
m_errorReporter.fatalTypeError(
7864_error,
_contract.location(),
"Contract dependencies exhausting cyclic dependency validator"
);

for (auto& [dependencyContract, referencee]: _contract.annotation().contractDependencies)
if (_cycleDetector.run(*dependencyContract))
return;
}};

ContractDefinition const* cycle = cycleDetector.run(*contractDefinition);

if (!cycle)
continue;

ASTNode const* referencee = contractDefinition->annotation().contractDependencies.at(cycle);

if (foundCycles.find(referencee) != foundCycles.end())
continue;

SecondarySourceLocation secondaryLocation{};
secondaryLocation.append("Referenced contract is here:"s, cycle->location());

m_errorReporter.typeError(
7813_error,
referencee->location(),
secondaryLocation,
"Circular reference to contract bytecode either via \"new\" or \"type(...).creationCode\" / \"type(...).runtimeCode\"."
);

foundCycles.emplace(referencee);
}
}
}

std::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping)
{
auto eq = find(_remapping.begin(), _remapping.end(), '=');
Expand Down Expand Up @@ -400,27 +492,11 @@ bool CompilerStack::analyze()
if (source->ast && !typeChecker.checkTypeRequirements(*source->ast))
noErrors = false;

// Create & assign callgraphs and check for contract dependency cycles
if (noErrors)
{
for (Source const* source: m_sourceOrder)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto const* contractDefinition = dynamic_cast<ContractDefinition*>(node.get()))
{
Contract& contractState = m_contracts.at(contractDefinition->fullyQualifiedName());

contractState.contract->annotation().creationCallGraph = make_unique<CallGraph>(
FunctionCallGraphBuilder::buildCreationGraph(
*contractDefinition
)
);
contractState.contract->annotation().deployedCallGraph = make_unique<CallGraph>(
FunctionCallGraphBuilder::buildDeployedGraph(
*contractDefinition,
**contractState.contract->annotation().creationCallGraph
)
);
}
createAndAssignCallGraphs();
findAndReportCyclicContractDependencies();
}

if (noErrors)
Expand Down Expand Up @@ -1206,7 +1282,7 @@ void CompilerStack::compileContract(
if (_otherCompilers.count(&_contract))
return;

for (auto const* dependency: _contract.annotation().contractDependencies)
for (auto const& [dependency, referencee]: _contract.annotation().contractDependencies)
compileContract(*dependency, _otherCompilers);

if (!_contract.canBeDeployed())
Expand Down Expand Up @@ -1292,7 +1368,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
);

string dependenciesSource;
for (auto const* dependency: _contract.annotation().contractDependencies)
for (auto const& [dependency, referencee]: _contract.annotation().contractDependencies)
generateIR(*dependency);

if (!_contract.canBeDeployed())
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ class CompilerStack: boost::noncopyable
mutable std::optional<std::string const> runtimeSourceMapping;
};

void createAndAssignCallGraphs();
void findAndReportCyclicContractDependencies();

/// Loads the missing sources from @a _ast (named @a _path) using the callback
/// @a m_readFile and stores the absolute paths of all imports in the AST annotations.
/// @returns the newly loaded sources.
Expand Down Expand Up @@ -495,6 +498,7 @@ class CompilerStack: boost::noncopyable
std::shared_ptr<GlobalContext> m_globalContext;
std::vector<Source const*> m_sourceOrder;
std::map<std::string const, Contract> m_contracts;

langutil::ErrorList m_errorList;
langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false;
Expand Down
5 changes: 1 addition & 4 deletions test/libsolidity/ASTJSON/base_constructor_call.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,7 @@
"src": "50:1:1"
}
],
"contractDependencies":
[
7
],
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 17,
Expand Down
Loading

0 comments on commit e590a99

Please sign in to comment.