Merge remote-tracking branch 'ethereum/develop' into sol_overloadingFunctions

This commit is contained in:
chriseth 2015-04-07 17:08:49 +02:00
commit 158795e48f
23 changed files with 1313 additions and 655 deletions

81
AST.cpp
View File

@ -21,6 +21,7 @@
*/ */
#include <algorithm> #include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/Utils.h> #include <libsolidity/Utils.h>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <libsolidity/ASTVisitor.h> #include <libsolidity/ASTVisitor.h>
@ -52,6 +53,7 @@ void ContractDefinition::checkTypeRequirements()
baseSpecifier->checkTypeRequirements(); baseSpecifier->checkTypeRequirements();
checkIllegalOverrides(); checkIllegalOverrides();
checkAbstractFunctions();
FunctionDefinition const* constructor = getConstructor(); FunctionDefinition const* constructor = getConstructor();
if (constructor && !constructor->getReturnParameters().empty()) if (constructor && !constructor->getReturnParameters().empty())
@ -60,6 +62,7 @@ void ContractDefinition::checkTypeRequirements()
FunctionDefinition const* fallbackFunction = nullptr; FunctionDefinition const* fallbackFunction = nullptr;
for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
{
if (function->getName().empty()) if (function->getName().empty())
{ {
if (fallbackFunction) if (fallbackFunction)
@ -71,6 +74,9 @@ void ContractDefinition::checkTypeRequirements()
BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters.")); BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters."));
} }
} }
if (!function->isFullyImplemented())
setFullyImplemented(false);
}
for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers()) for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers())
modifier->checkTypeRequirements(); modifier->checkTypeRequirements();
@ -98,7 +104,7 @@ void ContractDefinition::checkTypeRequirements()
if (hashes.count(hash)) if (hashes.count(hash))
BOOST_THROW_EXCEPTION(createTypeError( BOOST_THROW_EXCEPTION(createTypeError(
std::string("Function signature hash collision for ") + std::string("Function signature hash collision for ") +
it.second->getCanonicalSignature())); it.second->externalSignature()));
hashes.insert(hash); hashes.insert(hash);
} }
} }
@ -134,6 +140,28 @@ FunctionDefinition const* ContractDefinition::getFallbackFunction() const
return nullptr; return nullptr;
} }
void ContractDefinition::checkAbstractFunctions()
{
map<string, bool> functions;
// Search from base to derived
for (ContractDefinition const* contract: boost::adaptors::reverse(getLinearizedBaseContracts()))
for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions())
{
string const& name = function->getName();
if (!function->isFullyImplemented() && functions.count(name) && functions[name])
BOOST_THROW_EXCEPTION(function->createTypeError("Redeclaring an already implemented function as abstract"));
functions[name] = function->isFullyImplemented();
}
for (auto const& it: functions)
if (!it.second)
{
setFullyImplemented(false);
break;
}
}
void ContractDefinition::checkIllegalOverrides() const void ContractDefinition::checkIllegalOverrides() const
{ {
// TODO unify this at a later point. for this we need to put the constness and the access specifier // TODO unify this at a later point. for this we need to put the constness and the access specifier
@ -203,8 +231,8 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
{ {
for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
{ {
string functionSignature = f->getCanonicalSignature(); string functionSignature = f->externalSignature();
if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && signaturesSeen.count(functionSignature) == 0) if (f->isPartOfExternalInterface() && signaturesSeen.count(functionSignature) == 0)
{ {
functionsSeen.insert(f->getName()); functionsSeen.insert(f->getName());
signaturesSeen.insert(functionSignature); signaturesSeen.insert(functionSignature);
@ -214,11 +242,12 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
} }
for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables()) for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
if (v->isPublic() && functionsSeen.count(v->getName()) == 0) if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
{ {
FunctionType ftype(*v); FunctionType ftype(*v);
solAssert(v->getType().get(), "");
functionsSeen.insert(v->getName()); functionsSeen.insert(v->getName());
FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName()))); FixedHash<4> hash(dev::sha3(ftype.externalSignature(v->getName())));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v))); m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v)));
} }
} }
@ -322,25 +351,35 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const
void FunctionDefinition::checkTypeRequirements() void FunctionDefinition::checkTypeRequirements()
{ {
for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters()) for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
{
if (!var->getType()->canLiveOutsideStorage()) if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
if (getVisibility() >= Visibility::Public && !(var->getType()->externalType()))
{
// todo delete when will be implemented arrays as parameter type in internal functions
if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array)
BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions."));
else
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions."));
}
}
for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers) for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
modifier->checkTypeRequirements(isConstructor() ? modifier->checkTypeRequirements(isConstructor() ?
dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() : dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() :
vector<ASTPointer<InheritanceSpecifier>>()); vector<ASTPointer<InheritanceSpecifier>>());
if (m_body)
m_body->checkTypeRequirements(); m_body->checkTypeRequirements();
} }
string FunctionDefinition::getCanonicalSignature() const string FunctionDefinition::externalSignature() const
{ {
return FunctionType(*this).getCanonicalSignature(getName()); return FunctionType(*this).externalSignature(getName());
} }
bool VariableDeclaration::isLValue() const bool VariableDeclaration::isLValue() const
{ {
// External function parameters are Read-Only // External function parameters and constant declared variables are Read-Only
return !isExternalFunctionParameter(); return !isExternalFunctionParameter() && !m_isConstant;
} }
void VariableDeclaration::checkTypeRequirements() void VariableDeclaration::checkTypeRequirements()
@ -349,10 +388,24 @@ void VariableDeclaration::checkTypeRequirements()
// sets the type. // sets the type.
// Note that assignments before the first declaration are legal because of the special scoping // Note that assignments before the first declaration are legal because of the special scoping
// rules inherited from JavaScript. // rules inherited from JavaScript.
if (m_isConstant)
{
if (!dynamic_cast<ContractDefinition const*>(getScope()))
BOOST_THROW_EXCEPTION(createTypeError("Illegal use of \"constant\" specifier."));
if ((m_type && !m_type->isValueType()) || !m_value)
BOOST_THROW_EXCEPTION(createTypeError("Unitialized \"constant\" variable."));
}
if (!m_value) if (!m_value)
return; return;
if (m_type) if (m_type)
{
m_value->expectType(*m_type); m_value->expectType(*m_type);
if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public)
BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables."));
if (!FunctionType(*this).externalType())
BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables."));
}
else else
{ {
// no type declared and no previous assignment, infer the type // no type declared and no previous assignment, infer the type
@ -437,6 +490,8 @@ void EventDefinition::checkTypeRequirements()
numIndexed++; numIndexed++;
if (!var->getType()->canLiveOutsideStorage()) if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
if (!var->getType()->externalType())
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed as event parameter type."));
} }
if (numIndexed > 3) if (numIndexed > 3)
BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event.")); BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event."));
@ -681,6 +736,8 @@ void NewExpression::checkTypeRequirements()
m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration()); m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration());
if (!m_contract) if (!m_contract)
BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
if (!m_contract->isFullyImplemented())
BOOST_THROW_EXCEPTION(m_contract->createTypeError("Trying to create an instance of an abstract contract."));
shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract); shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract);
TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes(); TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes();
m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType}, m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType},
@ -720,7 +777,7 @@ void IndexAccess::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted."));
m_index->expectType(IntegerType(256)); m_index->expectType(IntegerType(256));
if (type.isByteArray()) if (type.isByteArray())
m_type = make_shared<IntegerType>(8, IntegerType::Modifier::Hash); m_type = make_shared<FixedBytesType>(1);
else else
m_type = type.getBaseType(); m_type = type.getBaseType();
m_isLValue = type.getLocation() != ArrayType::Location::CallData; m_isLValue = type.getLocation() != ArrayType::Location::CallData;

135
AST.h
View File

