mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user