Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth
2020-04-07 11:14:28 +02:00
288 changed files with 6927 additions and 2129 deletions
+6
View File
@@ -14,6 +14,8 @@ set(sources
analysis/DeclarationContainer.h
analysis/DocStringAnalyser.cpp
analysis/DocStringAnalyser.h
analysis/ImmutableValidator.cpp
analysis/ImmutableValidator.h
analysis/GlobalContext.cpp
analysis/GlobalContext.h
analysis/NameAndTypeResolver.cpp
@@ -100,8 +102,12 @@ set(sources
formal/SMTPortfolio.cpp
formal/SMTPortfolio.h
formal/SolverInterface.h
formal/Sorts.cpp
formal/Sorts.h
formal/SSAVariable.cpp
formal/SSAVariable.h
formal/SymbolicState.cpp
formal/SymbolicState.h
formal/SymbolicTypes.cpp
formal/SymbolicTypes.h
formal/SymbolicVariables.cpp
+5 -5
View File
@@ -54,11 +54,11 @@ public:
TypePointer evaluate(Expression const& _expr);
private:
virtual void endVisit(BinaryOperation const& _operation);
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
virtual void endVisit(Identifier const& _identifier);
virtual void endVisit(TupleExpression const& _tuple);
void endVisit(BinaryOperation const& _operation) override;
void endVisit(UnaryOperation const& _operation) override;
void endVisit(Literal const& _literal) override;
void endVisit(Identifier const& _identifier) override;
void endVisit(TupleExpression const& _tuple) override;
void setType(ASTNode const& _node, TypePointer const& _type);
TypePointer type(ASTNode const& _node);
+220
View File
@@ -0,0 +1,220 @@
/*
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/>.
*/
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolutil/CommonData.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace solidity::frontend;
void ImmutableValidator::analyze()
{
m_inConstructionContext = true;
auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | boost::adaptors::reversed;
for (ContractDefinition const* contract: linearizedContracts)
for (VariableDeclaration const* stateVar: contract->stateVariables())
if (stateVar->value())
{
stateVar->value()->accept(*this);
solAssert(m_initializedStateVariables.emplace(stateVar).second, "");
}
for (ContractDefinition const* contract: linearizedContracts)
if (contract->constructor())
visitCallableIfNew(*contract->constructor());
for (ContractDefinition const* contract: linearizedContracts)
for (std::shared_ptr<InheritanceSpecifier> const inheritSpec: contract->baseContracts())
if (auto args = inheritSpec->arguments())
ASTNode::listAccept(*args, *this);
m_inConstructionContext = false;
for (ContractDefinition const* contract: linearizedContracts)
{
for (auto funcDef: contract->definedFunctions())
visitCallableIfNew(*funcDef);
for (auto modDef: contract->functionModifiers())
visitCallableIfNew(*modDef);
}
checkAllVariablesInitialized(m_currentContract.location());
}
bool ImmutableValidator::visit(FunctionDefinition const& _functionDefinition)
{
return analyseCallable(_functionDefinition);
}
bool ImmutableValidator::visit(ModifierDefinition const& _modifierDefinition)
{
return analyseCallable(_modifierDefinition);
}
bool ImmutableValidator::visit(MemberAccess const& _memberAccess)
{
_memberAccess.expression().accept(*this);
if (auto contractType = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type))
if (!contractType->isSuper())
// external access, no analysis needed.
return false;
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _memberAccess);
else if (auto funcType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
if (funcType->kind() == FunctionType::Kind::Internal && funcType->hasDeclaration())
visitCallableIfNew(funcType->declaration());
return false;
}
bool ImmutableValidator::visit(IfStatement const& _ifStatement)
{
bool prevInBranch = m_inBranch;
_ifStatement.condition().accept(*this);
m_inBranch = true;
_ifStatement.trueStatement().accept(*this);
if (auto falseStatement = _ifStatement.falseStatement())
falseStatement->accept(*this);
m_inBranch = prevInBranch;
return false;
}
bool ImmutableValidator::visit(WhileStatement const& _whileStatement)
{
bool prevInLoop = m_inLoop;
m_inLoop = true;
_whileStatement.condition().accept(*this);
_whileStatement.body().accept(*this);
m_inLoop = prevInLoop;
return false;
}
void ImmutableValidator::endVisit(Identifier const& _identifier)
{
if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
visitCallableIfNew(callableDef->resolveVirtual(m_currentContract));
if (auto const varDecl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _identifier);
}
void ImmutableValidator::endVisit(Return const& _return)
{
if (m_currentConstructor != nullptr)
checkAllVariablesInitialized(_return.location());
}
bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration)
{
FunctionDefinition const* prevConstructor = m_currentConstructor;
m_currentConstructor = nullptr;
if (FunctionDefinition const* funcDef = dynamic_cast<decltype(funcDef)>(&_callableDeclaration))
{
ASTNode::listAccept(funcDef->modifiers(), *this);
if (funcDef->isConstructor())
m_currentConstructor = funcDef;
if (funcDef->isImplemented())
funcDef->body().accept(*this);
}
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
modDef->body().accept(*this);
m_currentConstructor = prevConstructor;
return false;
}
void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression)
{
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
return;
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
{
if (!m_currentConstructor)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables can only be initialized inline or assigned directly in the constructor."
);
else if (m_currentConstructor->annotation().contract->id() != _variableReference.annotation().contract->id())
m_errorReporter.typeError(
_expression.location(),
"Immutable variables must be initialized in the constructor of the contract they are defined in."
);
else if (m_inLoop)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables can only be initialized once, not in a while statement."
);
else if (m_inBranch)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables must be initialized unconditionally, not in an if statement."
);
if (!m_initializedStateVariables.emplace(&_variableReference).second)
m_errorReporter.typeError(
_expression.location(),
"Immutable state variable already initialized."
);
}
else if (m_inConstructionContext)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables cannot be read during contract creation time, which means "
"they cannot be read in the constructor or any function or modifier called from it."
);
}
void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location)
{
for (ContractDefinition const* contract: m_currentContract.annotation().linearizedBaseContracts)
for (VariableDeclaration const* varDecl: contract->stateVariables())
if (varDecl->immutable())
if (!util::contains(m_initializedStateVariables, varDecl))
m_errorReporter.typeError(
_location,
solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()),
"Construction control flow ends without initializing all immutable state variables."
);
}
void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration)
{
CallableDeclaration const* _callable = dynamic_cast<CallableDeclaration const*>(&_declaration);
solAssert(_callable != nullptr, "");
if (m_visitedCallables.emplace(_callable).second)
_declaration.accept(*this);
}
+78
View File
@@ -0,0 +1,78 @@
/*
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/>.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h>
#include <memory>
namespace solidity::frontend
{
/**
* Validates access and initialization of immutable variables:
* must be directly initialized in their respective c'tor
* can not be read by any function/modifier called by the c'tor (or the c'tor itself)
* must be initialized outside loops (only one initialization)
* must be initialized outside ifs (must be initialized unconditionally)
* must be initialized exactly once (no multiple statements)
* must be initialized exactly once (no early return to skip initialization)
*/
class ImmutableValidator: private ASTConstVisitor
{
using CallableDeclarationSet = std::set<CallableDeclaration const*, ASTNode::CompareByID>;
public:
ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition):
m_currentContract(_contractDefinition),
m_errorReporter(_errorReporter)
{ }
void analyze();
private:
bool visit(FunctionDefinition const& _functionDefinition);
bool visit(ModifierDefinition const& _modifierDefinition);
bool visit(MemberAccess const& _memberAccess);
bool visit(IfStatement const& _ifStatement);
bool visit(WhileStatement const& _whileStatement);
void endVisit(Identifier const& _identifier);
void endVisit(Return const& _return);
bool analyseCallable(CallableDeclaration const& _callableDeclaration);
void analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression);
void checkAllVariablesInitialized(langutil::SourceLocation const& _location);
void visitCallableIfNew(Declaration const& _declaration);
ContractDefinition const& m_currentContract;
CallableDeclarationSet m_visitedCallables;
std::set<VariableDeclaration const*, ASTNode::CompareByID> m_initializedStateVariables;
langutil::ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentConstructor = nullptr;
bool m_inLoop = false;
bool m_inBranch = false;
bool m_inConstructionContext = false;
};
}
+1 -1
View File
@@ -204,7 +204,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ class StaticAnalyzer: private ASTConstVisitor
public:
/// @param _errorReporter provides the error logging functionality.
explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter);
~StaticAnalyzer();
~StaticAnalyzer() override;
/// Performs static analysis on the given source unit and all of its sub-nodes.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
+3 -1
View File
@@ -303,7 +303,9 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
);
}
if (!_function.isImplemented() && !_function.modifiers().empty())
if (m_isInterface && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions in interfaces cannot have modifiers.");
else if (!_function.isImplemented() && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
return true;
+24 -17
View File
@@ -483,8 +483,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
);
}
else if (_variable.immutable())
{
if (!_variable.type()->isValueType())
m_errorReporter.typeError(_variable.location(), "Immutable variables cannot have a non-value type.");
if (
auto const* functionType = dynamic_cast<FunctionType const*>(_variable.type());
functionType && functionType->kind() == FunctionType::Kind::External
)
m_errorReporter.typeError(_variable.location(), "Immutable variables of external function type are not yet supported.");
solAssert(_variable.type()->sizeOnStack() == 1 || m_errorReporter.hasErrors(), "");
}
if (!_variable.isStateVariable())
{
@@ -742,6 +750,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration))
{
m_errorReporter.declarationError(_identifier.location, "Access to functions is not allowed in inline assembly.");
return size_t(-1);
}
else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
@@ -1063,17 +1073,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (!varDecl.annotation().type)
m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl)))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
string errorText{"Uninitialized storage pointer."};
solAssert(varDecl.referenceLocation() != VariableDeclaration::Location::Unspecified, "Expected a specified location at this point");
solAssert(m_scope, "");
m_errorReporter.declarationError(varDecl.location(), errorText);
}
}
else if (dynamic_cast<MappingType const*>(type(varDecl)))
if (dynamic_cast<MappingType const*>(type(varDecl)))
m_errorReporter.typeError(
varDecl.location(),
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
@@ -1319,11 +1319,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
{
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { &_type };
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
solAssert(
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
"Array sizes don't match or no errors generated."
"Array sizes don't match and no errors generated."
);
for (size_t i = 0; i < min(tupleExpression->components().size(), types.size()); i++)
@@ -1347,7 +1347,10 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
bool TypeChecker::visit(Assignment const& _assignment)
{
requireLValue(_assignment.leftHandSide());
requireLValue(
_assignment.leftHandSide(),
_assignment.assignmentOperator() == Token::Assign
);
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
@@ -1405,7 +1408,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
for (auto const& component: components)
if (component)
{
requireLValue(*component);
requireLValue(
*component,
_tuple.annotation().lValueOfOrdinaryAssignment
);
types.push_back(type(*component));
}
else
@@ -1490,7 +1496,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
Token op = _operation.getOperator();
bool const modifying = (op == Token::Inc || op == Token::Dec || op == Token::Delete);
if (modifying)
requireLValue(_operation.subExpression());
requireLValue(_operation.subExpression(), false);
else
_operation.subExpression().accept(*this);
TypePointer const& subExprType = type(_operation.subExpression());
@@ -2998,9 +3004,10 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
return true;
}
void TypeChecker::requireLValue(Expression const& _expression)
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
{
_expression.annotation().lValueRequested = true;
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);
if (_expression.annotation().isLValue)
+1 -1
View File
@@ -158,7 +158,7 @@ private:
/// convertible to @a _expectedType.
bool expectType(Expression const& _expression, Type const& _expectedType);
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
void requireLValue(Expression const& _expression);
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
ContractDefinition const* m_scope = nullptr;
+89
View File
@@ -217,6 +217,37 @@ ContractDefinitionAnnotation& ContractDefinition::annotation() const
return initAnnotation<ContractDefinitionAnnotation>();
}
ContractDefinition const* ContractDefinition::superContract(ContractDefinition const& _mostDerivedContract) const
{
auto const& hierarchy = _mostDerivedContract.annotation().linearizedBaseContracts;
auto it = find(hierarchy.begin(), hierarchy.end(), this);
solAssert(it != hierarchy.end(), "Base not found in inheritance hierarchy.");
++it;
if (it == hierarchy.end())
return nullptr;
else
{
solAssert(*it != this, "");
return *it;
}
}
FunctionDefinition const* ContractDefinition::nextConstructor(ContractDefinition const& _mostDerivedContract) const
{
ContractDefinition const* next = superContract(_mostDerivedContract);
if (next == nullptr)
return nullptr;
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
if (c == next || next == nullptr)
{
if (c->constructor())
return c->constructor();
next = nullptr;
}
return nullptr;
}
TypeNameAnnotation& TypeName::annotation() const
{
return initAnnotation<TypeNameAnnotation>();
@@ -319,6 +350,37 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
return initAnnotation<FunctionDefinitionAnnotation>();
}
FunctionDefinition const& FunctionDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(!isConstructor(), "");
// If we are not doing super-lookup and the function is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asCallableFunction(false);
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (FunctionDefinition const* function: c->definedFunctions())
if (
function->name() == name() &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(*functionType)
)
return *function;
}
solAssert(false, "Virtual function " + name() + " not found.");
return *this; // not reached
}
TypePointer ModifierDefinition::type() const
{
return TypeProvider::modifier(*this);
@@ -329,6 +391,33 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
return initAnnotation<ModifierDefinitionAnnotation>();
}
ModifierDefinition const& ModifierDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(_searchStart == nullptr, "Used super in connection with modifiers.");
// If we are not doing super-lookup and the modifier is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (ModifierDefinition const* modifier: c->functionModifiers())
if (modifier->name() == name())
return *modifier;
}
solAssert(false, "Virtual modifier " + name() + " not found.");
return *this; // not reached
}
TypePointer EventDefinition::type() const
{
return TypeProvider::function(*this);
+54
View File
@@ -62,6 +62,24 @@ 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 SourceLocation = langutil::SourceLocation;
explicit ASTNode(int64_t _id, SourceLocation const& _location);
@@ -500,6 +518,10 @@ public:
bool abstract() const { return m_abstract; }
ContractDefinition const* superContract(ContractDefinition const& _mostDerivedContract) const;
/// @returns the next constructor in the inheritance hierarchy.
FunctionDefinition const* nextConstructor(ContractDefinition const& _mostDerivedContract) const;
private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes;
@@ -689,6 +711,18 @@ public:
CallableDeclarationAnnotation& annotation() const override = 0;
/// Performs virtual or super function/modifier lookup:
/// If @a _searchStart is nullptr, performs virtual function lookup, i.e.
/// searches the inheritance hierarchy of @a _mostDerivedContract towards the base
/// and returns the first function/modifier definition that
/// is overwritten by this callable.
/// If @a _searchStart is non-null, starts searching only from that contract, but
/// still in the hierarchy of @a _mostDerivedContract.
virtual CallableDeclaration const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const = 0;
protected:
ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
@@ -799,6 +833,12 @@ public:
CallableDeclaration::virtualSemantics() ||
(annotation().contract && annotation().contract->isInterface());
}
FunctionDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
StateMutability m_stateMutability;
Token const m_kind;
@@ -945,6 +985,12 @@ public:
ModifierDefinitionAnnotation& annotation() const override;
ModifierDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
ASTPointer<Block> m_body;
};
@@ -1010,6 +1056,14 @@ public:
EventDefinitionAnnotation& annotation() const override;
CallableDeclaration const& resolveVirtual(
ContractDefinition const&,
ContractDefinition const*
) const override
{
return *this;
}
private:
bool m_anonymous = false;
};
+30
View File
@@ -47,6 +47,14 @@ using TypePointer = Type const*;
struct ASTAnnotation
{
ASTAnnotation() = default;
ASTAnnotation(ASTAnnotation const&) = delete;
ASTAnnotation(ASTAnnotation&&) = delete;
ASTAnnotation& operator=(ASTAnnotation const&) = delete;
ASTAnnotation& operator=(ASTAnnotation&&) = delete;
virtual ~ASTAnnotation() = default;
};
@@ -58,7 +66,16 @@ struct DocTag
struct StructurallyDocumentedAnnotation
{
StructurallyDocumentedAnnotation() = default;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation&&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation&&) = delete;
virtual ~StructurallyDocumentedAnnotation() = default;
/// Mapping docstring tag name -> content.
std::multimap<std::string, DocTag> docTags;
};
@@ -75,6 +92,16 @@ struct SourceUnitAnnotation: ASTAnnotation
struct ScopableAnnotation
{
ScopableAnnotation() = default;
ScopableAnnotation(ScopableAnnotation const&) = delete;
ScopableAnnotation(ScopableAnnotation&&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation const&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation&&) = delete;
virtual ~ScopableAnnotation() = default;
/// The scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope = nullptr;
@@ -208,6 +235,9 @@ struct ExpressionAnnotation: ASTAnnotation
bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required.
bool lValueRequested = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resoultion
+18
View File
@@ -41,7 +41,16 @@ namespace solidity::frontend
class ASTVisitor
{
public:
ASTVisitor() = default;
ASTVisitor(ASTVisitor const&) = delete;
ASTVisitor(ASTVisitor&&) = delete;
ASTVisitor& operator=(ASTVisitor const&) = delete;
ASTVisitor& operator=(ASTVisitor&&) = delete;
virtual ~ASTVisitor() = default;
virtual bool visit(SourceUnit& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective& _node) { return visitNode(_node); }
@@ -158,7 +167,16 @@ protected:
class ASTConstVisitor
{
public:
ASTConstVisitor() = default;
ASTConstVisitor(ASTConstVisitor const&) = delete;
ASTConstVisitor(ASTConstVisitor&&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor const&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor&&) = delete;
virtual ~ASTConstVisitor() = default;
virtual bool visit(SourceUnit const& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective const& _node) { return visitNode(_node); }
+11 -1
View File
@@ -2012,6 +2012,16 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<VariableDeclaration const*> ContractType::immutableVariables() const
{
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (variable->immutable())
variables.push_back(variable);
return variables;
}
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
@@ -2328,7 +2338,7 @@ TypePointers StructType::memoryMemberTypes() const
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(variable->annotation().type);
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}
+2
View File
@@ -895,6 +895,8 @@ public:
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
/// @returns a list of all immutable variables (including inherited) of the contract.
std::vector<VariableDeclaration const*> immutableVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
+54 -64
View File
@@ -71,6 +71,49 @@ void CompilerContext::addStateVariable(
m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
}
void CompilerContext::addImmutable(VariableDeclaration const& _variable)
{
solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable.");
solUnimplementedAssert(_variable.annotation().type->isValueType(), "Only immutable variables of value type are supported.");
solAssert(m_runtimeContext, "Attempted to register an immutable variable for runtime code generation.");
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory;
solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap.");
*m_reservedMemory += _variable.annotation().type->memoryHeadSize();
}
size_t CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const
{
solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried.");
solAssert(m_runtimeContext, "Attempted to fetch the memory offset of an immutable variable during runtime code generation.");
return m_immutableVariables.at(&_variable);
}
vector<string> CompilerContext::immutableVariableSlotNames(VariableDeclaration const& _variable)
{
string baseName = to_string(_variable.id());
solAssert(_variable.annotation().type->sizeOnStack() > 0, "");
if (_variable.annotation().type->sizeOnStack() == 1)
return {baseName};
vector<string> names;
auto collectSlotNames = [&](string const& _baseName, TypePointer type, auto const& _recurse) -> void {
for (auto const& [slot, type]: type->stackItems())
if (type)
_recurse(_baseName + " " + slot, type, _recurse);
else
names.emplace_back(_baseName);
};
collectSlotNames(baseName, _variable.annotation().type, collectSlotNames);
return names;
}
size_t CompilerContext::reservedMemory()
{
solAssert(m_reservedMemory.has_value(), "Reserved memory was used before ");
size_t reservedMemory = *m_reservedMemory;
m_reservedMemory = std::nullopt;
return reservedMemory;
}
void CompilerContext::startFunction(Declaration const& _function)
{
m_functionCompilationQueue.startFunction(_function);
@@ -223,32 +266,18 @@ evmasm::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration con
return m_functionCompilationQueue.entryLabelIfExists(_declaration);
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function)
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_function.scope()))
if (scope->isLibrary())
return _function;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin());
}
FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base)
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, superContract(_base));
solAssert(m_mostDerivedContract, "No most derived contract set.");
ContractDefinition const* super = _base.superContract(mostDerivedContract());
solAssert(super, "Super contract not available.");
return _function.resolveVirtual(mostDerivedContract(), super);
}
FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const
ContractDefinition const& CompilerContext::mostDerivedContract() const
{
vector<ContractDefinition const*>::const_iterator it = superContract(_contract);
for (; it != m_inheritanceHierarchy.end(); ++it)
if ((*it)->constructor())
return (*it)->constructor();
return nullptr;
solAssert(m_mostDerivedContract, "Most derived contract not set.");
return *m_mostDerivedContract;
}
Declaration const* CompilerContext::nextFunctionToCompile() const
@@ -256,24 +285,6 @@ Declaration const* CompilerContext::nextFunctionToCompile() const
return m_functionCompilationQueue.nextFunctionToCompile();
}
ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier(
ModifierDefinition const& _modifier
) const
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (scope->isLibrary())
return _modifier;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers())
if (modifier->name() == _modifier.name())
return *modifier;
solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy.");
}
unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const
{
auto res = m_localVariables.find(&_declaration);
@@ -500,32 +511,11 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
#endif
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart
)
LinkerObject const& CompilerContext::assembledObject() const
{
string name = _function.name();
FunctionType functionType(_function);
auto it = _searchStart;
for (; it != m_inheritanceHierarchy.end(); ++it)
for (FunctionDefinition const* function: (*it)->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
return _function; // not reached
}
vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract);
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy.");
return ++it;
LinkerObject const& object = m_asm->assemble();
solAssert(object.immutableReferences.empty(), "Leftover immutables.");
return object;
}
string CompilerContext::revertReasonIfDebug(string const& _message)
+27 -18
View File
@@ -64,6 +64,7 @@ public:
m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_reservedMemory{0},
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
@@ -80,6 +81,16 @@ public:
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addImmutable(VariableDeclaration const& _declaration);
/// @returns the reserved memory for storing the value of the immutable @a _variable during contract creation.
size_t immutableMemoryOffset(VariableDeclaration const& _variable) const;
/// @returns a list of slot names referring to the stack slots of an immutable variable.
static std::vector<std::string> immutableVariableSlotNames(VariableDeclaration const& _variable);
/// @returns the reserved memory and resets it to mark it as used.
size_t reservedMemory();
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(Declaration const& _declaration);
/// Removes all local variables currently allocated above _stackHeight.
@@ -103,15 +114,12 @@ public:
/// @returns the entry label of the given function. Might return an AssemblyItem of type
/// UndefinedItem if it does not exist yet.
evmasm::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
/// @returns the entry label of the given function and takes overrides into account.
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
/// @returns the function that overrides the given declaration from the most derived class just
/// above _base in the current inheritance hierarchy.
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
/// @returns the next constructor in the inheritance hierarchy.
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
/// Sets the contract currently being compiled - the most derived one.
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const;
/// @returns the next function in the queue of functions that are still to be compiled
/// (i.e. that were referenced during compilation but where we did not yet generate code for).
@@ -160,7 +168,6 @@ public:
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@@ -217,6 +224,10 @@ public:
evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
/// Appends the address (virtual, will be filled in by linker) of a library.
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
/// Appends an immutable variable. The value will be filled in by the constructor.
void appendImmutable(std::string const& _identifier) { m_asm->appendImmutable(_identifier); }
/// Appends an assignment to an immutable variable. Only valid in creation code.
void appendImmutableAssignment(std::string const& _identifier) { m_asm->appendImmutableAssignment(_identifier); }
/// Appends a zero-address that can be replaced by something else at deploy time (if the
/// position in bytecode is known).
void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); }
@@ -282,7 +293,7 @@ public:
return m_asm->assemblyJSON(_indicies);
}
evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
evmasm::LinkerObject const& assembledObject() const;
evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); }
/**
@@ -300,14 +311,6 @@ public:
RevertStrings revertStrings() const { return m_revertStrings; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.
FunctionDefinition const& resolveVirtualFunction(
FunctionDefinition const& _function,
std::vector<ContractDefinition const*>::const_iterator _searchStart
);
/// @returns an iterator to the contract directly above the given contract.
std::vector<ContractDefinition const*>::const_iterator superContract(ContractDefinition const& _contract) const;
/// Updates source location set in the assembly.
void updateSourceLocation();
@@ -355,13 +358,19 @@ private:
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Memory offsets reserved for the values of immutable variables during contract creation.
std::map<VariableDeclaration const*, size_t> m_immutableVariables;
/// Total amount of reserved memory. Reserved memory is used to store immutable variables during contract creation.
/// This has to be finalized before initialiseFreeMemoryPointer() is called. That function
/// will reset the optional to verify that.
std::optional<size_t> m_reservedMemory = {0};
/// Offsets of local variables on the stack (relative to stack base).
/// This needs to be a stack because if a modifier contains a local variable and this
/// modifier is applied twice, the position of the variable needs to be restored
/// after the nested modifier is left.
std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
/// List of current inheritance hierarchy from derived to base.
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
/// The contract currently being compiled. Virtual function lookup starts from this contarct.
ContractDefinition const* m_mostDerivedContract = nullptr;
/// Stack of current visited AST nodes, used for location attachment
std::stack<ASTNode const*> m_visitedNodes;
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.
+3 -1
View File
@@ -51,7 +51,9 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi
void CompilerUtils::initialiseFreeMemoryPointer()
{
m_context << u256(generalPurposeMemoryStart);
size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, "");
m_context << (u256(generalPurposeMemoryStart) + reservedMemory);
storeFreeMemoryPointer();
}
+35 -11
View File
@@ -129,7 +129,9 @@ void ContractCompiler::initializeContext(
{
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
if (m_runtimeCompiler)
registerImmutableVariables(_contract);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract);
m_context.resetVisitedNodes(&_contract);
@@ -157,7 +159,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
if (FunctionDefinition const* constructor = _contract.constructor())
appendConstructor(*constructor);
else if (auto c = m_context.nextConstructor(_contract))
else if (auto c = _contract.nextConstructor(m_context.mostDerivedContract()))
appendBaseConstructor(*c);
else
appendCallValueCheck();
@@ -183,10 +185,26 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
m_context << deployRoutine;
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
ContractType contractType(_contract);
auto const& immutables = contractType.immutableVariables();
// Push all immutable values on the stack.
for (auto const& immutable: immutables)
CompilerUtils(m_context).loadFromMemory(m_context.immutableMemoryOffset(*immutable), *immutable->annotation().type);
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << Instruction::DUP1;
if (immutables.empty())
m_context << Instruction::DUP1;
m_context.pushSubroutineOffset(m_context.runtimeSub());
m_context << u256(0) << Instruction::CODECOPY;
// Assign immutable values from stack in reversed order.
for (auto const& immutable: immutables | boost::adaptors::reversed)
{
auto slotNames = m_context.immutableVariableSlotNames(*immutable);
for (auto&& slotName: slotNames | boost::adaptors::reversed)
m_context.appendImmutableAssignment(slotName);
}
if (!immutables.empty())
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << u256(0) << Instruction::RETURN;
return m_context.runtimeSub();
@@ -521,11 +539,18 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
void ContractCompiler::registerImmutableVariables(ContractDefinition const& _contract)
{
solAssert(m_runtimeCompiler, "Attempted to register immutables for runtime code generation.");
for (auto const& var: ContractType(_contract).immutableVariables())
m_context.addImmutable(*var);
}
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
{
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant() && !variable->immutable())
if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
}
@@ -541,8 +566,6 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration);
else if (_variableDeclaration.immutable())
solUnimplementedAssert(false, "");
else
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration);
@@ -573,7 +596,9 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
appendStackVariableInitialisation(*variable);
if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
if (auto c = dynamic_cast<ContractDefinition const&>(*_function.scope()).nextConstructor(
m_context.mostDerivedContract()
))
appendBaseConstructor(*c);
solAssert(m_returnTags.empty(), "");
@@ -664,7 +689,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract());
auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(functionEntryLabel.data()));
@@ -1310,10 +1335,9 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode();
else
{
ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>(
ModifierDefinition const& modifier = dynamic_cast<ModifierDefinition const&>(
*modifierInvocation->name()->annotation().referencedDeclaration
);
ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier);
).resolveVirtual(m_context.mostDerivedContract());
CompilerContext::LocationSetter locationSetter(m_context, modifier);
std::vector<ASTPointer<Expression>> const& modifierArguments =
modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector<ASTPointer<Expression>>();
+1
View File
@@ -99,6 +99,7 @@ private:
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract);
void registerImmutableVariables(ContractDefinition const& _contract);
void initializeStateVariables(ContractDefinition const& _contract);
bool visit(VariableDeclaration const& _variableDeclaration) override;
+31 -9
View File
@@ -73,7 +73,10 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
utils().convertType(*type, *_varDecl.annotation().type);
type = _varDecl.annotation().type;
}
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
else
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
}
void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl)
@@ -88,16 +91,22 @@ void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration co
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
{
solAssert(!_varDecl.isConstant() && !_varDecl.immutable(), "");
solAssert(!_varDecl.isConstant(), "");
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
if (_varDecl.immutable())
solAssert(paramTypes.empty(), "");
m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes));
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
if (!_varDecl.immutable())
{
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
}
TypePointer returnType = _varDecl.annotation().type;
@@ -179,6 +188,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType))
{
solAssert(!_varDecl.immutable(), "");
// remove offset
m_context << Instruction::POP;
auto const& names = accessorType.returnParameterNames();
@@ -205,7 +215,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{
// simple value or array
solAssert(returnTypes.size() == 1, "");
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).retrieveValue(SourceLocation());
else
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
utils().convertType(*returnType, *returnTypes.front());
retSizeOnStack = returnTypes.front()->sizeOnStack();
}
@@ -572,7 +585,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Do not directly visit the identifier, because this way, we can avoid
// the runtime entry label to be created at the creation time context.
CompilerContext::LocationSetter locationSetter2(m_context, *identifier);
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false);
utils().pushCombinedFunctionEntryLabel(
functionDef->resolveVirtual(m_context.mostDerivedContract()),
false
);
shortcutTaken = true;
}
}
@@ -992,6 +1008,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Fetch requested length.
acceptAndConvert(*arguments[0], *TypeProvider::uint256());
// Make sure we can allocate memory without overflow
m_context << u256(0xffffffffffffffff);
m_context << Instruction::DUP2;
m_context << Instruction::GT;
m_context.appendConditionalRevert();
// Stack: requested_length
utils().fetchFreeMemoryPointer();
@@ -1861,7 +1883,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
// we want to avoid having a reference to the runtime function entry point in the
// constructor context, since this would force the compiler to include unreferenced
// internal functions in the runtime contex.
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
utils().pushCombinedFunctionEntryLabel(functionDef->resolveVirtual(m_context.mostDerivedContract()));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
@@ -2436,7 +2458,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex
if (_variable.isConstant())
acceptAndConvert(*_variable.value(), *_variable.annotation().type);
else if (_variable.immutable())
solUnimplemented("");
setLValue<ImmutableItem>(_expression, _variable);
else
setLValueFromDeclaration(_variable, _expression);
}
+37
View File
@@ -144,9 +144,46 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
m_context << Instruction::POP;
}
ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable):
LValue(_compilerContext, _variable.annotation().type), m_variable(_variable)
{
solAssert(_variable.immutable(), "");
}
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
{
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time.");
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
m_context.appendImmutable(slotName);
}
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
{
CompilerUtils utils(m_context);
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(_sourceType.isValueType(), "");
utils.convertType(_sourceType, *m_dataType, true);
m_context << m_context.immutableMemoryOffset(m_variable);
if (_move)
utils.moveIntoStack(m_dataType->sizeOnStack());
else
utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack());
utils.storeInMemoryDynamic(*m_dataType, false);
m_context << Instruction::POP;
}
void ImmutableItem::setToZero(SourceLocation const&, bool) const
{
solAssert(false, "Attempted to set immutable variable to zero.");
}
StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
StorageItem(_compilerContext, *_declaration.annotation().type)
{
solAssert(!_declaration.immutable(), "");
auto const& location = m_context.storageLocationOfVariable(_declaration);
m_context << location.first << u256(location.second);
}
+35 -10
View File
@@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolutil/Common.h>
#include <liblangutil/SourceLocation.h>
#include <memory>
#include <vector>
@@ -82,12 +83,12 @@ public:
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -108,12 +109,12 @@ public:
MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true);
unsigned sizeOnStack() const override { return 1; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -122,6 +123,30 @@ private:
bool m_padded = false;
};
/**
* Reference to an immutable variable. During contract creation this refers to a location in memory. At the
* end of contract creation the values from these memory locations are copied into all occurrences of the immutable
* variable in the runtime code.
*/
class ImmutableItem: public LValue
{
public:
ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable);
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
VariableDeclaration const& m_variable;
};
/**
* Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>,
* where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied
@@ -136,12 +161,12 @@ public:
StorageItem(CompilerContext& _compilerContext, Type const& _type);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -158,12 +183,12 @@ public:
StorageByteArrayElement(CompilerContext& _compilerContext);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@@ -180,12 +205,12 @@ public:
TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues);
unsigned sizeOnStack() const override;
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
+146 -24
View File
@@ -658,6 +658,8 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
solAssert(_type.baseType()->isValueType(), "");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@@ -794,6 +796,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
@@ -1178,7 +1181,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, value)
}
}
)")
("functionName", functionName)
.render();
@@ -1202,7 +1205,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, <cleanup>(value))
}
}
)")
("functionName", functionName)
("cleanup", cleanupFunction(_type))
@@ -1334,28 +1337,112 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
if (_type.baseType()->hasSimpleZeroValueInMemory())
return zeroMemoryFunction(*_type.baseType());
return zeroComplexMemoryArrayFunction(_type);
}
string functionName = "allocate_memory_array_" + _type.identifier();
string YulUtilFunctions::zeroMemoryFunction(Type const& _type)
{
solAssert(_type.hasSimpleZeroValueInMemory(), "");
string functionName = "zero_memory_chunk_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length))
<?dynamic>
mstore(memPtr, length)
</dynamic>
function <functionName>(dataStart, dataSizeInBytes) {
calldatacopy(dataStart, calldatasize(), dataSizeInBytes)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
{
solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), "");
string functionName = "zero_complex_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.memoryStride() == 32, "");
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } {
mstore(add(dataStart, i), <zeroValue>())
}
}
)")
("functionName", functionName)
("stride", to_string(_type.memoryStride()))
("zeroValue", zeroValueFunction(*_type.baseType(), false))
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
let allocSize := <allocSize>(length)
memPtr := <alloc>(allocSize)
let dataStart := memPtr
let dataSize := allocSize
<?dynamic>
dataStart := add(dataStart, 32)
dataSize := sub(dataSize, 32)
mstore(memPtr, length)
</dynamic>
<zeroArrayFunction>(dataStart, dataSize)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("zeroArrayFunction", zeroMemoryArrayFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
let allocSize := <allocSize>()
memPtr := <alloc>(allocSize)
let offset := memPtr
<#member>
mstore(offset, <zeroValue>())
offset := add(offset, 32)
</member>
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::Function)
@@ -1884,23 +1971,58 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type)
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
solAssert(_type.category() != Type::Category::Mapping, "");
string const functionName = "zero_value_for_" + _type.identifier();
string const functionName = "zero_value_for_" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
}
)")
FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type);
if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes)
return Whiskers(R"(
function <functionName>() -> retAddress, retFunction {
retAddress := 0
retFunction := 0
}
)")
("functionName", functionName)
("body", "ret := 0x0")
.render();
});
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
}
)");
templ("functionName", functionName);
if (_type.isValueType())
{
solAssert((
_type.hasSimpleZeroValueInMemory() ||
(fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External))
), "");
templ("zeroValue", "0");
}
else
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
if (_type.isDynamicallySized())
templ("zeroValue", to_string(CompilerUtils::zeroPointer));
else
templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + to_string(unsigned(arrayType->length())) + ")");
}
else if (auto const* structType = dynamic_cast<StructType const*>(&_type))
templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()");
else
solUnimplementedAssert(false, "");
}
return templ.render();
});
}
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
+24 -4
View File
@@ -37,6 +37,7 @@ class Type;
class ArrayType;
class MappingType;
class IntegerType;
class StructType;
/**
* Component that can generate various useful Yul functions.
@@ -154,6 +155,7 @@ public:
/// to store an array in memory given its length (internally encoded, not ABI encoded).
/// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// for the data position of an array which is stored in that slot / memory area / calldata area.
@@ -250,10 +252,27 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of a function that allocates a memory array.
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that zeroes a chunk of memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryFunction(Type const& _type);
/// @returns the name of a function that zeroes an array
/// where the base does not have simple zero value in memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroComplexMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory array.
/// For dynamic arrays it adds space for length and stores it.
/// signature: (length) -> memPtr
std::string allocateMemoryArrayFunction(ArrayType const& _type);
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory struct.
/// signature: (members) -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @returns the name of the function that converts a value of type @a _from
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
@@ -288,8 +307,9 @@ public:
std::string negateNumberCheckedFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the
/// provided type
std::string zeroValueFunction(Type const& _type);
/// provided type.
/// @param _splitFunctionTypes if false, returns two zeroes
std::string zeroValueFunction(Type const& _type, bool _splitFunctionTypes = true);
/// @returns the name of a function that will set the given storage item to
/// zero
+12 -21
View File
@@ -22,6 +22,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
@@ -31,6 +32,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
{
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
return *m_mostDerivedContract;
}
IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
{
auto const& [it, didInsert] = m_localVariables.emplace(
@@ -70,26 +77,9 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
string name = _function.name();
FunctionType functionType(_function);
for (auto const& contract: m_inheritanceHierarchy)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
}
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
{
return functionName(virtualFunction(_functionDeclaration));
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
}
string IRGenerationContext::newYulVariable()
@@ -120,12 +110,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
templ("arrow", _out > 0 ? "->" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy)
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
FunctionType const* functionType = TypeProvider::function(*function)->asCallableFunction(false);
!function->isConstructor() &&
function->parameters().size() == _in &&
function->returnParameters().size() == _out
TupleType(functionType->parameterTypes()).sizeOnStack() == _in &&
TupleType(functionType->returnParameterTypes()).sizeOnStack() == _out
)
{
// 0 is reserved for uninitialized function pointers
+5 -5
View File
@@ -61,11 +61,12 @@ public:
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
/// Sets the most derived contract (the one currently being compiled)>
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
{
m_inheritanceHierarchy = std::move(_hierarchy);
m_mostDerivedContract = &_mostDerivedContract;
}
ContractDefinition const& mostDerivedContract() const;
IRVariable const& addLocalVariable(VariableDeclaration const& _varDecl);
@@ -81,7 +82,6 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable();
@@ -103,7 +103,7 @@ private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
ContractDefinition const* m_mostDerivedContract = nullptr;
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
+35 -5
View File
@@ -110,7 +110,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
t("functions", m_context.functionCollector().requestedFunctions());
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
@@ -133,6 +133,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
<initReturnVariables>
<body>
}
)");
@@ -142,9 +143,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
string retInit;
for (auto const& varDecl: _function.returnParameters())
{
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
retInit += generateInitialAssignment(*varDecl);
}
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("initReturnVariables", retInit);
t("body", generate(_function.body()));
return t.render();
});
@@ -226,6 +232,13 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
}
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
{
IRGeneratorForStatements generator(m_context, m_utils);
generator.initializeLocalVar(_varDecl);
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
// Initialization of state variables in base-to-derived order.
@@ -259,11 +272,28 @@ string IRGenerator::constructorCode(ContractDefinition const& _contract)
if (constructor)
{
solUnimplementedAssert(constructor->parameters().empty(), "");
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
// TODO base constructors
Whiskers t(R"X(
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
out << m_context.functionName(*constructor) + "()\n";
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
<constructorName>(<params>)
)X");
t("object", creationObjectName(_contract));
t("allocate", m_utils.allocationFunction());
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
t("params", suffixedVariableNameList("param_", 0, paramVars));
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
t("constructorName", m_context.functionName(*constructor));
out << t.render();
}
return out.str();
@@ -389,7 +419,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
);
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
for (auto const& var: ContractType(_contract).stateVariables())
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
+3
View File
@@ -61,6 +61,9 @@ private:
/// Generates a getter for the given declaration and returns its name
std::string generateGetter(VariableDeclaration const& _varDecl);
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
@@ -154,6 +154,19 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
auto const* type = _varDecl.type();
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
return;
IRVariable zero = zeroValue(*type);
assign(m_context.localVariable(_varDecl), zero);
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@@ -179,7 +192,10 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
{
declare(m_context.addLocalVariable(*decl));
initializeLocalVar(*decl);
}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
@@ -568,7 +584,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
define(_functionCall) <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
@@ -667,7 +686,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
"(" <<
value.commaSeparatedList() <<
")\n";
@@ -1165,7 +1184,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
return;
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
define(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n";
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
// TODO for the constant case, we have to be careful:
@@ -1436,6 +1455,12 @@ std::ostream& IRGeneratorForStatements::define(IRVariable const& _var)
return m_code;
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
}
void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare)
{
string output;
@@ -1455,10 +1480,15 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
_rhs.commaSeparatedList() <<
")\n";
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
IRVariable irVar{
"zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(),
_type
};
define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n";
return irVar;
}
void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr)
@@ -46,6 +46,8 @@ public:
/// Generates code to initialize the given state variable.
void initializeStateVar(VariableDeclaration const& _varDecl);
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
@@ -100,6 +102,11 @@ private:
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
/// @returns an IRVariable with the zero
/// value of @a _type.
/// @param _splitFunctionTypes if false, returns two zeroes
IRVariable zeroValue(Type const& _type, bool _splitFunctionTypes = true);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
+2 -1
View File
@@ -18,6 +18,7 @@
#include <libsolidity/formal/BMC.h>
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <boost/algorithm/string/replace.hpp>
@@ -376,7 +377,7 @@ void BMC::endVisit(FunctionCall const& _funCall)
SMTEncoder::endVisit(_funCall);
auto value = _funCall.arguments().front();
solAssert(value, "");
smt::Expression thisBalance = m_context.balance();
smt::Expression thisBalance = m_context.state().balance();
addVerificationTarget(
VerificationTarget::Type::Balance,
+16 -25
View File
@@ -79,10 +79,9 @@ void CHC::analyze(SourceUnit const& _source)
resetSourceAnalysis();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto genesisSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
addRule(genesis(), "genesis");
@@ -131,12 +130,9 @@ bool CHC::visit(ContractDefinition const& _contract)
clearIndices(&_contract);
// TODO create static instances for Bool/Int sorts in SolverInterface.
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto errorFunctionSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
string suffix = _contract.name() + "_" + to_string(_contract.id());
@@ -664,29 +660,25 @@ vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
smt::SortPointer CHC::constructorSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts,
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
m_stateSorts,
boolSort
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
stateSorts(_contract),
boolSort
smt::SortProvider::boolSort
);
}
@@ -703,8 +695,6 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
@@ -712,8 +702,8 @@ smt::SortPointer CHC::sort(FunctionDefinition const& _function)
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
smt::SortProvider::boolSort
);
}
@@ -725,13 +715,12 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
solAssert(fSort, "");
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables())
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
fSort->domain + varSorts,
boolSort
smt::SortProvider::boolSort
);
}
@@ -740,16 +729,14 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
smt::SortProvider::boolSort
);
}
@@ -974,7 +961,11 @@ smt::Expression CHC::predicate(FunctionCall const& _funCall)
for (auto const& var: function->returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(contract).at(function))(args);
if (contract->isLibrary())
return (*m_summaries.at(contract).at(function))(args);
solAssert(m_currentContract, "");
return (*m_summaries.at(m_currentContract).at(function))(args);
}
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
+2 -55
View File
@@ -25,13 +25,8 @@ using namespace solidity::util;
using namespace solidity::frontend::smt;
EncodingContext::EncodingContext():
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *this))
m_state(*this)
{
auto sort = make_shared<ArraySort>(
make_shared<Sort>(Kind::Int),
make_shared<Sort>(Kind::Int)
);
m_balances = make_unique<SymbolicVariable>(sort, "balances", *this);
}
void EncodingContext::reset()
@@ -39,8 +34,7 @@ void EncodingContext::reset()
resetAllVariables();
m_expressions.clear();
m_globalContext.clear();
m_thisAddress->resetIndex();
m_balances->resetIndex();
m_state.reset();
m_assertions.clear();
}
@@ -183,40 +177,6 @@ bool EncodingContext::knownGlobalSymbol(string const& _var) const
return m_globalContext.count(_var);
}
// Blockchain
Expression EncodingContext::thisAddress()
{
return m_thisAddress->currentValue();
}
Expression EncodingContext::balance()
{
return balance(m_thisAddress->currentValue());
}
Expression EncodingContext::balance(Expression _address)
{
return Expression::select(m_balances->currentValue(), move(_address));
}
void EncodingContext::transfer(Expression _from, Expression _to, Expression _value)
{
unsigned indexBefore = m_balances->index();
addBalance(_from, 0 - _value);
addBalance(_to, move(_value));
unsigned indexAfter = m_balances->index();
solAssert(indexAfter > indexBefore, "");
m_balances->increaseIndex();
/// Do not apply the transfer operation if _from == _to.
auto newBalances = Expression::ite(
move(_from) == move(_to),
m_balances->valueAtIndex(indexBefore),
m_balances->valueAtIndex(indexAfter)
);
addAssertion(m_balances->currentValue() == newBalances);
}
/// Solver.
Expression EncodingContext::assertions()
@@ -248,16 +208,3 @@ void EncodingContext::addAssertion(Expression const& _expr)
else
m_assertions.back() = _expr && move(m_assertions.back());
}
/// Private helpers.
void EncodingContext::addBalance(Expression _address, Expression _value)
{
auto newBalances = Expression::store(
m_balances->currentValue(),
_address,
balance(_address) + move(_value)
);
m_balances->increaseIndex();
addAssertion(newBalances == m_balances->currentValue());
}
+5 -20
View File
@@ -18,6 +18,7 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicVariables.h>
#include <unordered_map>
@@ -121,18 +122,6 @@ public:
bool knownGlobalSymbol(std::string const& _var) const;
//@}
/// Blockchain.
//@{
/// Value of `this` address.
Expression thisAddress();
/// @returns the symbolic balance of address `this`.
Expression balance();
/// @returns the symbolic balance of an address.
Expression balance(Expression _address);
/// Transfer _value from _from to _to.
void transfer(Expression _from, Expression _to, Expression _value);
//@}
/// Solver.
//@{
/// @returns conjunction of all added assertions.
@@ -148,10 +137,9 @@ public:
}
//@}
private:
/// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value);
SymbolicState& state() { return m_state; }
private:
/// Symbolic expressions.
//{@
/// Symbolic variables.
@@ -164,11 +152,8 @@ private:
/// variables and functions.
std::unordered_map<std::string, std::shared_ptr<smt::SymbolicVariable>> m_globalContext;
/// Symbolic `this` address.
std::unique_ptr<SymbolicAddressVariable> m_thisAddress;
/// Symbolic balances.
std::unique_ptr<SymbolicVariable> m_balances;
/// Symbolic representation of the blockchain state.
SymbolicState m_state;
//@}
/// Solver related.
+5 -4
View File
@@ -19,6 +19,7 @@
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <boost/range/adaptors.hpp>
@@ -619,10 +620,10 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
auto const& value = args.front();
solAssert(value, "");
smt::Expression thisBalance = m_context.balance();
smt::Expression thisBalance = m_context.state().balance();
setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context);
m_context.transfer(m_context.thisAddress(), expr(address), expr(*value));
m_context.state().transfer(m_context.state().thisAddress(), expr(address), expr(*value));
break;
}
default:
@@ -711,7 +712,7 @@ void SMTEncoder::endVisit(Identifier const& _identifier)
defineGlobalVariable(_identifier.name(), _identifier);
else if (_identifier.name() == "this")
{
defineExpr(_identifier, m_context.thisAddress());
defineExpr(_identifier, m_context.state().thisAddress());
m_uninterpretedTerms.insert(&_identifier);
}
else
@@ -858,7 +859,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
_memberAccess.expression().accept(*this);
if (_memberAccess.memberName() == "balance")
{
defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression())));
defineExpr(_memberAccess, m_context.state().balance(expr(_memberAccess.expression())));
setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context);
m_uninterpretedTerms.insert(&_memberAccess);
return false;
+2 -88
View File
@@ -17,6 +17,8 @@
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/ReadFile.h>
#include <liblangutil/Exceptions.h>
@@ -52,94 +54,6 @@ enum class CheckResult
SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR
};
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
// Forward declaration.
SortPointer smtSort(Type const& _type);
+29
View File
@@ -0,0 +1,29 @@
/*
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/>.
*/
#include <libsolidity/formal/Sorts.h>
using namespace std;
namespace solidity::frontend::smt
{
shared_ptr<Sort> const SortProvider::boolSort{make_shared<Sort>(Kind::Bool)};
shared_ptr<Sort> const SortProvider::intSort{make_shared<Sort>(Kind::Int)};
}
+125
View File
@@ -0,0 +1,125 @@
/*
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/>.
*/
#pragma once
#include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <vector>
namespace solidity::frontend::smt
{
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
/** Frequently used sorts.*/
struct SortProvider
{
static std::shared_ptr<Sort> const boolSort;
static std::shared_ptr<Sort> const intSort;
};
}
+82
View File
@@ -0,0 +1,82 @@
/*
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/>.
*/
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/EncodingContext.h>
using namespace std;
using namespace solidity::frontend::smt;
SymbolicState::SymbolicState(EncodingContext& _context):
m_context(_context)
{
}
void SymbolicState::reset()
{
m_thisAddress.resetIndex();
m_balances.resetIndex();
}
// Blockchain
Expression SymbolicState::thisAddress()
{
return m_thisAddress.currentValue();
}
Expression SymbolicState::balance()
{
return balance(m_thisAddress.currentValue());
}
Expression SymbolicState::balance(Expression _address)
{
return Expression::select(m_balances.currentValue(), move(_address));
}
void SymbolicState::transfer(Expression _from, Expression _to, Expression _value)
{
unsigned indexBefore = m_balances.index();
addBalance(_from, 0 - _value);
addBalance(_to, move(_value));
unsigned indexAfter = m_balances.index();
solAssert(indexAfter > indexBefore, "");
m_balances.increaseIndex();
/// Do not apply the transfer operation if _from == _to.
auto newBalances = Expression::ite(
move(_from) == move(_to),
m_balances.valueAtIndex(indexBefore),
m_balances.valueAtIndex(indexAfter)
);
m_context.addAssertion(m_balances.currentValue() == newBalances);
}
/// Private helpers.
void SymbolicState::addBalance(Expression _address, Expression _value)
{
auto newBalances = Expression::store(
m_balances.currentValue(),
_address,
balance(_address) + move(_value)
);
m_balances.increaseIndex();
m_context.addAssertion(newBalances == m_balances.currentValue());
}
+71
View File
@@ -0,0 +1,71 @@
/*
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/>.
*/
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicVariables.h>
namespace solidity::frontend::smt
{
class EncodingContext;
/**
* Symbolic representation of the blockchain state.
*/
class SymbolicState
{
public:
SymbolicState(EncodingContext& _context);
void reset();
/// Blockchain.
//@{
/// Value of `this` address.
Expression thisAddress();
/// @returns the symbolic balance of address `this`.
Expression balance();
/// @returns the symbolic balance of an address.
Expression balance(Expression _address);
/// Transfer _value from _from to _to.
void transfer(Expression _from, Expression _to, Expression _value);
//@}
private:
/// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value);
EncodingContext& m_context;
/// Symbolic `this` address.
SymbolicAddressVariable m_thisAddress{
"this",
m_context
};
/// Symbolic balances.
SymbolicArrayVariable m_balances{
std::make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort),
"balances",
m_context
};
};
}
+8 -9
View File
@@ -32,9 +32,9 @@ SortPointer smtSort(frontend::Type const& _type)
switch (smtKind(_type.category()))
{
case Kind::Int:
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
case Kind::Bool:
return make_shared<Sort>(Kind::Bool);
return SortProvider::boolSort;
case Kind::Function:
{
auto fType = dynamic_cast<frontend::FunctionType const*>(&_type);
@@ -45,10 +45,10 @@ SortPointer smtSort(frontend::Type const& _type)
// TODO change this when we support tuples.
if (returnTypes.size() == 0)
// We cannot declare functions without a return sort, so we use the smallest.
returnSort = make_shared<Sort>(Kind::Bool);
returnSort = SortProvider::boolSort;
else if (returnTypes.size() > 1)
// Abstract sort.
returnSort = make_shared<Sort>(Kind::Int);
returnSort = SortProvider::intSort;
else
returnSort = smtSort(*returnTypes.front());
return make_shared<FunctionSort>(parameterSorts, returnSort);
@@ -65,20 +65,19 @@ SortPointer smtSort(frontend::Type const& _type)
{
auto stringLitType = dynamic_cast<frontend::StringLiteralType const*>(&_type);
solAssert(stringLitType, "");
auto intSort = make_shared<Sort>(Kind::Int);
return make_shared<ArraySort>(intSort, intSort);
return make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort);
}
else
{
solAssert(isArray(_type.category()), "");
auto arrayType = dynamic_cast<frontend::ArrayType const*>(&_type);
solAssert(arrayType, "");
return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSortAbstractFunction(*arrayType->baseType()));
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
}
}
default:
// Abstract case.
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
}
}
@@ -93,7 +92,7 @@ vector<SortPointer> smtSort(vector<frontend::TypePointer> const& _types)
SortPointer smtSortAbstractFunction(frontend::Type const& _type)
{
if (isFunction(_type.category()))
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
return smtSort(_type);
}
+13
View File
@@ -218,15 +218,28 @@ SymbolicArrayVariable::SymbolicArrayVariable(
solAssert(isArray(m_type->category()), "");
}
SymbolicArrayVariable::SymbolicArrayVariable(
SortPointer _sort,
string _uniqueName,
EncodingContext& _context
):
SymbolicVariable(move(_sort), move(_uniqueName), _context)
{
solAssert(m_sort->kind == Kind::Array, "");
}
smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const
{
if (_targetType)
{
solAssert(m_originalType, "");
// StringLiterals are encoded as SMT arrays in the generic case,
// but they can also be compared/assigned to fixed bytes, in which
// case they'd need to be encoded as numbers.
if (auto strType = dynamic_cast<StringLiteralType const*>(m_originalType))
if (_targetType->category() == frontend::Type::Category::FixedBytes)
return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
}
return SymbolicVariable::currentValue(_targetType);
}
+9
View File
@@ -47,6 +47,8 @@ public:
EncodingContext& _context
);
SymbolicVariable(SymbolicVariable&&) = default;
virtual ~SymbolicVariable() = default;
virtual Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const;
@@ -212,6 +214,13 @@ public:
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(
SortPointer _sort,
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(SymbolicArrayVariable&&) = default;
Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const override;
};
+10
View File
@@ -35,6 +35,7 @@
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/analysis/ViewPureChecker.h>
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@@ -383,6 +384,15 @@ bool CompilerStack::analyze()
noErrors = false;
}
// Check that immutable variables are never read in c'tors and assigned
// exactly once
if (noErrors)
for (Source const* source: m_sourceOrder)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
ImmutableValidator(m_errorReporter, *contract).analyze();
if (noErrors)
{
// Control flow graph generator and analyzer. It can check for issues such as
+31 -5
View File
@@ -234,6 +234,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
"evm.deployedBytecode.immutableReferences",
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
"evm.bytecode.linkReferences",
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
@@ -309,13 +310,36 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap)
Json::Value formatImmutableReferences(map<u256, pair<string, vector<size_t>>> const& _immutableReferences)
{
Json::Value ret(Json::objectValue);
for (auto const& immutableReference: _immutableReferences)
{
auto const& [identifier, byteOffsets] = immutableReference.second;
Json::Value array(Json::arrayValue);
for (size_t byteOffset: byteOffsets)
{
Json::Value byteRange(Json::objectValue);
byteRange["start"] = Json::UInt(byteOffset);
byteRange["length"] = Json::UInt(32); // immutable references are currently always 32 bytes wide
array.append(byteRange);
}
ret[identifier] = array;
}
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap, bool _runtimeObject)
{
Json::Value output = Json::objectValue;
output["object"] = _object.toHex();
output["opcodes"] = evmasm::disassemble(_object.bytecode);
output["sourceMap"] = _sourceMap ? *_sourceMap : "";
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
if (_runtimeObject)
output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences);
return output;
}
@@ -982,19 +1006,21 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
))
evmData["bytecode"] = collectEVMObject(
compilerStack.object(contractName),
compilerStack.sourceMapping(contractName)
compilerStack.sourceMapping(contractName),
false
);
if (compilationSuccess && isArtifactRequested(
_inputsAndSettings.outputSelection,
file,
name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" },
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.deployedBytecode.immutableReferences" },
wildcardMatchesExperimental
))
evmData["deployedBytecode"] = collectEVMObject(
compilerStack.runtimeObject(contractName),
compilerStack.runtimeSourceMapping(contractName)
compilerStack.runtimeSourceMapping(contractName),
true
);
if (!evmData.empty())
@@ -1081,7 +1107,7 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get());
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false);
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();