@ -156,6 +156,7 @@ public:
/// contract types. /// contract types.
virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0; virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0;
virtual bool isLValue() const { return false; } virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; };
protected: protected:
virtual Visibility getDefaultVisibility() const { return Visibility::Public; } virtual Visibility getDefaultVisibility() const { return Visibility::Public; }
@ -195,6 +196,22 @@ protected:
ASTPointer<ASTString> m_documentation; ASTPointer<ASTString> m_documentation;
}; };
/**
* Abstract class that is added to AST nodes that can be marked as not being fully implemented
*/
class ImplementationOptional
{
public:
explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {}
/// @return whether this node is fully implemented or not
bool isFullyImplemented() const { return m_implemented; }
void setFullyImplemented(bool _implemented) { m_implemented = _implemented; }
protected:
bool m_implemented;
};
/// @} /// @}
/** /**
@ -202,20 +219,24 @@ protected:
* document order. It first visits all struct declarations, then all variable declarations and * document order. It first visits all struct declarations, then all variable declarations and
* finally all function declarations. * finally all function declarations.
*/ */
class ContractDefinition: public Declaration, public Documented class ContractDefinition: public Declaration, public Documented, public ImplementationOptional
{ {
public: public:
ContractDefinition(SourceLocation const& _location, ContractDefinition(
ASTPointer<ASTString> const& _name, SourceLocation const& _location,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _name,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<StructDefinition>> const& _definedStructs, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<EnumDefinition>> const& _definedEnums, std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables, std::vector<ASTPointer<EnumDefinition>> const& _definedEnums,
std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions, std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers, std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions,
std::vector<ASTPointer<EventDefinition>> const& _events): std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers,
Declaration(_location, _name), Documented(_documentation), std::vector<ASTPointer<EventDefinition>> const& _events
):
Declaration(_location, _name),
Documented(_documentation),
ImplementationOptional(true),
m_baseContracts(_baseContracts), m_baseContracts(_baseContracts),
m_definedStructs(_definedStructs), m_definedStructs(_definedStructs),
m_definedEnums(_definedEnums), m_definedEnums(_definedEnums),
@ -262,6 +283,7 @@ public:
private: private:
void checkIllegalOverrides() const; void checkIllegalOverrides() const;
void checkAbstractFunctions();
std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const; std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const;
@ -377,24 +399,29 @@ private:
std::vector<ASTPointer<VariableDeclaration>> m_parameters; std::vector<ASTPointer<VariableDeclaration>> m_parameters;
}; };
class FunctionDefinition: public Declaration, public VariableScope, public Documented class FunctionDefinition: public Declaration, public VariableScope, public Documented, public ImplementationOptional
{ {
public: public:
FunctionDefinition(SourceLocation const& _location, ASTPointer<ASTString> const& _name, FunctionDefinition(
Declaration::Visibility _visibility, bool _isConstructor, SourceLocation const& _location,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _name,
ASTPointer<ParameterList> const& _parameters, Declaration::Visibility _visibility, bool _isConstructor,
bool _isDeclaredConst, ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, ASTPointer<ParameterList> const& _parameters,
ASTPointer<ParameterList> const& _returnParameters, bool _isDeclaredConst,
ASTPointer<Block> const& _body): std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
Declaration(_location, _name, _visibility), Documented(_documentation), ASTPointer<ParameterList> const& _returnParameters,
m_isConstructor(_isConstructor), ASTPointer<Block> const& _body
m_parameters(_parameters), ):
m_isDeclaredConst(_isDeclaredConst), Declaration(_location, _name, _visibility),
m_functionModifiers(_modifiers), Documented(_documentation),
m_returnParameters(_returnParameters), ImplementationOptional(_body != nullptr),
m_body(_body) m_isConstructor(_isConstructor),
m_parameters(_parameters),
m_isDeclaredConst(_isDeclaredConst),
m_functionModifiers(_modifiers),
m_returnParameters(_returnParameters),
m_body(_body)
{} {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
@ -415,14 +442,15 @@ public:
getVisibility() >= Visibility::Internal; getVisibility() >= Visibility::Internal;
} }
virtual TypePointer getType(ContractDefinition const*) const override; virtual TypePointer getType(ContractDefinition const*) const override;
virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstructor && !getName().empty(); }
/// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
void checkTypeRequirements(); void checkTypeRequirements();
/// @returns the canonical signature of the function /// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the /// That consists of the name of the function followed by the types of the
/// arguments separated by commas all enclosed in parentheses without any spaces. /// arguments separated by commas all enclosed in parentheses without any spaces.
std::string getCanonicalSignature() const; std::string externalSignature() const;
private: private:
bool m_isConstructor; bool m_isConstructor;
@ -440,13 +468,23 @@ private:
class VariableDeclaration: public Declaration class VariableDeclaration: public Declaration
{ {
public: public:
VariableDeclaration(SourceLocation const& _location, ASTPointer<TypeName> const& _type, VariableDeclaration(
ASTPointer<ASTString> const& _name, ASTPointer<Expression> _value, SourceLocation const& _location,
Visibility _visibility, ASTPointer<TypeName> const& _type,
bool _isStateVar = false, bool _isIndexed = false): ASTPointer<ASTString> const& _name,
Declaration(_location, _name, _visibility), ASTPointer<Expression> _value,
m_typeName(_type), m_value(_value), Visibility _visibility,
m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {} bool _isStateVar = false,
bool _isIndexed = false,
bool _isConstant = false
):
Declaration(_location, _name, _visibility),
m_typeName(_type),
m_value(_value),
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_isConstant(_isConstant){}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
@ -459,21 +497,24 @@ public:
void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; } void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; }
virtual bool isLValue() const override; virtual bool isLValue() const override;
virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstant; }
void checkTypeRequirements(); void checkTypeRequirements();
bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); } bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); }
bool isExternalFunctionParameter() const; bool isExternalFunctionParameter() const;
bool isStateVariable() const { return m_isStateVariable; } bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; } bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
protected: protected:
Visibility getDefaultVisibility() const override { return Visibility::Internal; } Visibility getDefaultVisibility() const override { return Visibility::Internal; }
private: private:
ASTPointer<TypeName> m_typeName; ///< can be empty ("var") ASTPointer<TypeName> m_typeName; ///< can be empty ("var")
ASTPointer<Expression> m_value; ///< the assigned value, can be missing ASTPointer<Expression> m_value; ///< the assigned value, can be missing
bool m_isStateVariable; ///< Whether or not this is a contract state variable bool m_isStateVariable; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events). bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
bool m_isConstant; ///< Whether the variable is a compile-time constant.
std::shared_ptr<Type const> m_type; ///< derived type, initially empty std::shared_ptr<Type const> m_type; ///< derived type, initially empty
}; };
@ -538,17 +579,24 @@ private:
class EventDefinition: public Declaration, public VariableScope, public Documented class EventDefinition: public Declaration, public VariableScope, public Documented
{ {
public: public:
EventDefinition(SourceLocation const& _location, EventDefinition(
ASTPointer<ASTString> const& _name, SourceLocation const& _location,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _name,
ASTPointer<ParameterList> const& _parameters): ASTPointer<ASTString> const& _documentation,
Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {} ASTPointer<ParameterList> const& _parameters,
bool _anonymous = false
):
Declaration(_location, _name),
Documented(_documentation),
m_parameters(_parameters),
m_anonymous(_anonymous){}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
ParameterList const& getParameterList() const { return *m_parameters; } ParameterList const& getParameterList() const { return *m_parameters; }
bool isAnonymous() const { return m_anonymous; }
virtual TypePointer getType(ContractDefinition const* = nullptr) const override virtual TypePointer getType(ContractDefinition const* = nullptr) const override
{ {
@ -559,6 +607,7 @@ public:
private: private:
ASTPointer<ParameterList> m_parameters; ASTPointer<ParameterList> m_parameters;
bool m_anonymous = false;
}; };
/** /**

View File

@ -175,7 +175,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor)
if (m_returnParameters) if (m_returnParameters)
m_returnParameters->accept(_visitor); m_returnParameters->accept(_visitor);
listAccept(m_functionModifiers, _visitor); listAccept(m_functionModifiers, _visitor);
m_body->accept(_visitor); if (m_body)
m_body->accept(_visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
@ -188,7 +189,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
if (m_returnParameters) if (m_returnParameters)
m_returnParameters->accept(_visitor); m_returnParameters->accept(_visitor);
listAccept(m_functionModifiers, _visitor); listAccept(m_functionModifiers, _visitor);
m_body->accept(_visitor); if (m_body)
m_body->accept(_visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }

View File

@ -34,8 +34,10 @@ using namespace solidity;
void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const
{ {
// stack layout: [source_ref] target_ref (top) // this copies source to target and also clears target if it was larger
// need to leave target_ref on the stack at the end // need to leave "target_ref target_byte_off" on the stack at the end
// stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top)
solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); solAssert(_targetType.getLocation() == ArrayType::Location::Storage, "");
solAssert( solAssert(
_sourceType.getLocation() == ArrayType::Location::CallData || _sourceType.getLocation() == ArrayType::Location::CallData ||
@ -47,14 +49,26 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType());
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType());
// this copies source to target and also clears target if it was larger
// TODO unroll loop for small sizes // TODO unroll loop for small sizes
// stack: source_ref [source_length] target_ref bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage;
bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType;
bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16;
bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16;
unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0);
// stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off
// store target_ref // store target_ref
// arrays always start at zero byte offset, pop offset
m_context << eth::Instruction::POP;
for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i); m_context << eth::swapInstruction(i);
// stack: target_ref source_ref [source_byte_off] [source_length]
if (sourceIsStorage)
// arrays always start at zero byte offset, pop offset
m_context << eth::Instruction::POP;
// stack: target_ref source_ref [source_length]
// retrieve source length
if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized())
retrieveLength(_sourceType); // otherwise, length is already there retrieveLength(_sourceType); // otherwise, length is already there
// stack: target_ref source_ref source_length // stack: target_ref source_ref source_length
@ -73,6 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
m_context m_context
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP
<< eth::Instruction::POP << eth::Instruction::POP; << eth::Instruction::POP << eth::Instruction::POP;
m_context << u256(0);
return; return;
} }
// compute hashes (data positions) // compute hashes (data positions)
@ -88,8 +103,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack: target_ref target_data_end source_length target_data_pos source_ref // stack: target_ref target_data_end source_length target_data_pos source_ref
// skip copying if source length is zero // skip copying if source length is zero
m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag(); eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd); m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(m_context).computeHashStatic();
@ -98,88 +113,172 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
convertLengthToSize(_sourceType); convertLengthToSize(_sourceType);
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
if (haveByteOffsetTarget)
m_context << u256(0);
if (haveByteOffsetSource)
m_context << u256(0);
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
eth::AssemblyItem copyLoopStart = m_context.newTag(); eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart; m_context << copyLoopStart;
// check for loop condition // check for loop condition
m_context m_context
<< eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize)
<< eth::Instruction::GT << eth::Instruction::ISZERO; << eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd); m_context.appendConditionalJumpTo(copyLoopEnd);
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
// copy // copy
if (sourceBaseType->getCategory() == Type::Category::Array) if (sourceBaseType->getCategory() == Type::Category::Array)
{ {
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
m_context << eth::Instruction::DUP3;
if (sourceIsStorage)
m_context << u256(0);
m_context << eth::dupInstruction(sourceIsStorage ? 4 : 3) << u256(0);
copyArrayToStorage( copyArrayToStorage(
dynamic_cast<ArrayType const&>(*targetBaseType), dynamic_cast<ArrayType const&>(*targetBaseType),
dynamic_cast<ArrayType const&>(*sourceBaseType) dynamic_cast<ArrayType const&>(*sourceBaseType)
); );
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::POP;
}
else if (directCopy)
{
solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
m_context
<< eth::Instruction::DUP3 << eth::Instruction::SLOAD
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE;
} }
else else
{ {
m_context << eth::Instruction::DUP3; // Note that we have to copy each element on its own in case conversion is involved.
// We might copy too much if there is padding at the last element, but this way end
// checking is easier.
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
m_context << eth::dupInstruction(3 + byteOffsetSize);
if (_sourceType.getLocation() == ArrayType::Location::Storage) if (_sourceType.getLocation() == ArrayType::Location::Storage)
{
if (haveByteOffsetSource)
m_context << eth::Instruction::DUP2;
else
m_context << u256(0);
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
}
else if (sourceBaseType->isValueType()) else if (sourceBaseType->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false);
else else
solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString());
m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()); // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
solAssert(2 + byteOffsetSize + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep.");
// fetch target storage reference
m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->getSizeOnStack());
if (haveByteOffsetTarget)
m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->getSizeOnStack());
else
m_context << u256(0);
StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
} }
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
// increment source // increment source
m_context if (haveByteOffsetSource)
<< eth::Instruction::SWAP2 incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
<< (_sourceType.getLocation() == ArrayType::Location::Storage ? else
sourceBaseType->getStorageSize() : m_context
sourceBaseType->getCalldataEncodedSize()) << eth::swapInstruction(2 + byteOffsetSize)
<< eth::Instruction::ADD << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize())
<< eth::Instruction::SWAP2; << eth::Instruction::ADD
<< eth::swapInstruction(2 + byteOffsetSize);
// increment target // increment target
m_context if (haveByteOffsetTarget)
<< eth::Instruction::SWAP1 incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2);
<< targetBaseType->getStorageSize() else
<< eth::Instruction::ADD m_context
<< eth::Instruction::SWAP1; << eth::swapInstruction(1 + byteOffsetSize)
<< targetBaseType->getStorageSize()
<< eth::Instruction::ADD
<< eth::swapInstruction(1 + byteOffsetSize);
m_context.appendJumpTo(copyLoopStart); m_context.appendJumpTo(copyLoopStart);
m_context << copyLoopEnd; m_context << copyLoopEnd;
if (haveByteOffsetTarget)
{
// clear elements that might be left over in the current slot in target
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
m_context << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO;
eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize);
StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2);
m_context.appendJumpTo(copyLoopEnd);
m_context << copyCleanupLoopEnd;
m_context << eth::Instruction::POP; // might pop the source, but then target is popped next
}
if (haveByteOffsetSource)
m_context << eth::Instruction::POP;
m_context << copyLoopEndWithoutByteOffset;
// zero-out leftovers in target // zero-out leftovers in target
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP;
// stack: target_ref target_data_end target_data_pos_updated // stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop(*targetBaseType); clearStorageLoop(*targetBaseType);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
m_context << u256(0);
} }
void ArrayUtils::clearArray(ArrayType const& _type) const void ArrayUtils::clearArray(ArrayType const& _type) const
{ {
unsigned stackHeightStart = m_context.getStackHeight();
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
if (_type.getBaseType()->getStorageBytes() < 32)
{
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid storage size for type.");
}
if (_type.getBaseType()->isValueType())
solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid size for value type.");
m_context << eth::Instruction::POP; // remove byte offset
if (_type.isDynamicallySized()) if (_type.isDynamicallySized())
clearDynamicArray(_type); clearDynamicArray(_type);
else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value else if (_type.getBaseType()->isValueType() && _type.getStorageSize() <= 5)
{ {
solAssert(!_type.isByteArray(), ""); // unroll loop for small arrays @todo choose a good value
// Note that we loop over storage slots here, not elements.
for (unsigned i = 1; i < _type.getStorageSize(); ++i)
m_context
<< u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::ADD;
m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
}
else if (!_type.getBaseType()->isValueType() && _type.getLength() <= 4)
{
// unroll loop for small arrays @todo choose a good value
solAssert(_type.getBaseType()->getStorageBytes() >= 32, "Invalid storage size.");
for (unsigned i = 1; i < _type.getLength(); ++i) for (unsigned i = 1; i < _type.getLength(); ++i)
{ {
m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false);
m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; m_context
<< eth::Instruction::POP
<< u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD;
} }
m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true);
} }
else else
{ {
solAssert(!_type.isByteArray(), ""); m_context << eth::Instruction::DUP1 << _type.getLength();
m_context convertLengthToSize(_type);
<< eth::Instruction::DUP1 << u256(_type.getLength()) m_context << eth::Instruction::ADD << eth::Instruction::SWAP1;
<< u256(_type.getBaseType()->getStorageSize()) if (_type.getBaseType()->getStorageBytes() < 32)
<< eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; clearStorageLoop(IntegerType(256));
clearStorageLoop(*_type.getBaseType()); else
clearStorageLoop(*_type.getBaseType());
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
} }
solAssert(m_context.getStackHeight() == stackHeightStart - 2, "");
} }
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@ -187,6 +286,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), ""); solAssert(_type.isDynamicallySized(), "");
unsigned stackHeightStart = m_context.getStackHeight();
// fetch length // fetch length
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// set length to zero // set length to zero
@ -196,23 +296,27 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
// compute data positions // compute data positions
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(m_context).computeHashStatic();
// stack: len data_pos (len is in slots for byte array and in items for other arrays) // stack: len data_pos
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1; << eth::Instruction::SWAP1;
// stack: data_pos_end data_pos // stack: data_pos_end data_pos
if (_type.isByteArray()) if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256)); clearStorageLoop(IntegerType(256));
else else
clearStorageLoop(*_type.getBaseType()); clearStorageLoop(*_type.getBaseType());
// cleanup // cleanup
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
solAssert(m_context.getStackHeight() == stackHeightStart - 1, "");
} }
void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
{ {
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), ""); solAssert(_type.isDynamicallySized(), "");
if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32)
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
unsigned stackHeightStart = m_context.getStackHeight();
eth::AssemblyItem resizeEnd = m_context.newTag(); eth::AssemblyItem resizeEnd = m_context.newTag();
// stack: ref new_length // stack: ref new_length
@ -240,7 +344,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
// stack: ref new_length data_pos new_size delete_end // stack: ref new_length data_pos new_size delete_end
m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD;
// stack: ref new_length delete_end delete_start // stack: ref new_length delete_end delete_start
if (_type.isByteArray()) if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256)); clearStorageLoop(IntegerType(256));
else else
clearStorageLoop(*_type.getBaseType()); clearStorageLoop(*_type.getBaseType());
@ -248,10 +352,12 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
m_context << resizeEnd; m_context << resizeEnd;
// cleanup // cleanup
m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
solAssert(m_context.getStackHeight() == stackHeightStart - 2, "");
} }
void ArrayUtils::clearStorageLoop(Type const& _type) const void ArrayUtils::clearStorageLoop(Type const& _type) const
{ {
unsigned stackHeightStart = m_context.getStackHeight();
if (_type.getCategory() == Type::Category::Mapping) if (_type.getCategory() == Type::Category::Mapping)
{ {
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
@ -266,13 +372,16 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const
eth::AssemblyItem zeroLoopEnd = m_context.newTag(); eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd); m_context.appendConditionalJumpTo(zeroLoopEnd);
// delete // delete
m_context << u256(0);
StorageItem(m_context, _type).setToZero(SourceLocation(), false); StorageItem(m_context, _type).setToZero(SourceLocation(), false);
m_context << eth::Instruction::POP;
// increment // increment
m_context << u256(1) << eth::Instruction::ADD; m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(loopStart); m_context.appendJumpTo(loopStart);
// cleanup // cleanup
m_context << zeroLoopEnd; m_context << zeroLoopEnd;
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
solAssert(m_context.getStackHeight() == stackHeightStart - 1, "");
} }
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
@ -282,7 +391,20 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con
if (_arrayType.isByteArray()) if (_arrayType.isByteArray())
m_context << u256(31) << eth::Instruction::ADD m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
else if (_arrayType.getBaseType()->getStorageSize() > 1) else if (_arrayType.getBaseType()->getStorageSize() <= 1)
{
unsigned baseBytes = _arrayType.getBaseType()->getStorageBytes();
if (baseBytes == 0)
m_context << eth::Instruction::POP << u256(1);
else if (baseBytes <= 16)
{
unsigned itemsPerSlot = 32 / baseBytes;
m_context
<< u256(itemsPerSlot - 1) << eth::Instruction::ADD
<< u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
}
}
else
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
} }
else else
@ -318,3 +440,143 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
} }
} }
void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
{
ArrayType::Location location = _arrayType.getLocation();
eth::Instruction load =
location == ArrayType::Location::Storage ? eth::Instruction::SLOAD :
location == ArrayType::Location::Memory ? eth::Instruction::MLOAD :
eth::Instruction::CALLDATALOAD;
// retrieve length
if (!_arrayType.isDynamicallySized())
m_context << _arrayType.getLength();
else if (location == ArrayType::Location::CallData)
// length is stored on the stack
m_context << eth::Instruction::SWAP1;
else
m_context << eth::Instruction::DUP2 << load;
// stack: <base_ref> <index> <length>
// check out-of-bounds access
m_context << eth::Instruction::DUP2 << eth::Instruction::LT;
eth::AssemblyItem legalAccess = m_context.appendConditionalJump();
// out-of-bounds access throws exception (just STOP for now)
m_context << eth::Instruction::STOP;
m_context << legalAccess;
// stack: <base_ref> <index>
if (_arrayType.isByteArray())
switch (location)
{
case ArrayType::Location::Storage:
// byte array index storage lvalue on stack (goal):
// <ref> <byte_number> = <base_ref + index / 32> <index % 32>
m_context << u256(32) << eth::Instruction::SWAP2;
CompilerUtils(m_context).computeHashStatic();
// stack: 32 index data_ref
m_context
<< eth::Instruction::DUP3 << eth::Instruction::DUP3
<< eth::Instruction::DIV << eth::Instruction::ADD
// stack: 32 index (data_ref + index / 32)
<< eth::Instruction::SWAP2 << eth::Instruction::SWAP1
<< eth::Instruction::MOD;
break;
case ArrayType::Location::CallData:
// no lvalue, just retrieve the value
m_context
<< eth::Instruction::ADD << eth::Instruction::CALLDATALOAD
<< ((u256(0xff) << (256 - 8))) << eth::Instruction::AND;
break;
case ArrayType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented.");
}
else
{
// stack: <base_ref> <index>
m_context << eth::Instruction::SWAP1;
if (_arrayType.isDynamicallySized())
{
if (location == ArrayType::Location::Storage)
CompilerUtils(m_context).computeHashStatic();
else if (location == ArrayType::Location::Memory)
m_context << u256(32) << eth::Instruction::ADD;
}
// stack: <index> <data_ref>
switch (location)
{
case ArrayType::Location::CallData:
m_context
<< eth::Instruction::SWAP1 << _arrayType.getBaseType()->getCalldataEncodedSize()
<< eth::Instruction::MUL << eth::Instruction::ADD;
if (_arrayType.getBaseType()->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic(*_arrayType.getBaseType(), true, true, false);
break;
case ArrayType::Location::Storage:
m_context << eth::Instruction::SWAP1;
if (_arrayType.getBaseType()->getStorageBytes() <= 16)
{
// stack: <data_ref> <index>
// goal:
// <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize>
unsigned byteSize = _arrayType.getBaseType()->getStorageBytes();
solAssert(byteSize != 0, "");
unsigned itemsPerSlot = 32 / byteSize;
m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2;
// stack: itemsPerSlot index data_ref
m_context
<< eth::Instruction::DUP3 << eth::Instruction::DUP3
<< eth::Instruction::DIV << eth::Instruction::ADD
// stack: itemsPerSlot index (data_ref + index / itemsPerSlot)
<< eth::Instruction::SWAP2 << eth::Instruction::SWAP1
<< eth::Instruction::MOD
<< u256(byteSize) << eth::Instruction::MUL;
}
else
{
if (_arrayType.getBaseType()->getStorageSize() != 1)
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
m_context << eth::Instruction::ADD << u256(0);
}
break;
case ArrayType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented.");
}
}
}
void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const
{
solAssert(_byteSize < 32, "");
solAssert(_byteSize != 0, "");
// We do the following, but avoiding jumps:
// byteOffset += byteSize
// if (byteOffset + byteSize > 32)
// {
// storageOffset++;
// byteOffset = 0;
// }
if (_byteOffsetPosition > 1)
m_context << eth::swapInstruction(_byteOffsetPosition - 1);
m_context << u256(_byteSize) << eth::Instruction::ADD;
if (_byteOffsetPosition > 1)
m_context << eth::swapInstruction(_byteOffsetPosition - 1);
// compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32
m_context
<< u256(32) << eth::dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1)
<< eth::Instruction::ADD << eth::Instruction::DIV;
// increment storage offset if X == 1 (just add X to it)
// stack: X
m_context
<< eth::swapInstruction(_storageOffsetPosition) << eth::dupInstruction(_storageOffsetPosition + 1)
<< eth::Instruction::ADD << eth::swapInstruction(_storageOffsetPosition);
// stack: X
// set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X)
m_context << u256(1) << eth::Instruction::SUB;
// stack: 1 - X
if (_byteOffsetPosition == 1)
m_context << eth::Instruction::MUL;
else
m_context
<< eth::dupInstruction(_byteOffsetPosition + 1) << eth::Instruction::MUL
<< eth::swapInstruction(_byteOffsetPosition) << eth::Instruction::POP;
}

View File

@ -41,19 +41,19 @@ public:
/// Copies an array to an array in storage. The arrays can be of different types only if /// Copies an array to an array in storage. The arrays can be of different types only if
/// their storage representation is the same. /// their storage representation is the same.
/// Stack pre: [source_reference] target_reference /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset
/// Stack post: target_reference /// Stack post: target_reference target_byte_offset
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
/// Clears the given dynamic or static array. /// Clears the given dynamic or static array.
/// Stack pre: reference /// Stack pre: storage_ref storage_byte_offset
/// Stack post: /// Stack post:
void clearArray(ArrayType const& _type) const; void clearArray(ArrayType const& _type) const;
/// Clears the length and data elements of the array referenced on the stack. /// Clears the length and data elements of the array referenced on the stack.
/// Stack pre: reference /// Stack pre: reference (excludes byte offset)
/// Stack post: /// Stack post:
void clearDynamicArray(ArrayType const& _type) const; void clearDynamicArray(ArrayType const& _type) const;
/// Changes the size of a dynamic array and clears the tail if it is shortened. /// Changes the size of a dynamic array and clears the tail if it is shortened.
/// Stack pre: reference new_length /// Stack pre: reference (excludes byte offset) new_length
/// Stack post: /// Stack post:
void resizeDynamicArray(ArrayType const& _type) const; void resizeDynamicArray(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
@ -67,11 +67,23 @@ public:
void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const;
/// Retrieves the length (number of elements) of the array ref on the stack. This also /// Retrieves the length (number of elements) of the array ref on the stack. This also
/// works for statically-sized arrays. /// works for statically-sized arrays.
/// Stack pre: reference /// Stack pre: reference (excludes byte offset for dynamic storage arrays)
/// Stack post: reference length /// Stack post: reference length
void retrieveLength(ArrayType const& _arrayType) const; void retrieveLength(ArrayType const& _arrayType) const;
/// Retrieves the value at a specific index. If the location is storage, only retrieves the
/// position.
/// Stack pre: reference [length] index
/// Stack post for storage: slot byte_offset
/// Stack post for calldata: value
void accessIndex(ArrayType const& _arrayType) const;
private: private:
/// Adds the given number of bytes to a storage byte offset counter and also increments
/// the storage offset if adding this number again would increase the counter over 32.
/// @param byteOffsetPosition the stack offset of the storage byte offset
/// @param storageOffsetPosition the stack offset of the storage slot offset
void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
CompilerContext& m_context; CompilerContext& m_context;
}; };

View File

@ -20,12 +20,12 @@
* Solidity compiler. * Solidity compiler.
*/ */
#include <libsolidity/Compiler.h>
#include <algorithm> #include <algorithm>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <libevmcore/Instruction.h> #include <libevmcore/Instruction.h>
#include <libevmcore/Assembly.h> #include <libevmcore/Assembly.h>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h> #include <libsolidity/ExpressionCompiler.h>
#include <libsolidity/CompilerUtils.h> #include <libsolidity/CompilerUtils.h>
@ -132,7 +132,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp
void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_constructor); CompilerContext::LocationSetter locationSetter(m_context, _constructor);
FunctionType constructorType(_constructor); FunctionType constructorType(_constructor);
if (!constructorType.getParameterTypes().empty()) if (!constructorType.getParameterTypes().empty())
{ {
@ -146,7 +146,7 @@ void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor)
void Compiler::appendConstructor(FunctionDefinition const& _constructor) void Compiler::appendConstructor(FunctionDefinition const& _constructor)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_constructor); CompilerContext::LocationSetter locationSetter(m_context, _constructor);
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
unsigned argumentSize = 0; unsigned argumentSize = 0;
for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters()) for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters())
@ -192,10 +192,12 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
for (auto const& it: interfaceFunctions) for (auto const& it: interfaceFunctions)
{ {
FunctionTypePointer const& functionType = it.second; FunctionTypePointer const& functionType = it.second;
solAssert(functionType->hasDeclaration(), "");
CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration());
m_context << callDataUnpackerEntryPoints.at(it.first); m_context << callDataUnpackerEntryPoints.at(it.first);
eth::AssemblyItem returnTag = m_context.pushNewTag(); eth::AssemblyItem returnTag = m_context.pushNewTag();
appendCalldataUnpacker(functionType->getParameterTypes()); appendCalldataUnpacker(functionType->getParameterTypes());
m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration()));
m_context << returnTag; m_context << returnTag;
appendReturnValuePacker(functionType->getReturnParameterTypes()); appendReturnValuePacker(functionType->getReturnParameterTypes());
} }
@ -226,6 +228,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool
{ {
// Retrieve data start offset by adding length to start offset of previous dynamic type // Retrieve data start offset by adding length to start offset of previous dynamic type
unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument;
solAssert(stackDepth <= 16, "Stack too deep.");
m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth);
ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true); ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true);
m_context << eth::Instruction::ADD; m_context << eth::Instruction::ADD;
@ -271,22 +274,21 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
void Compiler::registerStateVariables(ContractDefinition const& _contract) void Compiler::registerStateVariables(ContractDefinition const& _contract)
{ {
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) for (auto const& var: ContractType(_contract).getStateVariables())
for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables()) m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
m_context.addStateVariable(*variable);
} }
void Compiler::initializeStateVariables(ContractDefinition const& _contract) void Compiler::initializeStateVariables(ContractDefinition const& _contract)
{ {
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
if (variable->getValue()) if (variable->getValue() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable);
} }
bool Compiler::visit(VariableDeclaration const& _variableDeclaration) bool Compiler::visit(VariableDeclaration const& _variableDeclaration)
{ {
solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration.");
CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclaration); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration);
m_context.startFunction(_variableDeclaration); m_context.startFunction(_variableDeclaration);
m_breakTags.clear(); m_breakTags.clear();
@ -300,7 +302,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration)
bool Compiler::visit(FunctionDefinition const& _function) bool Compiler::visit(FunctionDefinition const& _function)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_function); CompilerContext::LocationSetter locationSetter(m_context, _function);
//@todo to simplify this, the calling convention could by changed such that //@todo to simplify this, the calling convention could by changed such that
// caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
// although note that this reduces the size of the visible stack // although note that this reduces the size of the visible stack
@ -357,6 +359,7 @@ bool Compiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i); stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1); stackLayout += vector<int>(c_localVariablesSize, -1);
solAssert(stackLayout.size() <= 17, "Stack too deep.");
while (stackLayout.back() != int(stackLayout.size() - 1)) while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0) if (stackLayout.back() < 0)
{ {
@ -376,15 +379,16 @@ bool Compiler::visit(FunctionDefinition const& _function)
m_context.removeVariable(*localVariable); m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize); m_context.adjustStackOffset(-(int)c_returnValuesSize);
if (!_function.isConstructor()) if (!_function.isConstructor())
m_context << eth::Instruction::JUMP; m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
return false; return false;
} }
bool Compiler::visit(IfStatement const& _ifStatement) bool Compiler::visit(IfStatement const& _ifStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_ifStatement); CompilerContext::LocationSetter locationSetter(m_context, _ifStatement);
compileExpression(_ifStatement.getCondition()); compileExpression(_ifStatement.getCondition());
eth::AssemblyItem trueTag = m_context.appendConditionalJump(); eth::AssemblyItem trueTag = m_context.appendConditionalJump();
if (_ifStatement.getFalseStatement()) if (_ifStatement.getFalseStatement())
@ -401,7 +405,7 @@ bool Compiler::visit(IfStatement const& _ifStatement)
bool Compiler::visit(WhileStatement const& _whileStatement) bool Compiler::visit(WhileStatement const& _whileStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_whileStatement); CompilerContext::LocationSetter locationSetter(m_context, _whileStatement);
eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart); m_continueTags.push_back(loopStart);
@ -427,7 +431,7 @@ bool Compiler::visit(WhileStatement const& _whileStatement)
bool Compiler::visit(ForStatement const& _forStatement) bool Compiler::visit(ForStatement const& _forStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_forStatement); CompilerContext::LocationSetter locationSetter(m_context, _forStatement);
eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart); m_continueTags.push_back(loopStart);
@ -464,7 +468,7 @@ bool Compiler::visit(ForStatement const& _forStatement)
bool Compiler::visit(Continue const& _continueStatement) bool Compiler::visit(Continue const& _continueStatement)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_continueStatement); CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
if (!m_continueTags.empty()) if (!m_continueTags.empty())
m_context.appendJumpTo(m_continueTags.back()); m_context.appendJumpTo(m_continueTags.back());
return false; return false;
@ -472,7 +476,7 @@ bool Compiler::visit(Continue const& _continueStatement)
bool Compiler::visit(Break const& _breakStatement) bool Compiler::visit(Break const& _breakStatement)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_breakStatement); CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
if (!m_breakTags.empty()) if (!m_breakTags.empty())
m_context.appendJumpTo(m_breakTags.back()); m_context.appendJumpTo(m_breakTags.back());
return false; return false;
@ -480,7 +484,7 @@ bool Compiler::visit(Break const& _breakStatement)
bool Compiler::visit(Return const& _return) bool Compiler::visit(Return const& _return)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_return); CompilerContext::LocationSetter locationSetter(m_context, _return);
//@todo modifications are needed to make this work with functions returning multiple values //@todo modifications are needed to make this work with functions returning multiple values
if (Expression const* expression = _return.getExpression()) if (Expression const* expression = _return.getExpression())
{ {
@ -499,7 +503,7 @@ bool Compiler::visit(Return const& _return)
bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
if (Expression const* expression = _variableDeclarationStatement.getExpression()) if (Expression const* expression = _variableDeclarationStatement.getExpression())
{ {
compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType());
@ -512,7 +516,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta
bool Compiler::visit(ExpressionStatement const& _expressionStatement) bool Compiler::visit(ExpressionStatement const& _expressionStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_expressionStatement); CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement);
Expression const& expression = _expressionStatement.getExpression(); Expression const& expression = _expressionStatement.getExpression();
compileExpression(expression); compileExpression(expression);
CompilerUtils(m_context).popStackElement(*expression.getType()); CompilerUtils(m_context).popStackElement(*expression.getType());
@ -523,7 +527,7 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement)
bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) bool Compiler::visit(PlaceholderStatement const& _placeholderStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, &_placeholderStatement); CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
++m_modifierDepth; ++m_modifierDepth;
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
--m_modifierDepth; --m_modifierDepth;
@ -550,7 +554,7 @@ void Compiler::appendModifierOrFunctionCode()
} }
ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName());
CompilerContext::LocationSetter locationSetter(m_context, &modifier); CompilerContext::LocationSetter locationSetter(m_context, modifier);
solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), "");
for (unsigned i = 0; i < modifier.getParameters().size(); ++i) for (unsigned i = 0; i < modifier.getParameters().size(); ++i)
{ {

View File

@ -94,8 +94,8 @@ private:
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
unsigned m_modifierDepth = 0; unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction; FunctionDefinition const* m_currentFunction = nullptr;
unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order // arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments; std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments;
}; };

View File

@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio
m_magicGlobals.insert(&_declaration); m_magicGlobals.insert(&_declaration);
} }
void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) void CompilerContext::addStateVariable(
VariableDeclaration const& _declaration,
u256 const& _storageOffset,
unsigned _byteOffset
)
{ {
m_stateVariables[&_declaration] = m_stateVariablesSize; m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
bigint newSize = bigint(m_stateVariablesSize) + _declaration.getType()->getStorageSize();
if (newSize >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError()
<< errinfo_comment("State variable does not fit in storage.")
<< errinfo_sourceLocation(_declaration.getLocation()));
m_stateVariablesSize = u256(newSize);
} }
void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::startFunction(Declaration const& _function)
@ -69,7 +67,7 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration)
{ {
LocationSetter locationSetter(*this, &_declaration); LocationSetter locationSetter(*this, _declaration);
addVariable(_declaration); addVariable(_declaration);
int const size = _declaration.getType()->getSizeOnStack(); int const size = _declaration.getType()->getSizeOnStack();
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
@ -174,46 +172,26 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const
return m_asm.deposit() - _offset - 1; return m_asm.deposit() - _offset - 1;
} }
u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const pair<u256, unsigned> CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
{ {
auto it = m_stateVariables.find(&_declaration); auto it = m_stateVariables.find(&_declaration);
solAssert(it != m_stateVariables.end(), "Variable not found in storage."); solAssert(it != m_stateVariables.end(), "Variable not found in storage.");
return it->second; return it->second;
} }
CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpType)
{
eth::AssemblyItem item(eth::Instruction::JUMP);
item.setJumpType(_jumpType);
return *this << item;
}
void CompilerContext::resetVisitedNodes(ASTNode const* _node) void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{ {
stack<ASTNode const*> newStack; stack<ASTNode const*> newStack;
newStack.push(_node); newStack.push(_node);
std::swap(m_visitedNodes, newStack); std::swap(m_visitedNodes, newStack);
} updateSourceLocation();
CompilerContext& CompilerContext::operator<<(eth::AssemblyItem const& _item)
{
solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
m_asm.append(_item, m_visitedNodes.top()->getLocation());
return *this;
}
CompilerContext& CompilerContext::operator<<(eth::Instruction _instruction)
{
solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
m_asm.append(_instruction, m_visitedNodes.top()->getLocation());
return *this;
}
CompilerContext& CompilerContext::operator<<(u256 const& _value)
{
solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
m_asm.append(_value, m_visitedNodes.top()->getLocation());
return *this;
}
CompilerContext& CompilerContext::operator<<(bytes const& _data)
{
solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
m_asm.append(_data, m_visitedNodes.top()->getLocation());
return *this;
} }
vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const
@ -224,5 +202,10 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContr
return ++it; return ++it;
} }
void CompilerContext::updateSourceLocation()
{
m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation());
}
} }
} }

View File

@ -24,6 +24,7 @@
#include <ostream> #include <ostream>
#include <stack> #include <stack>
#include <utility>
#include <libevmcore/Instruction.h> #include <libevmcore/Instruction.h>
#include <libevmcore/Assembly.h> #include <libevmcore/Assembly.h>
#include <libsolidity/ASTForward.h> #include <libsolidity/ASTForward.h>
@ -42,7 +43,7 @@ class CompilerContext
{ {
public: public:
void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addMagicGlobal(MagicVariableDeclaration const& _declaration);
void addStateVariable(VariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration); void removeVariable(VariableDeclaration const& _declaration);
void addAndInitializeVariable(VariableDeclaration const& _declaration); void addAndInitializeVariable(VariableDeclaration const& _declaration);
@ -82,7 +83,7 @@ public:
/// Converts an offset relative to the current stack height to a value that can be used later /// Converts an offset relative to the current stack height to a value that can be used later
/// with baseToCurrentStackOffset to point to the same stack element. /// with baseToCurrentStackOffset to point to the same stack element.
unsigned currentToBaseStackOffset(unsigned _offset) const; unsigned currentToBaseStackOffset(unsigned _offset) const;
u256 getStorageLocationOfVariable(Declaration const& _declaration) const; std::pair<u256, unsigned> getStorageLocationOfVariable(Declaration const& _declaration) const;
/// Appends a JUMPI instruction to a new tag and @returns the tag /// Appends a JUMPI instruction to a new tag and @returns the tag
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); }
@ -91,7 +92,7 @@ public:
/// Appends a JUMP to a new tag and @returns the tag /// Appends a JUMP to a new tag and @returns the tag
eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack /// Appends a JUMP to a tag already on the stack
CompilerContext& appendJump() { return *this << eth::Instruction::JUMP; } CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
/// Appends a JUMP to a specific tag /// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag. /// Appends pushing of a new tag and @returns the new tag.
@ -108,19 +109,19 @@ public:
/// Resets the stack of visited nodes with a new stack having only @c _node /// Resets the stack of visited nodes with a new stack having only @c _node
void resetVisitedNodes(ASTNode const* _node); void resetVisitedNodes(ASTNode const* _node);
/// Pops the stack of visited nodes /// Pops the stack of visited nodes
void popVisitedNodes() { m_visitedNodes.pop(); } void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); }
/// Pushes an ASTNode to the stack of visited nodes /// Pushes an ASTNode to the stack of visited nodes
void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); } void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); }
/// Append elements to the current instruction list and adjust @a m_stackOffset. /// Append elements to the current instruction list and adjust @a m_stackOffset.
CompilerContext& operator<<(eth::AssemblyItem const& _item); CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; }
CompilerContext& operator<<(eth::Instruction _instruction); CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; }
CompilerContext& operator<<(u256 const& _value); CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; }
CompilerContext& operator<<(bytes const& _data); CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; }
eth::Assembly const& getAssembly() const { return m_asm; } eth::Assembly const& getAssembly() const { return m_asm; }
/// @arg _sourceCodes is the map of input files to source code strings /// @arg _sourceCodes is the map of input files to source code strings
void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.streamRLP(_stream, "", _sourceCodes); } void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.stream(_stream, "", _sourceCodes); }
bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); } bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); }
@ -130,22 +131,22 @@ public:
class LocationSetter: public ScopeGuard class LocationSetter: public ScopeGuard
{ {
public: public:
LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node):
ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); }
}; };
private: private:
std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const; std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const;
/// Updates source location set in the assembly.
void updateSourceLocation();
eth::Assembly m_asm; eth::Assembly m_asm;
/// Magic global variables like msg, tx or this, distinguished by type. /// Magic global variables like msg, tx or this, distinguished by type.
std::set<Declaration const*> m_magicGlobals; std::set<Declaration const*> m_magicGlobals;
/// Other already compiled contracts to be used in contract creation calls. /// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, bytes const*> m_compiledContracts; std::map<ContractDefinition const*, bytes const*> m_compiledContracts;
/// Size of the state variables, offset of next variable to be added.
u256 m_stateVariablesSize = 0;
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<Declaration const*, u256> m_stateVariables; std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base). /// Offsets of local variables on the stack (relative to stack base).
std::map<Declaration const*, unsigned> m_localVariables; std::map<Declaration const*, unsigned> m_localVariables;
/// Labels pointing to the entry points of functions. /// Labels pointing to the entry points of functions.

View File

@ -41,14 +41,14 @@ namespace solidity
{ {
const map<string, string> StandardSources = map<string, string>{ const map<string, string> StandardSources = map<string, string>{
{"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"},
{"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, {"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"},
{"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"}, {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,bytes3 name,uint256 denom){}function register(bytes3 name,uint256 denom){}function unregister(){}})"},
{"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, {"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"},
{"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, {"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"},
{"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, {"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"},
{"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(bytes32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"},
{"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"}, {"NameReg", R"(contract NameReg{function register(bytes32 name){}function addressOf(bytes32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(bytes32 name){}})"},
{"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, {"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"},
{"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, {"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"},
{"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} {"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"}
@ -138,6 +138,8 @@ void CompilerStack::compile(bool _optimize)
for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) for (ASTPointer<ASTNode> const& node: source->ast->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{ {
if (!contract->isFullyImplemented())
continue;
shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize); shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize);
compiler->compileContract(*contract, contractBytecode); compiler->compileContract(*contract, contractBytecode);
Contract& compiledContract = m_contracts[contract->getName()]; Contract& compiledContract = m_contracts[contract->getName()];

View File

@ -93,6 +93,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
else else
{ {
solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented."); solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented.");
m_context << eth::Instruction::POP; //@todo
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack here: memory_offset storage_offset length_bytes // stack here: memory_offset storage_offset length_bytes
// jump to end if length is zero // jump to end if length is zero
@ -138,6 +139,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
{ {
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable));
unsigned const size = _variable.getType()->getSizeOnStack(); unsigned const size = _variable.getType()->getSizeOnStack();
solAssert(stackPosition >= size, "Variable size and position mismatch.");
// move variable starting from its top end in the stack // move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16) if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation())
@ -148,8 +150,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{ {
if (_stackDepth > 16) solAssert(_stackDepth <= 16, "Stack too deep.");
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Stack too deep."));
for (unsigned i = 0; i < _itemSize; ++i) for (unsigned i = 0; i < _itemSize; ++i)
m_context << eth::dupInstruction(_stackDepth); m_context << eth::dupInstruction(_stackDepth);
} }
@ -178,7 +179,7 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
bool leftAligned = _type.getCategory() == Type::Category::String; bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0) if (numBytes == 0)
m_context << eth::Instruction::POP << u256(0); m_context << eth::Instruction::POP << u256(0);
else else
@ -198,11 +199,10 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
return numBytes; return numBytes;
} }
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
bool leftAligned = _type.getCategory() == Type::Category::String; bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0) if (numBytes == 0)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
else else

View File

@ -48,7 +48,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
if (!_varDecl.getValue()) if (!_varDecl.getValue())
return; return;
solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); solAssert(!!_varDecl.getValue()->getType(), "Type information not available.");
CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
_varDecl.getValue()->accept(*this); _varDecl.getValue()->accept(*this);
appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true);
@ -57,7 +57,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
FunctionType accessorType(_varDecl); FunctionType accessorType(_varDecl);
unsigned length = 0; unsigned length = 0;
@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); length += CompilerUtils(m_context).storeInMemory(length, *paramType, true);
// retrieve the position of the variable // retrieve the position of the variable
m_context << m_context.getStorageLocationOfVariable(_varDecl); auto const& location = m_context.getStorageLocationOfVariable(_varDecl);
TypePointer returnType = _varDecl.getType(); m_context << location.first;
TypePointer returnType = _varDecl.getType();
for (TypePointer const& paramType: paramTypes) for (TypePointer const& paramType: paramTypes)
{ {
// move offset to memory // move offset to memory
@ -90,9 +91,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
// struct // struct
for (size_t i = 0; i < names.size(); ++i) for (size_t i = 0; i < names.size(); ++i)
{ {
m_context << eth::Instruction::DUP1 if (types[i]->getCategory() == Type::Category::Mapping)
<< structType->getStorageOffsetOfMember(names[i]) continue;
<< eth::Instruction::ADD; pair<u256, unsigned> const& offsets = structType->getStorageOffsetsOfMember(names[i]);
m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second);
StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true);
solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented.");
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
@ -104,11 +106,13 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{ {
// simple value // simple value
solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); solAssert(accessorType.getReturnParameterTypes().size() == 1, "");
m_context << u256(location.second);
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
retSizeOnStack = returnType->getSizeOnStack(); retSizeOnStack = returnType->getSizeOnStack();
} }
solAssert(retSizeOnStack <= 15, "Stack too deep."); solAssert(retSizeOnStack <= 15, "Stack too deep.");
m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; m_context << eth::dupInstruction(retSizeOnStack + 1);
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
} }
void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded)
@ -122,23 +126,25 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
Type::Category stackTypeCategory = _typeOnStack.getCategory(); Type::Category stackTypeCategory = _typeOnStack.getCategory();
Type::Category targetTypeCategory = _targetType.getCategory(); Type::Category targetTypeCategory = _targetType.getCategory();
if (stackTypeCategory == Type::Category::String) switch (stackTypeCategory)
{ {
StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); case Type::Category::FixedBytes:
{
FixedBytesType const& typeOnStack = dynamic_cast<FixedBytesType const&>(_typeOnStack);
if (targetTypeCategory == Type::Category::Integer) if (targetTypeCategory == Type::Category::Integer)
{ {
// conversion from string to hash. no need to clean the high bit // conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment // only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed.");
solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same.");
m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8)
appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded);
} }
else else
{ {
// clear lower-order bytes for conversion to shorter strings - we always clean // clear lower-order bytes for conversion to shorter bytes - we always clean
solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
if (targetType.getNumBytes() < typeOnStack.getNumBytes()) if (targetType.getNumBytes() < typeOnStack.getNumBytes())
{ {
if (targetType.getNumBytes() == 0) if (targetType.getNumBytes() == 0)
@ -150,22 +156,24 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
} }
} }
} }
else if (stackTypeCategory == Type::Category::Enum) break;
solAssert(targetTypeCategory == Type::Category::Integer || case Type::Category::Enum:
targetTypeCategory == Type::Category::Enum, ""); solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, "");
else if (stackTypeCategory == Type::Category::Integer || break;
stackTypeCategory == Type::Category::Contract || case Type::Category::Integer:
stackTypeCategory == Type::Category::IntegerConstant) case Type::Category::Contract:
{ case Type::Category::IntegerConstant:
if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) if (targetTypeCategory == Type::Category::FixedBytes)
{ {
// conversion from hash to string. no need to clean the high bit solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant,
"Invalid conversion to FixedBytesType requested.");
// conversion from bytes to string. no need to clean the high bit
// only to shift left because of opposite alignment // only to shift left because of opposite alignment
StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits())
solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); appendHighBitsCleanup(*typeOnStack);
m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL;
} }
else if (targetTypeCategory == Type::Category::Enum) else if (targetTypeCategory == Type::Category::Enum)
// just clean // just clean
@ -175,7 +183,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
IntegerType addressType(0, IntegerType::Modifier::Address); IntegerType addressType(0, IntegerType::Modifier::Address);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_targetType) : addressType; ? dynamic_cast<IntegerType const&>(_targetType) : addressType;
if (stackTypeCategory == Type::Category::IntegerConstant) if (stackTypeCategory == Type::Category::IntegerConstant)
{ {
IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack);
@ -187,7 +195,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
else else
{ {
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType;
// Widening: clean up according to source type width // Widening: clean up according to source type width
// Non-widening and force: clean up according to target type bits // Non-widening and force: clean up according to target type bits
if (targetType.getNumBits() > typeOnStack.getNumBits()) if (targetType.getNumBits() > typeOnStack.getNumBits())
@ -196,15 +204,17 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
appendHighBitsCleanup(targetType); appendHighBitsCleanup(targetType);
} }
} }
} break;
else if (_typeOnStack != _targetType) default:
// All other types should not be convertible to non-equal types. // All other types should not be convertible to non-equal types.
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
break;
}
} }
bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(Assignment const& _assignment)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_assignment); CompilerContext::LocationSetter locationSetter(m_context, _assignment);
_assignment.getRightHandSide().accept(*this); _assignment.getRightHandSide().accept(*this);
if (_assignment.getType()->isValueType()) if (_assignment.getType()->isValueType())
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
@ -226,9 +236,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
m_currentLValue->retrieveValue(_assignment.getLocation(), true); m_currentLValue->retrieveValue(_assignment.getLocation(), true);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType());
if (lvalueSize > 0) if (lvalueSize > 0)
{
solAssert(itemSize + lvalueSize <= 16, "Stack too deep.");
// value [lvalue_ref] updated_value // value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i) for (unsigned i = 0; i < itemSize; ++i)
m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP;
}
} }
m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation());
m_currentLValue.reset(); m_currentLValue.reset();
@ -237,7 +250,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_unaryOperation); CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
//@todo type checking and creating code for an operator should be in the same place: //@todo type checking and creating code for an operator should be in the same place:
// the operator should know how to convert itself and to which types it applies, so // the operator should know how to convert itself and to which types it applies, so
// put this code together with "Type::acceptsBinary/UnaryOperator" into a class that // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that
@ -270,23 +283,24 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Dec: // -- (pre- or postfix) case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved."); solAssert(!!m_currentLValue, "LValue not retrieved.");
m_currentLValue->retrieveValue(_unaryOperation.getLocation()); m_currentLValue->retrieveValue(_unaryOperation.getLocation());
solAssert(m_currentLValue->sizeOnStack() <= 1, "Not implemented.");
if (!_unaryOperation.isPrefixOperation()) if (!_unaryOperation.isPrefixOperation())
{ {
if (m_currentLValue->sizeOnStack() == 1) // store value for later
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented.");
else m_context << eth::Instruction::DUP1;
m_context << eth::Instruction::DUP1; if (m_currentLValue->sizeOnStack() > 0)
for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i);
} }
m_context << u256(1); m_context << u256(1);
if (_unaryOperation.getOperator() == Token::Inc) if (_unaryOperation.getOperator() == Token::Inc)
m_context << eth::Instruction::ADD; m_context << eth::Instruction::ADD;
else else
m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB;
// Stack for prefix: [ref] (*ref)+-1 // Stack for prefix: [ref...] (*ref)+-1
// Stack for postfix: *ref [ref] (*ref)+-1 // Stack for postfix: *ref [ref...] (*ref)+-1
if (m_currentLValue->sizeOnStack() == 1) for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
m_context << eth::Instruction::SWAP1; m_context << eth::swapInstruction(i);
m_currentLValue->storeValue( m_currentLValue->storeValue(
*_unaryOperation.getType(), _unaryOperation.getLocation(), *_unaryOperation.getType(), _unaryOperation.getLocation(),
!_unaryOperation.isPrefixOperation()); !_unaryOperation.isPrefixOperation());
@ -307,7 +321,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_binaryOperation); CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation);
Expression const& leftExpression = _binaryOperation.getLeftExpression(); Expression const& leftExpression = _binaryOperation.getLeftExpression();
Expression const& rightExpression = _binaryOperation.getRightExpression(); Expression const& rightExpression = _binaryOperation.getRightExpression();
Type const& commonType = _binaryOperation.getCommonType(); Type const& commonType = _binaryOperation.getCommonType();
@ -354,7 +368,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall) bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_functionCall); CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
using Location = FunctionType::Location; using Location = FunctionType::Location;
if (_functionCall.isTypeConversion()) if (_functionCall.isTypeConversion())
{ {
@ -405,7 +419,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
_functionCall.getExpression().accept(*this); _functionCall.getExpression().accept(*this);
m_context.appendJump(); m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction);
m_context << returnLabel; m_context << returnLabel;
unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes()); unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes());
@ -528,8 +542,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
appendTypeConversion(*arguments[arg - 1]->getType(), appendTypeConversion(*arguments[arg - 1]->getType(),
*function.getParameterTypes()[arg - 1], true); *function.getParameterTypes()[arg - 1], true);
} }
m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); if (!event.isAnonymous())
++numIndexed; {
m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName()))));
++numIndexed;
}
solAssert(numIndexed <= 4, "Too many indexed arguments."); solAssert(numIndexed <= 4, "Too many indexed arguments.");
// Copy all non-indexed arguments to memory (data) // Copy all non-indexed arguments to memory (data)
m_context << u256(0); m_context << u256(0);
@ -550,10 +567,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case Location::SHA256: case Location::SHA256:
case Location::RIPEMD160: case Location::RIPEMD160:
{ {
_functionCall.getExpression().accept(*this);
static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
{Location::SHA256, 2}, {Location::SHA256, 2},
{Location::RIPEMD160, 3}}; {Location::RIPEMD160, 3}};
m_context << contractAddresses.find(function.getLocation())->second; m_context << contractAddresses.find(function.getLocation())->second;
for (unsigned i = function.getSizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i);
appendExternalFunctionCall(function, arguments, true); appendExternalFunctionCall(function, arguments, true);
break; break;
} }
@ -572,7 +592,7 @@ bool ExpressionCompiler::visit(NewExpression const&)
void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_memberAccess); CompilerContext::LocationSetter locationSetter(m_context, _memberAccess);
ASTString const& member = _memberAccess.getMemberName(); ASTString const& member = _memberAccess.getMemberName();
switch (_memberAccess.getExpression().getType()->getCategory()) switch (_memberAccess.getExpression().getType()->getCategory())
{ {
@ -639,13 +659,18 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << eth::Instruction::GASPRICE; m_context << eth::Instruction::GASPRICE;
else if (member == "data") else if (member == "data")
m_context << u256(0) << eth::Instruction::CALLDATASIZE; m_context << u256(0) << eth::Instruction::CALLDATASIZE;
else if (member == "sig")
m_context << u256(0) << eth::Instruction::CALLDATALOAD
<< (u256(0xffffffff) << (256 - 32)) << eth::Instruction::AND;
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
break; break;
case Type::Category::Struct: case Type::Category::Struct:
{ {
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType()); StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; m_context << eth::Instruction::POP; // structs always align to new slot
pair<u256, unsigned> const& offsets = type.getStorageOffsetsOfMember(member);
m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second);
setLValueToStorageItem(_memberAccess); setLValueToStorageItem(_memberAccess);
break; break;
} }
@ -707,110 +732,43 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_indexAccess); CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
_indexAccess.getBaseExpression().accept(*this); _indexAccess.getBaseExpression().accept(*this);
Type const& baseType = *_indexAccess.getBaseExpression().getType(); Type const& baseType = *_indexAccess.getBaseExpression().getType();
if (baseType.getCategory() == Type::Category::Mapping) if (baseType.getCategory() == Type::Category::Mapping)
{ {
// storage byte offset is ignored for mappings, it should be zero.
m_context << eth::Instruction::POP;
// stack: storage_base_ref
Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType(); Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType();
m_context << u256(0); m_context << u256(0); // memory position
solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression());
solAssert(baseType.getSizeOnStack() == 1,
"Unexpected: Not exactly one stack slot taken by subscriptable expression.");
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
appendTypeMoveToMemory(IntegerType(256)); appendTypeMoveToMemory(IntegerType(256));
m_context << u256(0) << eth::Instruction::SHA3; m_context << u256(0) << eth::Instruction::SHA3;
m_context << u256(0);
setLValueToStorageItem( _indexAccess); setLValueToStorageItem( _indexAccess);
} }
else if (baseType.getCategory() == Type::Category::Array) else if (baseType.getCategory() == Type::Category::Array)
{ {
// stack layout: <base_ref> [<length>] <index>
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType); ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
ArrayType::Location location = arrayType.getLocation();
eth::Instruction load = // remove storage byte offset
location == ArrayType::Location::Storage ? eth::Instruction::SLOAD : if (arrayType.getLocation() == ArrayType::Location::Storage)
location == ArrayType::Location::Memory ? eth::Instruction::MLOAD : m_context << eth::Instruction::POP;
eth::Instruction::CALLDATALOAD;
_indexAccess.getIndexExpression()->accept(*this); _indexAccess.getIndexExpression()->accept(*this);
// retrieve length // stack layout: <base_ref> [<length>] <index>
if (!arrayType.isDynamicallySized()) ArrayUtils(m_context).accessIndex(arrayType);
m_context << arrayType.getLength(); if (arrayType.getLocation() == ArrayType::Location::Storage)
else if (location == ArrayType::Location::CallData)
// length is stored on the stack
m_context << eth::Instruction::SWAP1;
else
m_context << eth::Instruction::DUP2 << load;
// stack: <base_ref> <index> <length>
// check out-of-bounds access
m_context << eth::Instruction::DUP2 << eth::Instruction::LT;
eth::AssemblyItem legalAccess = m_context.appendConditionalJump();
// out-of-bounds access throws exception (just STOP for now)
m_context << eth::Instruction::STOP;
m_context << legalAccess;
// stack: <base_ref> <index>
if (arrayType.isByteArray())
// byte array is packed differently, especially in storage
switch (location)
{
case ArrayType::Location::Storage:
// byte array index storage lvalue on stack (goal):
// <ref> <byte_number> = <base_ref + index / 32> <index % 32>
m_context << u256(32) << eth::Instruction::SWAP2;
CompilerUtils(m_context).computeHashStatic();
// stack: 32 index data_ref
m_context
<< eth::Instruction::DUP3 << eth::Instruction::DUP3
<< eth::Instruction::DIV << eth::Instruction::ADD
// stack: 32 index (data_ref + index / 32)
<< eth::Instruction::SWAP2 << eth::Instruction::SWAP1 << eth::Instruction::MOD;
setLValue<StorageByteArrayElement>(_indexAccess);
break;
case ArrayType::Location::CallData:
// no lvalue, just retrieve the value
m_context
<< eth::Instruction::ADD << eth::Instruction::CALLDATALOAD
<< u256(0) << eth::Instruction::BYTE;
break;
case ArrayType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented.");
}
else
{ {
u256 elementSize = if (arrayType.isByteArray())
location == ArrayType::Location::Storage ? setLValue<StorageByteArrayElement>(_indexAccess);
arrayType.getBaseType()->getStorageSize() : else
arrayType.getBaseType()->getCalldataEncodedSize();
solAssert(elementSize != 0, "Invalid element size.");
if (elementSize > 1)
m_context << elementSize << eth::Instruction::MUL;
if (arrayType.isDynamicallySized())
{
if (location == ArrayType::Location::Storage)
{
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
}
else if (location == ArrayType::Location::Memory)
m_context << u256(32) << eth::Instruction::ADD;
}
m_context << eth::Instruction::ADD;
switch (location)
{
case ArrayType::Location::CallData:
if (arrayType.getBaseType()->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false);
break;
case ArrayType::Location::Storage:
setLValueToStorageItem(_indexAccess); setLValueToStorageItem(_indexAccess);
break;
case ArrayType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented.");
}
} }
} }
else else
@ -821,18 +779,34 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Identifier const& _identifier)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _identifier);
Declaration const* declaration = _identifier.getReferencedDeclaration(); Declaration const* declaration = _identifier.getReferencedDeclaration();
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
{ {
if (magicVar->getType()->getCategory() == Type::Category::Contract) switch (magicVar->getType()->getCategory())
{
case Type::Category::Contract:
// "this" or "super" // "this" or "super"
if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper()) if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper())
m_context << eth::Instruction::ADDRESS; m_context << eth::Instruction::ADDRESS;
break;
case Type::Category::Integer:
// "now"
m_context << eth::Instruction::TIMESTAMP;
break;
default:
break;
}
} }
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag();
else if (dynamic_cast<VariableDeclaration const*>(declaration)) else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
setLValueFromDeclaration(*declaration, _identifier); {
if (!variable->isConstant())
setLValueFromDeclaration(*declaration, _identifier);
else
variable->getValue()->accept(*this);
}
else if (dynamic_cast<ContractDefinition const*>(declaration)) else if (dynamic_cast<ContractDefinition const*>(declaration))
{ {
// no-op // no-op
@ -860,11 +834,12 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
void ExpressionCompiler::endVisit(Literal const& _literal) void ExpressionCompiler::endVisit(Literal const& _literal)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _literal);
switch (_literal.getType()->getCategory()) switch (_literal.getType()->getCategory())
{ {
case Type::Category::IntegerConstant: case Type::Category::IntegerConstant:
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::String: case Type::Category::FixedBytes:
m_context << _literal.getType()->literalValue(&_literal); m_context << _literal.getType()->literalValue(&_literal);
break; break;
default: default:
@ -1115,7 +1090,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio
if (m_context.isLocalVariable(&_declaration)) if (m_context.isLocalVariable(&_declaration))
setLValue<StackVariable>(_expression, _declaration); setLValue<StackVariable>(_expression, _declaration);
else if (m_context.isStateVariable(&_declaration)) else if (m_context.isStateVariable(&_declaration))
setLValue<StorageItem>(_expression, _declaration); setLValue<StorageItem>(_expression, _declaration);
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() BOOST_THROW_EXCEPTION(InternalCompilerError()
<< errinfo_sourceLocation(_expression.getLocation()) << errinfo_sourceLocation(_expression.getLocation())

View File

@ -42,7 +42,6 @@ class CompilerContext;
class Type; class Type;
class IntegerType; class IntegerType;
class ArrayType; class ArrayType;
class StaticStringType;
/** /**
* Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
@ -140,7 +139,6 @@ void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments con
m_currentLValue = move(lvalue); m_currentLValue = move(lvalue);
else else
lvalue->retrieveValue(_expression.getLocation(), true); lvalue->retrieveValue(_expression.getLocation(), true);
} }
} }

View File

@ -37,26 +37,27 @@ GlobalContext::GlobalContext():
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)),
make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)), make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)),
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("suicide", make_shared<MagicVariableDeclaration>("suicide",
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)),
make_shared<MagicVariableDeclaration>("sha3", make_shared<MagicVariableDeclaration>("sha3",
make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)),
make_shared<MagicVariableDeclaration>("log0", make_shared<MagicVariableDeclaration>("log0",
make_shared<FunctionType>(strings{"hash"},strings{}, FunctionType::Location::Log0)), make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)),
make_shared<MagicVariableDeclaration>("log1", make_shared<MagicVariableDeclaration>("log1",
make_shared<FunctionType>(strings{"hash", "hash"},strings{}, FunctionType::Location::Log1)), make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)),
make_shared<MagicVariableDeclaration>("log2", make_shared<MagicVariableDeclaration>("log2",
make_shared<FunctionType>(strings{"hash", "hash", "hash"},strings{}, FunctionType::Location::Log2)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)),
make_shared<MagicVariableDeclaration>("log3", make_shared<MagicVariableDeclaration>("log3",
make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log3)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)),
make_shared<MagicVariableDeclaration>("log4", make_shared<MagicVariableDeclaration>("log4",
make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<MagicVariableDeclaration>("sha256",
make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<MagicVariableDeclaration>("ecrecover",
make_shared<FunctionType>(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))})
{ {
} }

View File

@ -70,6 +70,7 @@ std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinitio
Json::Value event; Json::Value event;
event["type"] = "event"; event["type"] = "event";
event["name"] = it->getName(); event["name"] = it->getName();
event["anonymous"] = it->isAnonymous();
Json::Value params(Json::arrayValue); Json::Value params(Json::arrayValue);
for (auto const& p: it->getParameters()) for (auto const& p: it->getParameters())
{ {
@ -128,7 +129,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi
if (!m_notice.empty()) if (!m_notice.empty())
{// since @notice is the only user tag if missing function should not appear {// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(m_notice); user["notice"] = Json::Value(m_notice);
methods[it.second->getCanonicalSignature()] = user; methods[it.second->externalSignature()] = user;
} }
} }
} }
@ -174,8 +175,17 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
method["author"] = m_author; method["author"] = m_author;
Json::Value params(Json::objectValue); Json::Value params(Json::objectValue);
std::vector<std::string> paramNames = it.second->getParameterNames();
for (auto const& pair: m_params) for (auto const& pair: m_params)
{
if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end())
// LTODO: mismatching parameter name, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(
DocstringParsingError() <<
errinfo_comment("documented parameter \"" + pair.first + "\" not found found in the function")
);
params[pair.first] = pair.second; params[pair.first] = pair.second;
}
if (!m_params.empty()) if (!m_params.empty())
method["params"] = params; method["params"] = params;
@ -184,7 +194,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
method["return"] = m_return; method["return"] = m_return;
if (!method.empty()) // add the function, only if we have any documentation to add if (!method.empty()) // add the function, only if we have any documentation to add
methods[it.second->getCanonicalSignature()] = method; methods[it.second->externalSignature()] = method;
} }
} }
doc["methods"] = methods; doc["methods"] = methods;

View File

@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const
StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration):
StorageItem(_compilerContext, *_declaration.getType()) StorageItem(_compilerContext, *_declaration.getType())
{ {
m_context << m_context.getStorageLocationOfVariable(_declaration); auto const& location = m_context.getStorageLocationOfVariable(_declaration);
m_context << location.first << u256(location.second);
} }
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
@ -86,62 +87,78 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
if (m_dataType.isValueType()) if (m_dataType.isValueType())
{ {
solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), "");
solAssert(m_dataType.getStorageSize() <= numeric_limits<unsigned>::max(), solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size.");
"The storage size of " + m_dataType.toString() + " should fit in an unsigned");
m_size = unsigned(m_dataType.getStorageSize());
} }
else
m_size = 0; // unused
} }
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
{ {
// stack: storage_key storage_offset
if (!m_dataType.isValueType()) if (!m_dataType.isValueType())
return; // no distinction between value and reference for non-value types return; // no distinction between value and reference for non-value types
if (!_remove) if (!_remove)
m_context << eth::Instruction::DUP1; CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
if (m_size == 1) if (m_dataType.getStorageBytes() == 32)
m_context << eth::Instruction::SLOAD; m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
else else
for (unsigned i = 0; i < m_size; ++i) {
{ m_context
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1
if (i + 1 < m_size) << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV;
m_context << u256(1) << eth::Instruction::ADD; if (m_dataType.getCategory() == Type::Category::FixedBytes)
else m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL;
m_context << eth::Instruction::POP; else
} m_context << ((u256(0x1) << (8 * m_dataType.getStorageBytes())) - 1) << eth::Instruction::AND;
}
} }
void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const
{ {
// stack layout: value value ... value target_ref // stack: value storage_key storage_offset
if (m_dataType.isValueType()) if (m_dataType.isValueType())
{ {
if (!_move) // copy values solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size.");
if (m_dataType.getStorageBytes() == 32)
{ {
if (m_size + 1 > 16) // offset should be zero
BOOST_THROW_EXCEPTION(CompilerError() m_context << eth::Instruction::POP;
<< errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); if (!_move)
for (unsigned i = 0; i < m_size; ++i) m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; m_context << eth::Instruction::SSTORE;
} }
if (m_size > 1) // store high index value first else
m_context << u256(m_size - 1) << eth::Instruction::ADD;
for (unsigned i = 0; i < m_size; ++i)
{ {
if (i + 1 >= m_size) // OR the value into the other values in the storage slot
m_context << eth::Instruction::SSTORE; m_context << u256(0x100) << eth::Instruction::EXP;
else // stack: value storage_ref multiplier
// stack here: value value ... value value (target_ref+offset) // fetch old value
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
<< eth::Instruction::SSTORE // stack: value storege_ref multiplier old_full_value
<< u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; // clear bytes in old value
m_context
<< eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
<< eth::Instruction::MUL;
m_context << eth::Instruction::NOT << eth::Instruction::AND;
// stack: value storage_ref multiplier cleared_value
m_context
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4;
// stack: value storage_ref cleared_value multiplier value
if (m_dataType.getCategory() == Type::Category::FixedBytes)
m_context
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).getNumBytes()))
<< eth::Instruction::SWAP1 << eth::Instruction::DIV;
m_context << eth::Instruction::MUL << eth::Instruction::OR;
// stack: value storage_ref updated_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
if (_move)
m_context << eth::Instruction::POP;
} }
} }
else else
{ {
solAssert(_sourceType.getCategory() == m_dataType.getCategory(), solAssert(
_sourceType.getCategory() == m_dataType.getCategory(),
"Wrong type conversation for assignment."); "Wrong type conversation for assignment.");
if (m_dataType.getCategory() == Type::Category::Array) if (m_dataType.getCategory() == Type::Category::Array)
{ {
@ -149,11 +166,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
dynamic_cast<ArrayType const&>(m_dataType), dynamic_cast<ArrayType const&>(m_dataType),
dynamic_cast<ArrayType const&>(_sourceType)); dynamic_cast<ArrayType const&>(_sourceType));
if (_move) if (_move)
m_context << eth::Instruction::POP; CompilerUtils(m_context).popStackElement(_sourceType);
} }
else if (m_dataType.getCategory() == Type::Category::Struct) else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: source_ref target_ref // stack layout: source_ref source_offset target_ref target_offset
// note that we have structs, so offsets should be zero and are ignored
auto const& structType = dynamic_cast<StructType const&>(m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
solAssert(structType == _sourceType, "Struct assignment with conversion."); solAssert(structType == _sourceType, "Struct assignment with conversion.");
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
@ -162,26 +180,37 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
TypePointer const& memberType = member.second; TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
m_context << structType.getStorageOffsetOfMember(member.first) pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
<< eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; m_context
// stack: source_ref target_ref member_offset source_member_ref << offsets.first << u256(offsets.second)
<< eth::Instruction::DUP6 << eth::Instruction::DUP3
<< eth::Instruction::ADD << eth::Instruction::DUP2;
// stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_member_ref source_member_off
StorageItem(m_context, *memberType).retrieveValue(_location, true); StorageItem(m_context, *memberType).retrieveValue(_location, true);
// stack: source_ref target_ref member_offset source_value... // stack: source_ref source_off target_ref target_off member_offset source_value...
m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep.");
<< eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; m_context
// stack: source_ref target_ref member_offset source_value... target_member_ref << eth::dupInstruction(4 + memberType->getSizeOnStack())
<< eth::dupInstruction(3 + memberType->getSizeOnStack()) << eth::Instruction::ADD
<< eth::dupInstruction(2 + memberType->getSizeOnStack());
// stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*memberType, _location, true); StorageItem(m_context, *memberType).storeValue(*memberType, _location, true);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::POP;
} }
if (_move) if (_move)
m_context << eth::Instruction::POP; m_context
<< eth::Instruction::POP << eth::Instruction::POP
<< eth::Instruction::POP << eth::Instruction::POP;
else else
m_context << eth::Instruction::SWAP1; m_context
m_context << eth::Instruction::POP; << eth::Instruction::SWAP2 << eth::Instruction::POP
<< eth::Instruction::SWAP2 << eth::Instruction::POP;
} }
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() BOOST_THROW_EXCEPTION(
<< errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); InternalCompilerError()
<< errinfo_sourceLocation(_location)
<< errinfo_comment("Invalid non-value type for assignment."));
} }
} }
@ -190,12 +219,13 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
if (m_dataType.getCategory() == Type::Category::Array) if (m_dataType.getCategory() == Type::Category::Array)
{ {
if (!_removeReference) if (!_removeReference)
m_context << eth::Instruction::DUP1; CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType));
} }
else if (m_dataType.getCategory() == Type::Category::Struct) else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: ref // stack layout: storage_key storage_offset
// @todo this can be improved for packed types
auto const& structType = dynamic_cast<StructType const&>(m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
@ -203,38 +233,48 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
TypePointer const& memberType = member.second; TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
m_context << structType.getStorageOffsetOfMember(member.first) pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
<< eth::Instruction::DUP2 << eth::Instruction::ADD; m_context
<< offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD
<< u256(offsets.second);
StorageItem(m_context, *memberType).setToZero(); StorageItem(m_context, *memberType).setToZero();
} }
if (_removeReference) if (_removeReference)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP << eth::Instruction::POP;
} }
else else
{ {
solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString());
if (m_size == 0 && _removeReference) // @todo actually use offset
m_context << eth::Instruction::POP; if (!_removeReference)
else if (m_size == 1) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
if (m_dataType.getStorageBytes() == 32)
{
// offset should be zero
m_context m_context
<< u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) << eth::Instruction::POP << u256(0)
<< eth::Instruction::SSTORE; << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
}
else else
{ {
if (!_removeReference) m_context << u256(0x100) << eth::Instruction::EXP;
m_context << eth::Instruction::DUP1; // stack: storage_ref multiplier
for (unsigned i = 0; i < m_size; ++i) // fetch old value
if (i + 1 >= m_size) m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; // stack: storege_ref multiplier old_full_value
else // clear bytes in old value
m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE m_context
<< u256(1) << eth::Instruction::ADD; << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
<< eth::Instruction::MUL;
m_context << eth::Instruction::NOT << eth::Instruction::AND;
// stack: storage_ref cleared_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
} }
} }
} }
/// Used in StorageByteArrayElement /// Used in StorageByteArrayElement
static IntegerType byteType(8, IntegerType::Modifier::Hash); static FixedBytesType byteType(1);
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
LValue(_compilerContext, byteType) LValue(_compilerContext, byteType)
@ -250,6 +290,7 @@ void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove)
else else
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD
<< eth::Instruction::DUP2 << eth::Instruction::BYTE; << eth::Instruction::DUP2 << eth::Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL;
} }
void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const
@ -265,8 +306,9 @@ void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, boo
m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL
<< eth::Instruction::NOT << eth::Instruction::AND; << eth::Instruction::NOT << eth::Instruction::AND;
// stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::MUL m_context << eth::Instruction::SWAP1;
<< eth::Instruction::OR; m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV
<< eth::Instruction::MUL << eth::Instruction::OR;
// stack: value ref new_full_value // stack: value ref new_full_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
if (_move) if (_move)
@ -297,6 +339,8 @@ StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const
m_arrayType(_arrayType) m_arrayType(_arrayType)
{ {
solAssert(m_arrayType.isDynamicallySized(), ""); solAssert(m_arrayType.isDynamicallySized(), "");
// storage byte offset must be zero
m_context << eth::Instruction::POP;
} }
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const

View File

@ -98,7 +98,9 @@ private:
}; };
/** /**
* Reference to some item in storage. The (starting) position of the item is stored on the stack. * 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
* by 2**i before storing it.
*/ */
class StorageItem: public LValue class StorageItem: public LValue
{ {
@ -107,6 +109,7 @@ public:
StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration);
/// Constructs the LValue and assumes that the storage reference is already on the stack. /// Constructs the LValue and assumes that the storage reference is already on the stack.
StorageItem(CompilerContext& _compilerContext, Type const& _type); StorageItem(CompilerContext& _compilerContext, Type const& _type);
virtual unsigned sizeOnStack() const { return 2; }
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue( virtual void storeValue(
Type const& _sourceType, Type const& _sourceType,
@ -117,11 +120,6 @@ public:
SourceLocation const& _location = SourceLocation(), SourceLocation const& _location = SourceLocation(),
bool _removeReference = true bool _removeReference = true
) const override; ) const override;
private:
/// Number of stack elements occupied by the value (not the reference).
/// Only used for value types.
unsigned m_size;
}; };
/** /**

View File

@ -164,8 +164,17 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
} }
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RBrace); expectToken(Token::RBrace);
return nodeFactory.createNode<ContractDefinition>(name, docString, baseContracts, structs, enums, return nodeFactory.createNode<ContractDefinition>(
stateVariables, functions, modifiers, events); name,
docString,
baseContracts,
structs,
enums,
stateVariables,
functions,
modifiers,
events
);
} }
ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier() ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
@ -247,8 +256,15 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const*
} }
else else
returnParameters = createEmptyParameterList(); returnParameters = createEmptyParameterList();
ASTPointer<Block> block = parseBlock(); ASTPointer<Block> block = ASTPointer<Block>();
nodeFactory.setEndPositionFromNode(block); nodeFactory.markEndPosition();
if (m_scanner->getCurrentToken() != Token::Semicolon)
{
block = parseBlock();
nodeFactory.setEndPositionFromNode(block);
}
else
m_scanner->next(); // just consume the ';'
bool const c_isConstructor = (_contractName && *name == *_contractName); bool const c_isConstructor = (_contractName && *name == *_contractName);
return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring, return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring,
parameters, isDeclaredConst, modifiers, parameters, isDeclaredConst, modifiers,
@ -317,6 +333,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
nodeFactory.setEndPositionFromNode(type); nodeFactory.setEndPositionFromNode(type);
} }
bool isIndexed = false; bool isIndexed = false;
bool isDeclaredConst = false;
ASTPointer<ASTString> identifier; ASTPointer<ASTString> identifier;
Token::Value token = m_scanner->getCurrentToken(); Token::Value token = m_scanner->getCurrentToken();
Declaration::Visibility visibility(Declaration::Visibility::Default); Declaration::Visibility visibility(Declaration::Visibility::Default);
@ -327,7 +344,13 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
isIndexed = true; isIndexed = true;
m_scanner->next(); m_scanner->next();
} }
if (token == Token::Const)
{
isDeclaredConst = true;
m_scanner->next();
}
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier) if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier)
{ {
identifier = make_shared<ASTString>(""); identifier = make_shared<ASTString>("");
@ -348,7 +371,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
} }
return nodeFactory.createNode<VariableDeclaration>(type, identifier, value, return nodeFactory.createNode<VariableDeclaration>(type, identifier, value,
visibility, _options.isStateVariable, visibility, _options.isStateVariable,
isIndexed); isIndexed, isDeclaredConst);
} }
ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
@ -387,9 +410,15 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
parameters = parseParameterList(true, true); parameters = parseParameterList(true, true);
else else
parameters = createEmptyParameterList(); parameters = createEmptyParameterList();
bool anonymous = false;
if (m_scanner->getCurrentToken() == Token::Anonymous)
{
anonymous = true;
m_scanner->next();
}
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::Semicolon); expectToken(Token::Semicolon);
return nodeFactory.createNode<EventDefinition>(name, docstring, parameters); return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous);
} }
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
@ -913,6 +942,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const
// In all other cases, we have an expression statement. // In all other cases, we have an expression statement.
Token::Value token(m_scanner->getCurrentToken()); Token::Value token(m_scanner->getCurrentToken());
bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
if (token == Token::Mapping || token == Token::Var || if (token == Token::Mapping || token == Token::Var ||
(mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier)) (mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier))
return LookAheadInfo::VariableDeclarationStatement; return LookAheadInfo::VariableDeclarationStatement;

View File

@ -34,6 +34,8 @@ class Scanner;
class Parser class Parser
{ {
public: public:
Parser() {}
ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner); ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner);
std::shared_ptr<std::string const> const& getSourceName() const; std::shared_ptr<std::string const> const& getSourceName() const;
@ -64,8 +66,7 @@ private:
ASTPointer<StructDefinition> parseStructDefinition(); ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition(); ASTPointer<EnumDefinition> parseEnumDefinition();
ASTPointer<EnumValue> parseEnumValue(); ASTPointer<EnumValue> parseEnumValue();
ASTPointer<VariableDeclaration> parseVariableDeclaration( ASTPointer<VariableDeclaration> parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions(),
VarDeclParserOptions const& _options = VarDeclParserOptions(),
ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>()); ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>());
ASTPointer<ModifierDefinition> parseModifierDefinition(); ASTPointer<ModifierDefinition> parseModifierDefinition();
ASTPointer<EventDefinition> parseEventDefinition(); ASTPointer<EventDefinition> parseEventDefinition();

117
Token.h
View File

@ -143,8 +143,8 @@ namespace solidity
\ \
/* Keywords */ \ /* Keywords */ \
K(Break, "break", 0) \ K(Break, "break", 0) \
K(Case, "case", 0) \
K(Const, "constant", 0) \ K(Const, "constant", 0) \
K(Anonymous, "anonymous", 0) \
K(Continue, "continue", 0) \ K(Continue, "continue", 0) \
K(Contract, "contract", 0) \ K(Contract, "contract", 0) \
K(Default, "default", 0) \ K(Default, "default", 0) \
@ -167,7 +167,6 @@ namespace solidity
K(Return, "return", 0) \ K(Return, "return", 0) \
K(Returns, "returns", 0) \ K(Returns, "returns", 0) \
K(Struct, "struct", 0) \ K(Struct, "struct", 0) \
K(Switch, "switch", 0) \
K(Var, "var", 0) \ K(Var, "var", 0) \
K(While, "while", 0) \ K(While, "while", 0) \
K(Enum, "enum", 0) \ K(Enum, "enum", 0) \
@ -252,77 +251,43 @@ namespace solidity
K(UInt240, "uint240", 0) \ K(UInt240, "uint240", 0) \
K(UInt248, "uint248", 0) \ K(UInt248, "uint248", 0) \
K(UInt256, "uint256", 0) \ K(UInt256, "uint256", 0) \
K(Hash, "hash", 0) \ K(Bytes0, "bytes0", 0) \
K(Hash8, "hash8", 0) \ K(Bytes1, "bytes1", 0) \
K(Hash16, "hash16", 0) \ K(Bytes2, "bytes2", 0) \
K(Hash24, "hash24", 0) \ K(Bytes3, "bytes3", 0) \
K(Hash32, "hash32", 0) \ K(Bytes4, "bytes4", 0) \
K(Hash40, "hash40", 0) \ K(Bytes5, "bytes5", 0) \
K(Hash48, "hash48", 0) \ K(Bytes6, "bytes6", 0) \
K(Hash56, "hash56", 0) \ K(Bytes7, "bytes7", 0) \
K(Hash64, "hash64", 0) \ K(Bytes8, "bytes8", 0) \
K(Hash72, "hash72", 0) \ K(Bytes9, "bytes9", 0) \
K(Hash80, "hash80", 0) \ K(Bytes10, "bytes10", 0) \
K(Hash88, "hash88", 0) \ K(Bytes11, "bytes11", 0) \
K(Hash96, "hash96", 0) \ K(Bytes12, "bytes12", 0) \
K(Hash104, "hash104", 0) \ K(Bytes13, "bytes13", 0) \
K(Hash112, "hash112", 0) \ K(Bytes14, "bytes14", 0) \
K(Hash120, "hash120", 0) \ K(Bytes15, "bytes15", 0) \
K(Hash128, "hash128", 0) \ K(Bytes16, "bytes16", 0) \
K(Hash136, "hash136", 0) \ K(Bytes17, "bytes17", 0) \
K(Hash144, "hash144", 0) \ K(Bytes18, "bytes18", 0) \
K(Hash152, "hash152", 0) \ K(Bytes19, "bytes19", 0) \
K(Hash160, "hash160", 0) \ K(Bytes20, "bytes20", 0) \
K(Hash168, "hash168", 0) \ K(Bytes21, "bytes21", 0) \
K(Hash176, "hash178", 0) \ K(Bytes22, "bytes22", 0) \
K(Hash184, "hash184", 0) \ K(Bytes23, "bytes23", 0) \
K(Hash192, "hash192", 0) \ K(Bytes24, "bytes24", 0) \
K(Hash200, "hash200", 0) \ K(Bytes25, "bytes25", 0) \
K(Hash208, "hash208", 0) \ K(Bytes26, "bytes26", 0) \
K(Hash216, "hash216", 0) \ K(Bytes27, "bytes27", 0) \
K(Hash224, "hash224", 0) \ K(Bytes28, "bytes28", 0) \
K(Hash232, "hash232", 0) \ K(Bytes29, "bytes29", 0) \
K(Hash240, "hash240", 0) \ K(Bytes30, "bytes30", 0) \
K(Hash248, "hash248", 0) \ K(Bytes31, "bytes31", 0) \
K(Hash256, "hash256", 0) \ K(Bytes32, "bytes32", 0) \
K(Bytes, "bytes", 0) \
K(Byte, "byte", 0) \
K(Address, "address", 0) \ K(Address, "address", 0) \
K(Bool, "bool", 0) \ K(Bool, "bool", 0) \
K(Bytes, "bytes", 0) \
K(StringType, "string", 0) \
K(String0, "string0", 0) \
K(String1, "string1", 0) \
K(String2, "string2", 0) \
K(String3, "string3", 0) \
K(String4, "string4", 0) \
K(String5, "string5", 0) \
K(String6, "string6", 0) \
K(String7, "string7", 0) \
K(String8, "string8", 0) \
K(String9, "string9", 0) \
K(String10, "string10", 0) \
K(String11, "string11", 0) \
K(String12, "string12", 0) \
K(String13, "string13", 0) \
K(String14, "string14", 0) \
K(String15, "string15", 0) \
K(String16, "string16", 0) \
K(String17, "string17", 0) \
K(String18, "string18", 0) \
K(String19, "string19", 0) \
K(String20, "string20", 0) \
K(String21, "string21", 0) \
K(String22, "string22", 0) \
K(String23, "string23", 0) \
K(String24, "string24", 0) \
K(String25, "string25", 0) \
K(String26, "string26", 0) \
K(String27, "string27", 0) \
K(String28, "string28", 0) \
K(String29, "string29", 0) \
K(String30, "string30", 0) \
K(String31, "string31", 0) \
K(String32, "string32", 0) \
K(Text, "text", 0) \
K(Real, "real", 0) \ K(Real, "real", 0) \
K(UReal, "ureal", 0) \ K(UReal, "ureal", 0) \
T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ T(TypesEnd, NULL, 0) /* used as type enum end marker */ \
@ -338,6 +303,16 @@ namespace solidity
/* Identifiers (not keywords or future reserved words). */ \ /* Identifiers (not keywords or future reserved words). */ \
T(Identifier, NULL, 0) \ T(Identifier, NULL, 0) \
\ \
/* Keywords reserved for future. use*/ \
T(String, "string", 0) \
K(Case, "case", 0) \
K(Switch, "switch", 0) \
K(Throw, "throw", 0) \
K(Try, "try", 0) \
K(Catch, "catch", 0) \
K(Using, "using", 0) \
K(Type, "type", 0) \
K(TypeOf, "typeof", 0) \
/* Illegal token - not able to scan. */ \ /* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \ T(Illegal, "ILLEGAL", 0) \
\ \

408
Types.cpp
View File

@ -20,14 +20,14 @@
* Solidity data types * Solidity data types
*/ */
#include <libsolidity/Types.h>
#include <limits>
#include <boost/range/adaptor/reversed.hpp>
#include <libdevcore/CommonIO.h> #include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libsolidity/Utils.h> #include <libsolidity/Utils.h>
#include <libsolidity/Types.h>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <limits>
using namespace std; using namespace std;
namespace dev namespace dev
@ -35,28 +35,113 @@ namespace dev
namespace solidity namespace solidity
{ {
void StorageOffsets::computeOffsets(TypePointers const& _types)
{
bigint slotOffset = 0;
unsigned byteOffset = 0;
map<size_t, pair<u256, unsigned>> offsets;
for (size_t i = 0; i < _types.size(); ++i)
{
TypePointer const& type = _types[i];
if (!type->canBeStored())
continue;
if (byteOffset + type->getStorageBytes() > 32)
{
// would overflow, go to next slot
++slotOffset;
byteOffset = 0;
}
if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
offsets[i] = make_pair(u256(slotOffset), byteOffset);
solAssert(type->getStorageSize() >= 1, "Invalid storage size.");
if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32)
byteOffset += type->getStorageBytes();
else
{
slotOffset += type->getStorageSize();
byteOffset = 0;
}
}
if (byteOffset > 0)
++slotOffset;
if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
m_storageSize = u256(slotOffset);
swap(m_offsets, offsets);
}
pair<u256, unsigned> const* StorageOffsets::getOffset(size_t _index) const
{
if (m_offsets.count(_index))
return &m_offsets.at(_index);
else
return nullptr;
}
MemberList& MemberList::operator=(MemberList&& _other)
{
m_memberTypes = std::move(_other.m_memberTypes);
m_storageOffsets = std::move(_other.m_storageOffsets);
return *this;
}
std::pair<u256, unsigned> const* MemberList::getMemberStorageOffset(string const& _name) const
{
if (!m_storageOffsets)
{
TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size());
for (auto const& nameAndType: m_memberTypes)
memberTypes.push_back(nameAndType.second);
m_storageOffsets.reset(new StorageOffsets());
m_storageOffsets->computeOffsets(memberTypes);
}
for (size_t index = 0; index < m_memberTypes.size(); ++index)
if (m_memberTypes[index].first == _name)
return m_storageOffsets->getOffset(index);
return nullptr;
}
u256 const& MemberList::getStorageSize() const
{
// trigger lazy computation
getMemberStorageOffset("");
return m_storageOffsets->getStorageSize();
}
TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
{ {
solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected."); char const* tokenCstr = Token::toString(_typeToken);
solAssert(Token::isElementaryTypeName(_typeToken),
"Expected an elementary type name but got " + ((tokenCstr) ? std::string(Token::toString(_typeToken)) : ""));
if (Token::Int <= _typeToken && _typeToken <= Token::Hash256) if (Token::Int <= _typeToken && _typeToken <= Token::Bytes32)
{ {
int offset = _typeToken - Token::Int; int offset = _typeToken - Token::Int;
int bytes = offset % 33; int bytes = offset % 33;
if (bytes == 0) if (bytes == 0 && _typeToken != Token::Bytes0)
bytes = 32; bytes = 32;
int modifier = offset / 33; int modifier = offset / 33;
return make_shared<IntegerType>(bytes * 8, switch(modifier)
modifier == 0 ? IntegerType::Modifier::Signed : {
modifier == 1 ? IntegerType::Modifier::Unsigned : case 0:
IntegerType::Modifier::Hash); return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Signed);
case 1:
return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Unsigned);
case 2:
return make_shared<FixedBytesType>(bytes);
default:
solAssert(false, "Unexpected modifier value. Should never happen");
return TypePointer();
}
} }
else if (_typeToken == Token::Byte)
return make_shared<FixedBytesType>(1);
else if (_typeToken == Token::Address) else if (_typeToken == Token::Address)
return make_shared<IntegerType>(0, IntegerType::Modifier::Address); return make_shared<IntegerType>(0, IntegerType::Modifier::Address);
else if (_typeToken == Token::Bool) else if (_typeToken == Token::Bool)
return make_shared<BoolType>(); return make_shared<BoolType>();
else if (Token::String0 <= _typeToken && _typeToken <= Token::String32)
return make_shared<StaticStringType>(int(_typeToken) - int(Token::String0));
else if (_typeToken == Token::Bytes) else if (_typeToken == Token::Bytes)
return make_shared<ArrayType>(ArrayType::Location::Storage); return make_shared<ArrayType>(ArrayType::Location::Storage);
else else
@ -99,6 +184,8 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length
TypePointer baseType = _baseTypeName.toType(); TypePointer baseType = _baseTypeName.toType();
if (!baseType) if (!baseType)
BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name.")); BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name."));
if (baseType->getStorageBytes() == 0)
BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Illegal base type of storage size zero for array."));
if (_length) if (_length)
{ {
if (!_length->getType()) if (!_length->getType())
@ -123,7 +210,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
return make_shared<IntegerConstantType>(_literal); return make_shared<IntegerConstantType>(_literal);
case Token::StringLiteral: case Token::StringLiteral:
//@todo put larger strings into dynamic strings //@todo put larger strings into dynamic strings
return StaticStringType::smallestTypeForLiteral(_literal.getValue()); return FixedBytesType::smallestTypeForLiteral(_literal.getValue());
default: default:
return shared_ptr<Type>(); return shared_ptr<Type>();
} }
@ -139,7 +226,7 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b)
return TypePointer(); return TypePointer();
} }
const MemberList Type::EmptyMemberList = MemberList(); const MemberList Type::EmptyMemberList;
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
m_bits(_bits), m_modifier(_modifier) m_bits(_bits), m_modifier(_modifier)
@ -159,8 +246,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false; return false;
if (isAddress()) if (isAddress())
return convertTo.isAddress(); return convertTo.isAddress();
else if (isHash())
return convertTo.isHash();
else if (isSigned()) else if (isSigned())
return convertTo.isSigned(); return convertTo.isSigned();
else else
@ -169,14 +254,10 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() == Category::String)
{
StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo);
return isHash() && (m_bits == convertTo.getNumBytes() * 8);
}
return _convertTo.getCategory() == getCategory() || return _convertTo.getCategory() == getCategory() ||
_convertTo.getCategory() == Category::Contract || _convertTo.getCategory() == Category::Contract ||
_convertTo.getCategory() == Category::Enum; _convertTo.getCategory() == Category::Enum ||
_convertTo.getCategory() == Category::FixedBytes;
} }
TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
@ -187,16 +268,10 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
// no further unary operators for addresses // no further unary operators for addresses
else if (isAddress()) else if (isAddress())
return TypePointer(); return TypePointer();
// "~" is ok for all other types // for non-address integers, we allow +, -, ++ and --
else if (_operator == Token::BitNot)
return shared_from_this();
// nothing else for hashes
else if (isHash())
return TypePointer();
// for non-hash integers, we allow +, -, ++ and --
else if (_operator == Token::Add || _operator == Token::Sub || else if (_operator == Token::Add || _operator == Token::Sub ||
_operator == Token::Inc || _operator == Token::Dec || _operator == Token::Inc || _operator == Token::Dec ||
_operator == Token::After) _operator == Token::After || _operator == Token::BitNot)
return shared_from_this(); return shared_from_this();
else else
return TypePointer(); return TypePointer();
@ -214,7 +289,7 @@ string IntegerType::toString() const
{ {
if (isAddress()) if (isAddress())
return "address"; return "address";
string prefix = isHash() ? "hash" : (isSigned() ? "int" : "uint"); string prefix = isSigned() ? "int" : "uint";
return prefix + dev::toString(m_bits); return prefix + dev::toString(m_bits);
} }
@ -230,20 +305,18 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
// All integer types can be compared // All integer types can be compared
if (Token::isCompareOp(_operator)) if (Token::isCompareOp(_operator))
return commonType; return commonType;
// Nothing else can be done with addresses
// Nothing else can be done with addresses, but hashes can receive bit operators
if (commonType->isAddress()) if (commonType->isAddress())
return TypePointer(); return TypePointer();
else if (commonType->isHash() && !Token::isBitOp(_operator))
return TypePointer(); return commonType;
else
return commonType;
} }
const MemberList IntegerType::AddressMemberList = const MemberList IntegerType::AddressMemberList({
MemberList({{"balance", make_shared<IntegerType >(256)}, {"balance", make_shared<IntegerType >(256)},
{"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)}, {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)},
{"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}
});
IntegerConstantType::IntegerConstantType(Literal const& _literal) IntegerConstantType::IntegerConstantType(Literal const& _literal)
{ {
@ -284,8 +357,17 @@ IntegerConstantType::IntegerConstantType(Literal const& _literal)
bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{ {
TypePointer integerType = getIntegerType(); shared_ptr<IntegerType const> integerType = getIntegerType();
return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); if (!integerType)
return false;
if (_convertTo.getCategory() == Category::FixedBytes)
{
FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
return convertTo.getNumBytes() * 8 >= integerType->getNumBits();
}
return integerType->isImplicitlyConvertibleTo(_convertTo);
} }
bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -433,50 +515,67 @@ shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const
: IntegerType::Modifier::Unsigned); : IntegerType::Modifier::Unsigned);
} }
shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal) shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
{ {
if (_literal.length() <= 32) if (_literal.length() <= 32)
return make_shared<StaticStringType>(_literal.length()); return make_shared<FixedBytesType>(_literal.length());
return shared_ptr<StaticStringType>(); return shared_ptr<FixedBytesType>();
} }
StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes) FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes)
{ {
solAssert(m_bytes >= 0 && m_bytes <= 32, solAssert(m_bytes >= 0 && m_bytes <= 32,
"Invalid byte number for static string type: " + dev::toString(m_bytes)); "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes));
} }
bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() != getCategory()) if (_convertTo.getCategory() != getCategory())
return false; return false;
StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo); FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
return convertTo.m_bytes >= m_bytes; return convertTo.m_bytes >= m_bytes;
} }
bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() == getCategory()) return _convertTo.getCategory() == Category::Integer ||
return true; _convertTo.getCategory() == Category::Contract ||
if (_convertTo.getCategory() == Category::Integer) _convertTo.getCategory() == getCategory();
{
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits()))
return true;
}
return false;
} }
bool StaticStringType::operator==(Type const& _other) const TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const
{
// "delete" and "~" is okay for FixedBytesType
if (_operator == Token::Delete)
return make_shared<VoidType>();
else if (_operator == Token::BitNot)
return shared_from_this();
return TypePointer();
}
TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other));
if (!commonType)
return TypePointer();
// FixedBytes can be compared and have bitwise operators applied to them
if (Token::isCompareOp(_operator) || Token::isBitOp(_operator))
return commonType;
return TypePointer();
}
bool FixedBytesType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
return false; return false;
StaticStringType const& other = dynamic_cast<StaticStringType const&>(_other); FixedBytesType const& other = dynamic_cast<FixedBytesType const&>(_other);
return other.m_bytes == m_bytes; return other.m_bytes == m_bytes;
} }
u256 StaticStringType::literalValue(const Literal* _literal) const u256 FixedBytesType::literalValue(const Literal* _literal) const
{ {
solAssert(_literal, ""); solAssert(_literal, "");
u256 value = 0; u256 value = 0;
@ -603,13 +702,21 @@ u256 ArrayType::getStorageSize() const
{ {
if (isDynamicallySized()) if (isDynamicallySized())
return 1; return 1;
else
bigint size;
unsigned baseBytes = getBaseType()->getStorageBytes();
if (baseBytes == 0)
size = 1;
else if (baseBytes < 32)
{ {
bigint size = bigint(getLength()) * getBaseType()->getStorageSize(); unsigned itemsPerSlot = 32 / baseBytes;
if (size >= bigint(1) << 256) size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot;
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage."));
return max<u256>(1, u256(size));
} }
else
size = bigint(getLength()) * getBaseType()->getStorageSize();
if (size >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage."));
return max<u256>(1, u256(size));
} }
unsigned ArrayType::getSizeOnStack() const unsigned ArrayType::getSizeOnStack() const
@ -617,6 +724,9 @@ unsigned ArrayType::getSizeOnStack() const
if (m_location == Location::CallData) if (m_location == Location::CallData)
// offset [length] (stack top) // offset [length] (stack top)
return 1 + (isDynamicallySized() ? 1 : 0); return 1 + (isDynamicallySized() ? 1 : 0);
else if (m_location == Location::Storage)
// storage_key storage_offset
return 2;
else else
// offset // offset
return 1; return 1;
@ -632,6 +742,23 @@ string ArrayType::toString() const
return ret + "]"; return ret + "]";
} }
TypePointer ArrayType::externalType() const
{
if (m_location != Location::CallData)
return TypePointer();
if (m_isByteArray)
return shared_from_this();
if (!m_baseType->externalType())
return TypePointer();
if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized())
return TypePointer();
if (isDynamicallySized())
return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType());
else
return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType(), m_length);
}
shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) const shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) const
{ {
auto copy = make_shared<ArrayType>(_location); auto copy = make_shared<ArrayType>(_location);
@ -645,7 +772,7 @@ shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location)
return copy; return copy;
} }
const MemberList ArrayType::s_arrayTypeMemberList = MemberList({{"length", make_shared<IntegerType>(256)}}); const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared<IntegerType>(256)}});
bool ContractType::operator==(Type const& _other) const bool ContractType::operator==(Type const& _other) const
{ {
@ -706,6 +833,26 @@ u256 ContractType::getFunctionIdentifier(string const& _functionName) const
return Invalid256; return Invalid256;
} }
vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::getStateVariables() const
{
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.getLinearizedBaseContracts()))
for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables())
if (!variable->isConstant())
variables.push_back(variable.get());
TypePointers types;
for (auto variable: variables)
types.push_back(variable->getType());
StorageOffsets offsets;
offsets.computeOffsets(types);
vector<tuple<VariableDeclaration const*, u256, unsigned>> variablesAndOffsets;
for (size_t index = 0; index < variables.size(); ++index)
if (auto const* offset = offsets.getOffset(index))
variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second));
return variablesAndOffsets;
}
TypePointer StructType::unaryOperatorResult(Token::Value _operator) const TypePointer StructType::unaryOperatorResult(Token::Value _operator) const
{ {
return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer(); return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer();
@ -721,12 +868,7 @@ bool StructType::operator==(Type const& _other) const
u256 StructType::getStorageSize() const u256 StructType::getStorageSize() const
{ {
bigint size = 0; return max<u256>(1, getMembers().getStorageSize());
for (pair<string, TypePointer> const& member: getMembers())
size += member.second->getStorageSize();
if (size >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage."));
return max<u256>(1, u256(size));
} }
bool StructType::canLiveOutsideStorage() const bool StructType::canLiveOutsideStorage() const
@ -755,17 +897,11 @@ MemberList const& StructType::getMembers() const
return *m_members; return *m_members;
} }
u256 StructType::getStorageOffsetOfMember(string const& _name) const pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const
{ {
//@todo cache member offset? auto const* offsets = getMembers().getMemberStorageOffset(_name);
u256 offset; solAssert(offsets, "Storage offset of non-existing member requested.");
for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers()) return *offsets;
{
if (variable->getName() == _name)
return offset;
offset += variable->getType()->getStorageSize();
}
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested."));
} }
TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
@ -781,6 +917,15 @@ bool EnumType::operator==(Type const& _other) const
return other.m_enum == m_enum; return other.m_enum == m_enum;
} }
unsigned EnumType::getStorageBytes() const
{
size_t elements = m_enum.getMembers().size();
if (elements <= 1)
return 1;
else
return dev::bytesRequired(elements - 1);
}
string EnumType::toString() const string EnumType::toString() const
{ {
return string("enum ") + m_enum.getName(); return string("enum ") + m_enum.getName();
@ -925,6 +1070,13 @@ string FunctionType::toString() const
return name + ")"; return name + ")";
} }
u256 FunctionType::getStorageSize() const
{
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_comment("Storage size of non-storable function type requested."));
}
unsigned FunctionType::getSizeOnStack() const unsigned FunctionType::getSizeOnStack() const
{ {
Location location = m_location; Location location = m_location;
@ -946,6 +1098,26 @@ unsigned FunctionType::getSizeOnStack() const
return size; return size;
} }
TypePointer FunctionType::externalType() const
{
TypePointers paramTypes;
TypePointers retParamTypes;
for (auto type: m_parameterTypes)
{
if (!type->externalType())
return TypePointer();
paramTypes.push_back(type->externalType());
}
for (auto type: m_returnParameterTypes)
{
if (!type->externalType())
return TypePointer();
retParamTypes.push_back(type->externalType());
}
return make_shared<FunctionType>(paramTypes, retParamTypes, m_location, m_arbitraryParameters);
}
MemberList const& FunctionType::getMembers() const MemberList const& FunctionType::getMembers() const
{ {
switch (m_location) switch (m_location)
@ -975,7 +1147,7 @@ MemberList const& FunctionType::getMembers() const
} }
} }
string FunctionType::getCanonicalSignature(std::string const& _name) const string FunctionType::externalSignature(std::string const& _name) const
{ {
std::string funcName = _name; std::string funcName = _name;
if (_name == "") if (_name == "")
@ -985,8 +1157,12 @@ string FunctionType::getCanonicalSignature(std::string const& _name) const
} }
string ret = funcName + "("; string ret = funcName + "(";
for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) TypePointers externalParameterTypes = dynamic_cast<FunctionType const&>(*externalType()).getParameterTypes();
ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ","); for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it)
{
solAssert(!!(*it), "Parameter should have external type");
ret += (*it)->toString() + (it + 1 == externalParameterTypes.cend() ? "" : ",");
}
return ret + ")"; return ret + ")";
} }
@ -1047,6 +1223,13 @@ string MappingType::toString() const
return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")"; return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")";
} }
u256 VoidType::getStorageSize() const
{
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_comment("Storage size of non-storable void type requested."));
}
bool TypeType::operator==(Type const& _other) const bool TypeType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -1055,6 +1238,13 @@ bool TypeType::operator==(Type const& _other) const
return *getActualType() == *other.getActualType(); return *getActualType() == *other.getActualType();
} }
u256 TypeType::getStorageSize() const
{
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_comment("Storage size of non-storable type type requested."));
}
MemberList const& TypeType::getMembers() const MemberList const& TypeType::getMembers() const
{ {
// We need to lazy-initialize it because of recursive references. // We need to lazy-initialize it because of recursive references.
@ -1092,6 +1282,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier)
swap(params, m_parameterTypes); swap(params, m_parameterTypes);
} }
u256 ModifierType::getStorageSize() const
{
BOOST_THROW_EXCEPTION(
InternalCompilerError()
<< errinfo_comment("Storage size of non-storable type type requested."));
}
bool ModifierType::operator==(Type const& _other) const bool ModifierType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -1122,22 +1319,29 @@ MagicType::MagicType(MagicType::Kind _kind):
switch (m_kind) switch (m_kind)
{ {
case Kind::Block: case Kind::Block:
m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = move(MemberList({
{"timestamp", make_shared<IntegerType>(256)}, {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)}, {"timestamp", make_shared<IntegerType>(256)},
{"difficulty", make_shared<IntegerType>(256)}, {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)},
{"number", make_shared<IntegerType>(256)}, {"difficulty", make_shared<IntegerType>(256)},
{"gaslimit", make_shared<IntegerType>(256)}}); {"number", make_shared<IntegerType>(256)},
{"gaslimit", make_shared<IntegerType>(256)}
}));
break; break;
case Kind::Message: case Kind::Message:
m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = move(MemberList({
{"gas", make_shared<IntegerType>(256)}, {"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"value", make_shared<IntegerType>(256)}, {"gas", make_shared<IntegerType>(256)},
{"data", make_shared<ArrayType>(ArrayType::Location::CallData)}}); {"value", make_shared<IntegerType>(256)},
{"data", make_shared<ArrayType>(ArrayType::Location::CallData)},
{"sig", make_shared<FixedBytesType>(4)}
}));
break; break;
case Kind::Transaction: case Kind::Transaction:
m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = move(MemberList({
{"gasprice", make_shared<IntegerType>(256)}}); {"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"gasprice", make_shared<IntegerType>(256)}
}));
break; break;
default: default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic."));

131
Types.h
View File

@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr<Type const>;
using FunctionTypePointer = std::shared_ptr<FunctionType const>; using FunctionTypePointer = std::shared_ptr<FunctionType const>;
using TypePointers = std::vector<TypePointer>; using TypePointers = std::vector<TypePointer>;
/**
* Helper class to compute storage offsets of members of structs and contracts.
*/
class StorageOffsets
{
public:
/// Resets the StorageOffsets objects and determines the position in storage for each
/// of the elements of @a _types.
void computeOffsets(TypePointers const& _types);
/// @returns the offset of the given member, might be null if the member is not part of storage.
std::pair<u256, unsigned> const* getOffset(size_t _index) const;
/// @returns the total number of slots occupied by all members.
u256 const& getStorageSize() const { return m_storageSize; }
private:
u256 m_storageSize;
std::map<size_t, std::pair<u256, unsigned>> m_offsets;
};
/** /**
* List of members of a type. * List of members of a type.
*/ */
@ -53,6 +73,7 @@ public:
MemberList() {} MemberList() {}
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
MemberList& operator=(MemberList&& _other);
TypePointer getMemberType(std::string const& _name) const TypePointer getMemberType(std::string const& _name) const
{ {
for (auto const& it: m_memberTypes) for (auto const& it: m_memberTypes)
@ -60,12 +81,18 @@ public:
return it.second; return it.second;
return TypePointer(); return TypePointer();
} }
/// @returns the offset of the given member in storage slots and bytes inside a slot or
/// a nullptr if the member is not part of storage.
std::pair<u256, unsigned> const* getMemberStorageOffset(std::string const& _name) const;
/// @returns the number of storage slots occupied by the members.
u256 const& getStorageSize() const;
MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } MemberMap::const_iterator begin() const { return m_memberTypes.begin(); }
MemberMap::const_iterator end() const { return m_memberTypes.end(); } MemberMap::const_iterator end() const { return m_memberTypes.end(); }
private: private:
MemberMap m_memberTypes; MemberMap m_memberTypes;
mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
}; };
/** /**
@ -77,12 +104,12 @@ public:
enum class Category enum class Category
{ {
Integer, IntegerConstant, Bool, Real, Array, Integer, IntegerConstant, Bool, Real, Array,
String, Contract, Struct, Function, OverloadedFunctions, Enum, FixedBytes, Contract, Struct, Function, OverloadedFunctions, Enum,
Mapping, Void, TypeType, Modifier, Magic Mapping, Void, TypeType, Modifier, Magic
}; };
///@{ /// @{
///@name Factory functions /// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type. /// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(Token::Value _typeToken); static TypePointer fromElementaryTypeName(Token::Value _typeToken);
static TypePointer fromElementaryTypeName(std::string const& _name); static TypePointer fromElementaryTypeName(std::string const& _name);
@ -97,6 +124,8 @@ public:
/// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
/// Calculates the
virtual Category getCategory() const = 0; virtual Category getCategory() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -126,9 +155,15 @@ public:
unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); }
/// @returns true if the type is dynamically encoded in calldata /// @returns true if the type is dynamically encoded in calldata
virtual bool isDynamicallySized() const { return false; } virtual bool isDynamicallySized() const { return false; }
/// @returns number of bytes required to hold this value in storage. /// @returns the number of storage slots required to hold this value in storage.
/// For dynamically "allocated" types, it returns the size of the statically allocated head, /// For dynamically "allocated" types, it returns the size of the statically allocated head,
virtual u256 getStorageSize() const { return 1; } virtual u256 getStorageSize() const { return 1; }
/// Multiple small types can be packed into a single storage slot. If such a packing is possible
/// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if
/// it does not fit.
/// In order to avoid computation at runtime of whether such moving is necessary, structs and
/// array data (not each element) always start a new slot.
virtual unsigned getStorageBytes() const { return 32; }
/// Returns true if the type can be stored in storage. /// Returns true if the type can be stored in storage.
virtual bool canBeStored() const { return true; } virtual bool canBeStored() const { return true; }
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
@ -152,20 +187,24 @@ public:
"for type without literals.")); "for type without literals."));
} }
/// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address.
/// If there is no such type, returns an empty shared pointer.
virtual TypePointer externalType() const { return TypePointer(); }
protected: protected:
/// Convenience object used when returning an empty member list. /// Convenience object used when returning an empty member list.
static const MemberList EmptyMemberList; static const MemberList EmptyMemberList;
}; };
/** /**
* Any kind of integer type including hash and address. * Any kind of integer type (signed, unsigned, address).
*/ */
class IntegerType: public Type class IntegerType: public Type
{ {
public: public:
enum class Modifier enum class Modifier
{ {
Unsigned, Signed, Hash, Address Unsigned, Signed, Address
}; };
virtual Category getCategory() const override { return Category::Integer; } virtual Category getCategory() const override { return Category::Integer; }
@ -179,14 +218,16 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; }
virtual unsigned getStorageBytes() const override { return m_bits / 8; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; }
virtual std::string toString() const override; virtual std::string toString() const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBits() const { return m_bits; } int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::Hash || m_modifier == Modifier::Address; }
bool isAddress() const { return m_modifier == Modifier::Address; } bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; } bool isSigned() const { return m_modifier == Modifier::Signed; }
@ -232,28 +273,32 @@ private:
}; };
/** /**
* String type with fixed length, up to 32 bytes. * Bytes type with fixed length of up to 32 bytes.
*/ */
class StaticStringType: public Type class FixedBytesType: public Type
{ {
public: public:
virtual Category getCategory() const override { return Category::String; } virtual Category getCategory() const override { return Category::FixedBytes; }
/// @returns the smallest string type for the given literal or an empty pointer /// @returns the smallest bytes type for the given literal or an empty pointer
/// if no type fits. /// if no type fits.
static std::shared_ptr<StaticStringType> smallestTypeForLiteral(std::string const& _literal); static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal);
explicit StaticStringType(int _bytes); explicit FixedBytesType(int _bytes);
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
virtual unsigned getStorageBytes() const override { return m_bytes; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); }
virtual u256 literalValue(Literal const* _literal) const override; virtual u256 literalValue(Literal const* _literal) const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBytes() const { return m_bytes; } int getNumBytes() const { return m_bytes; }
@ -273,16 +318,21 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; } virtual unsigned getCalldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
virtual unsigned getStorageBytes() const override { return 1; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "bool"; } virtual std::string toString() const override { return "bool"; }
virtual u256 literalValue(Literal const* _literal) const override; virtual u256 literalValue(Literal const* _literal) const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
}; };
/** /**
* The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>]) * The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>])
* and dynamically-sized array (<type>[]). * and dynamically-sized array (<type>[]).
* In storage, all arrays are packed tightly (as long as more than one elementary type fits in
* one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and
* thus start on their own slot.
*/ */
class ArrayType: public Type class ArrayType: public Type
{ {
@ -293,13 +343,22 @@ public:
/// Constructor for a byte array ("bytes") /// Constructor for a byte array ("bytes")
explicit ArrayType(Location _location): explicit ArrayType(Location _location):
m_location(_location), m_isByteArray(true), m_baseType(std::make_shared<IntegerType>(8)) {} m_location(_location),
m_isByteArray(true),
m_baseType(std::make_shared<FixedBytesType>(8))
{}
/// Constructor for a dynamically sized array type ("type[]") /// Constructor for a dynamically sized array type ("type[]")
ArrayType(Location _location, const TypePointer &_baseType): ArrayType(Location _location, const TypePointer &_baseType):
m_location(_location), m_baseType(_baseType) {} m_location(_location),
m_baseType(_baseType)
{}
/// Constructor for a fixed-size array type ("type[20]") /// Constructor for a fixed-size array type ("type[20]")
ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length):
m_location(_location), m_baseType(_baseType), m_hasDynamicLength(false), m_length(_length) {} m_location(_location),
m_baseType(_baseType),
m_hasDynamicLength(false),
m_length(_length)
{}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@ -310,6 +369,7 @@ public:
virtual unsigned getSizeOnStack() const override; virtual unsigned getSizeOnStack() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; }
virtual TypePointer externalType() const override;
Location getLocation() const { return m_location; } Location getLocation() const { return m_location; }
bool isByteArray() const { return m_isByteArray; } bool isByteArray() const { return m_isByteArray; }
@ -344,10 +404,12 @@ public:
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual unsigned getStorageBytes() const override { return 20; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual std::string toString() const override; virtual std::string toString() const override;
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); }
bool isSuper() const { return m_super; } bool isSuper() const { return m_super; }
ContractDefinition const& getContractDefinition() const { return m_contract; } ContractDefinition const& getContractDefinition() const { return m_contract; }
@ -360,6 +422,10 @@ public:
/// not exist. /// not exist.
u256 getFunctionIdentifier(std::string const& _functionName) const; u256 getFunctionIdentifier(std::string const& _functionName) const;
/// @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>> getStateVariables() const;
private: private:
ContractDefinition const& m_contract; ContractDefinition const& m_contract;
/// If true, it is the "super" type of the current contract, i.e. it contains only inherited /// If true, it is the "super" type of the current contract, i.e. it contains only inherited
@ -383,12 +449,12 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual u256 getStorageSize() const override; virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override; virtual bool canLiveOutsideStorage() const override;
virtual unsigned getSizeOnStack() const override { return 1; /*@todo*/ } virtual unsigned getSizeOnStack() const override { return 2; }
virtual std::string toString() const override; virtual std::string toString() const override;
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
u256 getStorageOffsetOfMember(std::string const& _name) const; std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const;
private: private:
StructDefinition const& m_struct; StructDefinition const& m_struct;
@ -407,10 +473,12 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual unsigned getSizeOnStack() const override { return 1; } virtual unsigned getSizeOnStack() const override { return 1; }
virtual unsigned getStorageBytes() const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(8 * int(getStorageBytes())); }
EnumDefinition const& getEnumDefinition() const { return m_enum; } EnumDefinition const& getEnumDefinition() const { return m_enum; }
/// @returns the value that the string has in the Enum /// @returns the value that the string has in the Enum
@ -443,6 +511,11 @@ public:
Bare }; Bare };
virtual Category getCategory() const override { return Category::Function; } virtual Category getCategory() const override { return Category::Function; }
/// @returns TypePointer of a new FunctionType object. All input/return parameters are an appropriate external types of input/return parameters of current function.
/// Returns an empty shared pointer if one of the input/return parameters does not have an externaltype.
virtual TypePointer externalType() const override;
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true);
explicit FunctionType(VariableDeclaration const& _varDecl); explicit FunctionType(VariableDeclaration const& _varDecl);
explicit FunctionType(EventDefinition const& _event); explicit FunctionType(EventDefinition const& _event);
@ -476,16 +549,16 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override; virtual unsigned getSizeOnStack() const override;
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
Location const& getLocation() const { return m_location; } Location const& getLocation() const { return m_location; }
/// @returns the canonical signature of this function type given the function name /// @returns the external signature of this function type given the function name
/// If @a _name is not provided (empty string) then the @c m_declaration member of the /// If @a _name is not provided (empty string) then the @c m_declaration member of the
/// function type is used /// function type is used
std::string getCanonicalSignature(std::string const& _name = "") const; std::string externalSignature(std::string const& _name = "") const;
Declaration const& getDeclaration() const Declaration const& getDeclaration() const
{ {
solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); solAssert(m_declaration, "Requested declaration from a FunctionType that has none");
@ -515,7 +588,7 @@ private:
std::vector<std::string> m_parameterNames; std::vector<std::string> m_parameterNames;
std::vector<std::string> m_returnParameterNames; std::vector<std::string> m_returnParameterNames;
Location const m_location; Location const m_location;
/// true iff the function takes an arbitrary number of arguments of arbitrary types /// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false; bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
@ -540,6 +613,7 @@ private:
/** /**
* The type of a mapping, there is one distinct type per key/value type pair. * The type of a mapping, there is one distinct type per key/value type pair.
* Mappings always occupy their own storage slot, but do not actually use it.
*/ */
class MappingType: public Type class MappingType: public Type
{ {
@ -550,6 +624,7 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override; virtual std::string toString() const override;
virtual unsigned getSizeOnStack() const override { return 2; }
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
TypePointer const& getKeyType() const { return m_keyType; } TypePointer const& getKeyType() const { return m_keyType; }
@ -573,7 +648,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual std::string toString() const override { return "void"; } virtual std::string toString() const override { return "void"; }
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; } virtual unsigned getSizeOnStack() const override { return 0; }
}; };
@ -593,7 +668,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; } virtual unsigned getSizeOnStack() const override { return 0; }
virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; }
@ -619,7 +694,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; } virtual unsigned getSizeOnStack() const override { return 0; }
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;

29
Utils.h
View File

@ -22,34 +22,9 @@
#pragma once #pragma once
#include <string> #include <libdevcore/Assertions.h>
#include <libsolidity/Exceptions.h>
namespace dev
{
namespace solidity
{
/// Assertion that throws an InternalCompilerError containing the given description if it is not met. /// Assertion that throws an InternalCompilerError containing the given description if it is not met.
#define solAssert(CONDITION, DESCRIPTION) \ #define solAssert(CONDITION, DESCRIPTION) \
::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC) assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION)
inline void solAssertAux(bool _condition, std::string const& _errorDescription, unsigned _line,
char const* _file, char const* _function)
{
if (!_condition)
::boost::throw_exception( InternalCompilerError()
<< errinfo_comment(_errorDescription)
<< ::boost::throw_function(_function)
<< ::boost::throw_file(_file)
<< ::boost::throw_line(_line));
}
inline void solAssertAux(void const* _pointer, std::string const& _errorDescription, unsigned _line,
char const* _file, char const* _function)
{
solAssertAux(_pointer != nullptr, _errorDescription, _line, _file, _function);
}
}
}