diff options
author | chriseth <c@ethdev.com> | 2015-04-07 23:08:49 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-04-07 23:08:49 +0800 |
commit | 158795e48f4285d713b11f78cdd04de8c6d1f667 (patch) | |
tree | a4194b4dfbac5bcbed9dbd6290e156c945fff56b | |
parent | ff4d2cc7dc035a248de4698c2c59a8df14db2e82 (diff) | |
parent | 8e19eea7d5d6cd4bffa03f8617023a74268de608 (diff) | |
download | dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.gz dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.bz2 dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.lz dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.xz dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.zst dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.zip |
Merge remote-tracking branch 'ethereum/develop' into sol_overloadingFunctions
-rw-r--r-- | AST.cpp | 81 | ||||
-rw-r--r-- | AST.h | 135 | ||||
-rw-r--r-- | AST_accept.h | 6 | ||||
-rw-r--r-- | ArrayUtils.cpp | 340 | ||||
-rw-r--r-- | ArrayUtils.h | 24 | ||||
-rw-r--r-- | Compiler.cpp | 46 | ||||
-rw-r--r-- | Compiler.h | 4 | ||||
-rw-r--r-- | CompilerContext.cpp | 59 | ||||
-rw-r--r-- | CompilerContext.h | 31 | ||||
-rw-r--r-- | CompilerStack.cpp | 10 | ||||
-rw-r--r-- | CompilerUtils.cpp | 10 | ||||
-rw-r--r-- | ExpressionCompiler.cpp | 259 | ||||
-rw-r--r-- | ExpressionCompiler.h | 2 | ||||
-rw-r--r-- | GlobalContext.cpp | 19 | ||||
-rw-r--r-- | InterfaceHandler.cpp | 14 | ||||
-rw-r--r-- | LValue.cpp | 186 | ||||
-rw-r--r-- | LValue.h | 10 | ||||
-rw-r--r-- | Parser.cpp | 42 | ||||
-rw-r--r-- | Parser.h | 5 | ||||
-rw-r--r-- | Token.h | 117 | ||||
-rw-r--r-- | Types.cpp | 406 | ||||
-rw-r--r-- | Types.h | 131 | ||||
-rw-r--r-- | Utils.h | 29 |
23 files changed, 1312 insertions, 654 deletions
@@ -21,6 +21,7 @@ */ #include <algorithm> +#include <boost/range/adaptor/reversed.hpp> #include <libsolidity/Utils.h> #include <libsolidity/AST.h> #include <libsolidity/ASTVisitor.h> @@ -52,6 +53,7 @@ void ContractDefinition::checkTypeRequirements() baseSpecifier->checkTypeRequirements(); checkIllegalOverrides(); + checkAbstractFunctions(); FunctionDefinition const* constructor = getConstructor(); if (constructor && !constructor->getReturnParameters().empty()) @@ -60,6 +62,7 @@ void ContractDefinition::checkTypeRequirements() FunctionDefinition const* fallbackFunction = nullptr; for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions()) + { if (function->getName().empty()) { if (fallbackFunction) @@ -71,6 +74,9 @@ void ContractDefinition::checkTypeRequirements() BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters.")); } } + if (!function->isFullyImplemented()) + setFullyImplemented(false); + } for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers()) modifier->checkTypeRequirements(); @@ -98,7 +104,7 @@ void ContractDefinition::checkTypeRequirements() if (hashes.count(hash)) BOOST_THROW_EXCEPTION(createTypeError( std::string("Function signature hash collision for ") + - it.second->getCanonicalSignature())); + it.second->externalSignature())); hashes.insert(hash); } } @@ -134,6 +140,28 @@ FunctionDefinition const* ContractDefinition::getFallbackFunction() const 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 { // 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()) { - string functionSignature = f->getCanonicalSignature(); - if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && signaturesSeen.count(functionSignature) == 0) + string functionSignature = f->externalSignature(); + if (f->isPartOfExternalInterface() && signaturesSeen.count(functionSignature) == 0) { functionsSeen.insert(f->getName()); signaturesSeen.insert(functionSignature); @@ -214,11 +242,12 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn } 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); + solAssert(v->getType().get(), ""); 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))); } } @@ -322,25 +351,35 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const void FunctionDefinition::checkTypeRequirements() { for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters()) + { if (!var->getType()->canLiveOutsideStorage()) 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) modifier->checkTypeRequirements(isConstructor() ? dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() : vector<ASTPointer<InheritanceSpecifier>>()); - - m_body->checkTypeRequirements(); + if (m_body) + 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 { - // External function parameters are Read-Only - return !isExternalFunctionParameter(); + // External function parameters and constant declared variables are Read-Only + return !isExternalFunctionParameter() && !m_isConstant; } void VariableDeclaration::checkTypeRequirements() @@ -349,10 +388,24 @@ void VariableDeclaration::checkTypeRequirements() // sets the type. // Note that assignments before the first declaration are legal because of the special scoping // 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) return; if (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 { // no type declared and no previous assignment, infer the type @@ -437,6 +490,8 @@ void EventDefinition::checkTypeRequirements() numIndexed++; if (!var->getType()->canLiveOutsideStorage()) 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) 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()); if (!m_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); TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes(); m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType}, @@ -720,7 +777,7 @@ void IndexAccess::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); m_index->expectType(IntegerType(256)); if (type.isByteArray()) - m_type = make_shared<IntegerType>(8, IntegerType::Modifier::Hash); + m_type = make_shared<FixedBytesType>(1); else m_type = type.getBaseType(); m_isLValue = type.getLocation() != ArrayType::Location::CallData; @@ -156,6 +156,7 @@ public: /// contract types. virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0; virtual bool isLValue() const { return false; } + virtual bool isPartOfExternalInterface() const { return false; }; protected: virtual Visibility getDefaultVisibility() const { return Visibility::Public; } @@ -195,6 +196,22 @@ protected: 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 * finally all function declarations. */ -class ContractDefinition: public Declaration, public Documented +class ContractDefinition: public Declaration, public Documented, public ImplementationOptional { public: - ContractDefinition(SourceLocation const& _location, - ASTPointer<ASTString> const& _name, - ASTPointer<ASTString> const& _documentation, - std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, - std::vector<ASTPointer<StructDefinition>> const& _definedStructs, - std::vector<ASTPointer<EnumDefinition>> const& _definedEnums, - std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables, - std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions, - std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers, - std::vector<ASTPointer<EventDefinition>> const& _events): - Declaration(_location, _name), Documented(_documentation), + ContractDefinition( + SourceLocation const& _location, + ASTPointer<ASTString> const& _name, + ASTPointer<ASTString> const& _documentation, + std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, + std::vector<ASTPointer<StructDefinition>> const& _definedStructs, + std::vector<ASTPointer<EnumDefinition>> const& _definedEnums, + std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables, + std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions, + std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers, + std::vector<ASTPointer<EventDefinition>> const& _events + ): + Declaration(_location, _name), + Documented(_documentation), + ImplementationOptional(true), m_baseContracts(_baseContracts), m_definedStructs(_definedStructs), m_definedEnums(_definedEnums), @@ -262,6 +283,7 @@ public: private: void checkIllegalOverrides() const; + void checkAbstractFunctions(); std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const; @@ -377,24 +399,29 @@ private: 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: - FunctionDefinition(SourceLocation const& _location, ASTPointer<ASTString> const& _name, - Declaration::Visibility _visibility, bool _isConstructor, - ASTPointer<ASTString> const& _documentation, - ASTPointer<ParameterList> const& _parameters, - bool _isDeclaredConst, - std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, - ASTPointer<ParameterList> const& _returnParameters, - ASTPointer<Block> const& _body): - Declaration(_location, _name, _visibility), Documented(_documentation), - m_isConstructor(_isConstructor), - m_parameters(_parameters), - m_isDeclaredConst(_isDeclaredConst), - m_functionModifiers(_modifiers), - m_returnParameters(_returnParameters), - m_body(_body) + FunctionDefinition( + SourceLocation const& _location, + ASTPointer<ASTString> const& _name, + Declaration::Visibility _visibility, bool _isConstructor, + ASTPointer<ASTString> const& _documentation, + ASTPointer<ParameterList> const& _parameters, + bool _isDeclaredConst, + std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, + ASTPointer<ParameterList> const& _returnParameters, + ASTPointer<Block> const& _body + ): + Declaration(_location, _name, _visibility), + Documented(_documentation), + ImplementationOptional(_body != nullptr), + m_isConstructor(_isConstructor), + m_parameters(_parameters), + m_isDeclaredConst(_isDeclaredConst), + m_functionModifiers(_modifiers), + m_returnParameters(_returnParameters), + m_body(_body) {} virtual void accept(ASTVisitor& _visitor) override; @@ -415,14 +442,15 @@ public: getVisibility() >= Visibility::Internal; } 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. 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 /// arguments separated by commas all enclosed in parentheses without any spaces. - std::string getCanonicalSignature() const; + std::string externalSignature() const; private: bool m_isConstructor; @@ -440,13 +468,23 @@ private: class VariableDeclaration: public Declaration { public: - VariableDeclaration(SourceLocation const& _location, ASTPointer<TypeName> const& _type, - ASTPointer<ASTString> const& _name, ASTPointer<Expression> _value, - Visibility _visibility, - bool _isStateVar = false, bool _isIndexed = false): - Declaration(_location, _name, _visibility), - m_typeName(_type), m_value(_value), - m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {} + VariableDeclaration( + SourceLocation const& _location, + ASTPointer<TypeName> const& _type, + ASTPointer<ASTString> const& _name, + ASTPointer<Expression> _value, + Visibility _visibility, + 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(ASTConstVisitor& _visitor) const override; @@ -459,21 +497,24 @@ public: void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; } virtual bool isLValue() const override; + virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstant; } void checkTypeRequirements(); bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); } bool isExternalFunctionParameter() const; bool isStateVariable() const { return m_isStateVariable; } bool isIndexed() const { return m_isIndexed; } + bool isConstant() const { return m_isConstant; } protected: Visibility getDefaultVisibility() const override { return Visibility::Internal; } private: 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_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 }; @@ -538,17 +579,24 @@ private: class EventDefinition: public Declaration, public VariableScope, public Documented { public: - EventDefinition(SourceLocation const& _location, - ASTPointer<ASTString> const& _name, - ASTPointer<ASTString> const& _documentation, - ASTPointer<ParameterList> const& _parameters): - Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {} + EventDefinition( + SourceLocation const& _location, + ASTPointer<ASTString> const& _name, + ASTPointer<ASTString> const& _documentation, + 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(ASTConstVisitor& _visitor) const override; std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } ParameterList const& getParameterList() const { return *m_parameters; } + bool isAnonymous() const { return m_anonymous; } virtual TypePointer getType(ContractDefinition const* = nullptr) const override { @@ -559,6 +607,7 @@ public: private: ASTPointer<ParameterList> m_parameters; + bool m_anonymous = false; }; /** diff --git a/AST_accept.h b/AST_accept.h index 81ede8fc..3557f877 100644 --- a/AST_accept.h +++ b/AST_accept.h @@ -175,7 +175,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) if (m_returnParameters) m_returnParameters->accept(_visitor); listAccept(m_functionModifiers, _visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } @@ -188,7 +189,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const if (m_returnParameters) m_returnParameters->accept(_visitor); listAccept(m_functionModifiers, _visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } diff --git a/ArrayUtils.cpp b/ArrayUtils.cpp index f0d7d6a8..58031390 100644 --- a/ArrayUtils.cpp +++ b/ArrayUtils.cpp @@ -34,8 +34,10 @@ using namespace solidity; void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const { - // stack layout: [source_ref] target_ref (top) - // need to leave target_ref on the stack at the end + // this copies source to target and also clears target if it was larger + // 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( _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* 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 - // 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 + // arrays always start at zero byte offset, pop offset + m_context << eth::Instruction::POP; for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --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()) retrieveLength(_sourceType); // otherwise, length is already there // stack: target_ref source_ref source_length @@ -73,6 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + m_context << u256(0); return; } // 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 // skip copying if source length is zero m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); + eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) CompilerUtils(m_context).computeHashStatic(); @@ -98,88 +113,172 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons convertLengthToSize(_sourceType); m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; // 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(); m_context << copyLoopStart; // check for loop condition m_context - << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize) << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); 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 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( dynamic_cast<ArrayType const&>(*targetBaseType), 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 { - 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 (haveByteOffsetSource) + m_context << eth::Instruction::DUP2; + else + m_context << u256(0); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } else if (sourceBaseType->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); else 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); } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // increment source - m_context - << eth::Instruction::SWAP2 - << (_sourceType.getLocation() == ArrayType::Location::Storage ? - sourceBaseType->getStorageSize() : - sourceBaseType->getCalldataEncodedSize()) - << eth::Instruction::ADD - << eth::Instruction::SWAP2; + if (haveByteOffsetSource) + incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4); + else + m_context + << eth::swapInstruction(2 + byteOffsetSize) + << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) + << eth::Instruction::ADD + << eth::swapInstruction(2 + byteOffsetSize); // increment target - m_context - << eth::Instruction::SWAP1 - << targetBaseType->getStorageSize() - << eth::Instruction::ADD - << eth::Instruction::SWAP1; + if (haveByteOffsetTarget) + incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + m_context + << eth::swapInstruction(1 + byteOffsetSize) + << targetBaseType->getStorageSize() + << eth::Instruction::ADD + << eth::swapInstruction(1 + byteOffsetSize); m_context.appendJumpTo(copyLoopStart); 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 - // 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; // stack: target_ref target_data_end target_data_pos_updated clearStorageLoop(*targetBaseType); m_context << eth::Instruction::POP; + m_context << u256(0); } void ArrayUtils::clearArray(ArrayType const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); 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()) clearDynamicArray(_type); else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) 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) { + m_context << u256(0); 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); } else { - solAssert(!_type.isByteArray(), ""); - m_context - << eth::Instruction::DUP1 << u256(_type.getLength()) - << u256(_type.getBaseType()->getStorageSize()) - << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; - clearStorageLoop(*_type.getBaseType()); + m_context << eth::Instruction::DUP1 << _type.getLength(); + convertLengthToSize(_type); + m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; + if (_type.getBaseType()->getStorageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); m_context << eth::Instruction::POP; } + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } 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.isDynamicallySized(), ""); + unsigned stackHeightStart = m_context.getStackHeight(); // fetch length m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // set length to zero @@ -196,23 +296,27 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const // compute data positions m_context << eth::Instruction::SWAP1; 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 << eth::Instruction::SWAP1; // stack: data_pos_end data_pos - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); // cleanup m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const { solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); 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(); // 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 m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; // stack: ref new_length delete_end delete_start - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); @@ -248,10 +352,12 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const m_context << resizeEnd; // cleanup m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } void ArrayUtils::clearStorageLoop(Type const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); if (_type.getCategory() == Type::Category::Mapping) { m_context << eth::Instruction::POP; @@ -266,13 +372,16 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const eth::AssemblyItem zeroLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(zeroLoopEnd); // delete + m_context << u256(0); StorageItem(m_context, _type).setToZero(SourceLocation(), false); + m_context << eth::Instruction::POP; // increment m_context << u256(1) << eth::Instruction::ADD; m_context.appendJumpTo(loopStart); // cleanup m_context << zeroLoopEnd; m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } 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()) m_context << u256(31) << eth::Instruction::ADD << 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; } 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; +} diff --git a/ArrayUtils.h b/ArrayUtils.h index 31cca817..dab40e2d 100644 --- a/ArrayUtils.h +++ b/ArrayUtils.h @@ -41,19 +41,19 @@ public: /// Copies an array to an array in storage. The arrays can be of different types only if /// their storage representation is the same. - /// Stack pre: [source_reference] target_reference - /// Stack post: target_reference + /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset + /// Stack post: target_reference target_byte_offset void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; /// Clears the given dynamic or static array. - /// Stack pre: reference + /// Stack pre: storage_ref storage_byte_offset /// Stack post: void clearArray(ArrayType const& _type) const; /// Clears the length and data elements of the array referenced on the stack. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset) /// Stack post: void clearDynamicArray(ArrayType const& _type) const; /// 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: void resizeDynamicArray(ArrayType const& _type) const; /// 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; /// Retrieves the length (number of elements) of the array ref on the stack. This also /// works for statically-sized arrays. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset for dynamic storage arrays) /// Stack post: reference length 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: + /// 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; }; diff --git a/Compiler.cpp b/Compiler.cpp index 5eeb0c3e..8e263449 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -20,12 +20,12 @@ * Solidity compiler. */ +#include <libsolidity/Compiler.h> #include <algorithm> #include <boost/range/adaptor/reversed.hpp> #include <libevmcore/Instruction.h> #include <libevmcore/Assembly.h> #include <libsolidity/AST.h> -#include <libsolidity/Compiler.h> #include <libsolidity/ExpressionCompiler.h> #include <libsolidity/CompilerUtils.h> @@ -132,7 +132,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) { - CompilerContext::LocationSetter locationSetter(m_context, &_constructor); + CompilerContext::LocationSetter locationSetter(m_context, _constructor); FunctionType constructorType(_constructor); if (!constructorType.getParameterTypes().empty()) { @@ -146,7 +146,7 @@ void Compiler::appendBaseConstructor(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 unsigned argumentSize = 0; for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters()) @@ -192,10 +192,12 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) for (auto const& it: interfaceFunctions) { FunctionTypePointer const& functionType = it.second; + solAssert(functionType->hasDeclaration(), ""); + CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration()); m_context << callDataUnpackerEntryPoints.at(it.first); eth::AssemblyItem returnTag = m_context.pushNewTag(); appendCalldataUnpacker(functionType->getParameterTypes()); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration())); m_context << returnTag; 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 unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; + solAssert(stackDepth <= 16, "Stack too deep."); m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true); m_context << eth::Instruction::ADD; @@ -271,22 +274,21 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) void Compiler::registerStateVariables(ContractDefinition const& _contract) { - for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) - for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables()) - m_context.addStateVariable(*variable); + for (auto const& var: ContractType(_contract).getStateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); } void Compiler::initializeStateVariables(ContractDefinition const& _contract) { for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) - if (variable->getValue()) + if (variable->getValue() && !variable->isConstant()) ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); } bool Compiler::visit(VariableDeclaration const& _variableDeclaration) { 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_breakTags.clear(); @@ -300,7 +302,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration) 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 // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // 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 += vector<int>(c_localVariablesSize, -1); + solAssert(stackLayout.size() <= 17, "Stack too deep."); while (stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) { @@ -376,15 +379,16 @@ bool Compiler::visit(FunctionDefinition const& _function) m_context.removeVariable(*localVariable); m_context.adjustStackOffset(-(int)c_returnValuesSize); + if (!_function.isConstructor()) - m_context << eth::Instruction::JUMP; + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); return false; } bool Compiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_ifStatement); + CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); compileExpression(_ifStatement.getCondition()); eth::AssemblyItem trueTag = m_context.appendConditionalJump(); if (_ifStatement.getFalseStatement()) @@ -401,7 +405,7 @@ bool Compiler::visit(IfStatement const& _ifStatement) bool Compiler::visit(WhileStatement const& _whileStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_whileStatement); + CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -427,7 +431,7 @@ bool Compiler::visit(WhileStatement const& _whileStatement) bool Compiler::visit(ForStatement const& _forStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_forStatement); + CompilerContext::LocationSetter locationSetter(m_context, _forStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -464,7 +468,7 @@ bool Compiler::visit(ForStatement const& _forStatement) bool Compiler::visit(Continue const& _continueStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_continueStatement); + CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); if (!m_continueTags.empty()) m_context.appendJumpTo(m_continueTags.back()); return false; @@ -472,7 +476,7 @@ bool Compiler::visit(Continue const& _continueStatement) bool Compiler::visit(Break const& _breakStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_breakStatement); + CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); if (!m_breakTags.empty()) m_context.appendJumpTo(m_breakTags.back()); return false; @@ -480,7 +484,7 @@ bool Compiler::visit(Break const& _breakStatement) 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 if (Expression const* expression = _return.getExpression()) { @@ -499,7 +503,7 @@ bool Compiler::visit(Return const& _return) bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); if (Expression const* expression = _variableDeclarationStatement.getExpression()) { compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); @@ -512,7 +516,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta bool Compiler::visit(ExpressionStatement const& _expressionStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_expressionStatement); + CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); Expression const& expression = _expressionStatement.getExpression(); compileExpression(expression); CompilerUtils(m_context).popStackElement(*expression.getType()); @@ -523,7 +527,7 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement) bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_placeholderStatement); + CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); ++m_modifierDepth; appendModifierOrFunctionCode(); --m_modifierDepth; @@ -550,7 +554,7 @@ void Compiler::appendModifierOrFunctionCode() } 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(), ""); for (unsigned i = 0; i < modifier.getParameters().size(); ++i) { @@ -94,8 +94,8 @@ private: 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 unsigned m_modifierDepth = 0; - FunctionDefinition const* m_currentFunction; - unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag + FunctionDefinition const* m_currentFunction = nullptr; + 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 std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments; }; diff --git a/CompilerContext.cpp b/CompilerContext.cpp index b12e0192..0afda136 100644 --- a/CompilerContext.cpp +++ b/CompilerContext.cpp @@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio 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; - 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); + m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); } void CompilerContext::startFunction(Declaration const& _function) @@ -69,7 +67,7 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) { - LocationSetter locationSetter(*this, &_declaration); + LocationSetter locationSetter(*this, _declaration); addVariable(_declaration); int const size = _declaration.getType()->getSizeOnStack(); for (int i = 0; i < size; ++i) @@ -174,46 +172,26 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const 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); solAssert(it != m_stateVariables.end(), "Variable not found in storage."); 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) { stack<ASTNode const*> newStack; newStack.push(_node); std::swap(m_visitedNodes, newStack); -} - -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; + updateSourceLocation(); } vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const @@ -224,5 +202,10 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContr return ++it; } +void CompilerContext::updateSourceLocation() +{ + m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation()); +} + } } diff --git a/CompilerContext.h b/CompilerContext.h index e42e7c76..87f90d4c 100644 --- a/CompilerContext.h +++ b/CompilerContext.h @@ -24,6 +24,7 @@ #include <ostream> #include <stack> +#include <utility> #include <libevmcore/Instruction.h> #include <libevmcore/Assembly.h> #include <libsolidity/ASTForward.h> @@ -42,7 +43,7 @@ class CompilerContext { public: 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 removeVariable(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 /// with baseToCurrentStackOffset to point to the same stack element. 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 eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } @@ -91,7 +92,7 @@ public: /// Appends a JUMP to a new tag and @returns the tag eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } /// 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 CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } /// 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 void resetVisitedNodes(ASTNode const* _node); /// 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 - 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. - CompilerContext& operator<<(eth::AssemblyItem const& _item); - CompilerContext& operator<<(eth::Instruction _instruction); - CompilerContext& operator<<(u256 const& _value); - CompilerContext& operator<<(bytes const& _data); + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } + CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } eth::Assembly const& getAssembly() const { return m_asm; } /// @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(); } @@ -130,22 +131,22 @@ public: class LocationSetter: public ScopeGuard { public: - LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): - ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } + LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): + ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } }; private: std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const; + /// Updates source location set in the assembly. + void updateSourceLocation(); eth::Assembly m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set<Declaration const*> m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. 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 - 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). std::map<Declaration const*, unsigned> m_localVariables; /// Labels pointing to the entry points of functions. diff --git a/CompilerStack.cpp b/CompilerStack.cpp index a878bb61..1301bfa5 100644 --- a/CompilerStack.cpp +++ b/CompilerStack.cpp @@ -41,14 +41,14 @@ namespace solidity { 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){}})"}, - {"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;}})"}, {"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); }})"}, - {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 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){}})"}, + {"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(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;})"}, {"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";)"} @@ -138,6 +138,8 @@ void CompilerStack::compile(bool _optimize) for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { + if (!contract->isFullyImplemented()) + continue; shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize); compiler->compileContract(*contract, contractBytecode); Contract& compiledContract = m_contracts[contract->getName()]; diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index 8a26b5d1..511254fa 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -93,6 +93,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound else { 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; // stack here: memory_offset storage_offset length_bytes // 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 size = _variable.getType()->getSizeOnStack(); + solAssert(stackPosition >= size, "Variable size and position mismatch."); // move variable starting from its top end in the stack if (stackPosition - size + 1 > 16) 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) { - if (_stackDepth > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Stack too deep.")); + solAssert(_stackDepth <= 16, "Stack too deep."); for (unsigned i = 0; i < _itemSize; ++i) 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 numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.getCategory() == Type::Category::String; + bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP << u256(0); else @@ -198,11 +199,10 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } - unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.getCategory() == Type::Category::String; + bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP; else diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index 4be461b2..3ca8de89 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -48,7 +48,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c if (!_varDecl.getValue()) return; solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.getValue()->accept(*this); appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); @@ -57,7 +57,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) { - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); FunctionType accessorType(_varDecl); unsigned length = 0; @@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); // retrieve the position of the variable - m_context << m_context.getStorageLocationOfVariable(_varDecl); - TypePointer returnType = _varDecl.getType(); + auto const& location = m_context.getStorageLocationOfVariable(_varDecl); + m_context << location.first; + TypePointer returnType = _varDecl.getType(); for (TypePointer const& paramType: paramTypes) { // move offset to memory @@ -90,9 +91,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // struct for (size_t i = 0; i < names.size(); ++i) { - m_context << eth::Instruction::DUP1 - << structType->getStorageOffsetOfMember(names[i]) - << eth::Instruction::ADD; + if (types[i]->getCategory() == Type::Category::Mapping) + continue; + 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); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); m_context << eth::Instruction::SWAP1; @@ -104,11 +106,13 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_context << u256(location.second); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } 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) @@ -122,23 +126,25 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con Type::Category stackTypeCategory = _typeOnStack.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) { - // 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 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; + if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) + appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); } else { - // clear lower-order bytes for conversion to shorter strings - we always clean - solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); - StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); + // clear lower-order bytes for conversion to shorter bytes - we always clean + solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType); if (targetType.getNumBytes() < typeOnStack.getNumBytes()) { if (targetType.getNumBytes() == 0) @@ -150,22 +156,24 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con } } } - else if (stackTypeCategory == Type::Category::Enum) - solAssert(targetTypeCategory == Type::Category::Integer || - targetTypeCategory == Type::Category::Enum, ""); - else if (stackTypeCategory == Type::Category::Integer || - stackTypeCategory == Type::Category::Contract || - stackTypeCategory == Type::Category::IntegerConstant) - { - if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) + break; + case Type::Category::Enum: + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + break; + case Type::Category::Integer: + case Type::Category::Contract: + case Type::Category::IntegerConstant: + 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 - StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); - IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); - solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); - solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); - m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType); + if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) + if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) + appendHighBitsCleanup(*typeOnStack); + m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; } else if (targetTypeCategory == Type::Category::Enum) // just clean @@ -175,7 +183,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); IntegerType addressType(0, IntegerType::Modifier::Address); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast<IntegerType const&>(_targetType) : addressType; + ? dynamic_cast<IntegerType const&>(_targetType) : addressType; if (stackTypeCategory == Type::Category::IntegerConstant) { IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); @@ -187,7 +195,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con else { 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 // Non-widening and force: clean up according to target type bits if (targetType.getNumBits() > typeOnStack.getNumBits()) @@ -196,15 +204,17 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con appendHighBitsCleanup(targetType); } } - } - else if (_typeOnStack != _targetType) + break; + default: // 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) { - CompilerContext::LocationSetter locationSetter(m_context, &_assignment); + CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); if (_assignment.getType()->isValueType()) appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); @@ -226,9 +236,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_currentLValue->retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (lvalueSize > 0) + { + solAssert(itemSize + lvalueSize <= 16, "Stack too deep."); // value [lvalue_ref] updated_value for (unsigned i = 0; i < itemSize; ++i) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; + } } m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); @@ -237,7 +250,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) 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: // 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 @@ -270,23 +283,24 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::Dec: // -- (pre- or postfix) solAssert(!!m_currentLValue, "LValue not retrieved."); m_currentLValue->retrieveValue(_unaryOperation.getLocation()); - solAssert(m_currentLValue->sizeOnStack() <= 1, "Not implemented."); if (!_unaryOperation.isPrefixOperation()) { - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - else - m_context << eth::Instruction::DUP1; + // store value for later + solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented."); + 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); if (_unaryOperation.getOperator() == Token::Inc) m_context << eth::Instruction::ADD; else - m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap - // Stack for prefix: [ref] (*ref)+-1 - // Stack for postfix: *ref [ref] (*ref)+-1 - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // Stack for prefix: [ref...] (*ref)+-1 + // Stack for postfix: *ref [ref...] (*ref)+-1 + for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); m_currentLValue->storeValue( *_unaryOperation.getType(), _unaryOperation.getLocation(), !_unaryOperation.isPrefixOperation()); @@ -307,7 +321,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) 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& rightExpression = _binaryOperation.getRightExpression(); Type const& commonType = _binaryOperation.getCommonType(); @@ -354,7 +368,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { - CompilerContext::LocationSetter locationSetter(m_context, &_functionCall); + CompilerContext::LocationSetter locationSetter(m_context, _functionCall); using Location = FunctionType::Location; if (_functionCall.isTypeConversion()) { @@ -405,7 +419,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } _functionCall.getExpression().accept(*this); - m_context.appendJump(); + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes()); @@ -528,8 +542,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) appendTypeConversion(*arguments[arg - 1]->getType(), *function.getParameterTypes()[arg - 1], true); } - m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); - ++numIndexed; + if (!event.isAnonymous()) + { + m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName())))); + ++numIndexed; + } solAssert(numIndexed <= 4, "Too many indexed arguments."); // Copy all non-indexed arguments to memory (data) m_context << u256(0); @@ -550,10 +567,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case Location::SHA256: case Location::RIPEMD160: { + _functionCall.getExpression().accept(*this); static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, {Location::SHA256, 2}, {Location::RIPEMD160, 3}}; m_context << contractAddresses.find(function.getLocation())->second; + for (unsigned i = function.getSizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); appendExternalFunctionCall(function, arguments, true); break; } @@ -572,7 +592,7 @@ bool ExpressionCompiler::visit(NewExpression const&) void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_memberAccess); + CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); ASTString const& member = _memberAccess.getMemberName(); switch (_memberAccess.getExpression().getType()->getCategory()) { @@ -639,13 +659,18 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::GASPRICE; else if (member == "data") 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 BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); break; case Type::Category::Struct: { 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); break; } @@ -707,110 +732,43 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_indexAccess); + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); _indexAccess.getBaseExpression().accept(*this); Type const& baseType = *_indexAccess.getBaseExpression().getType(); 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(); - m_context << u256(0); + m_context << u256(0); // memory position solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); - solAssert(baseType.getSizeOnStack() == 1, - "Unexpected: Not exactly one stack slot taken by subscriptable expression."); m_context << eth::Instruction::SWAP1; appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; + m_context << u256(0); setLValueToStorageItem( _indexAccess); } else if (baseType.getCategory() == Type::Category::Array) { - // stack layout: <base_ref> [<length>] <index> ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); - 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; + + // remove storage byte offset + if (arrayType.getLocation() == ArrayType::Location::Storage) + m_context << eth::Instruction::POP; _indexAccess.getIndexExpression()->accept(*this); - // 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()) - // 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 + // stack layout: <base_ref> [<length>] <index> + ArrayUtils(m_context).accessIndex(arrayType); + if (arrayType.getLocation() == ArrayType::Location::Storage) { - u256 elementSize = - location == ArrayType::Location::Storage ? - arrayType.getBaseType()->getStorageSize() : - 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: + if (arrayType.isByteArray()) + setLValue<StorageByteArrayElement>(_indexAccess); + else setLValueToStorageItem(_indexAccess); - break; - case ArrayType::Location::Memory: - solAssert(false, "Memory lvalues not yet implemented."); - } } } else @@ -821,18 +779,34 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) void ExpressionCompiler::endVisit(Identifier const& _identifier) { + CompilerContext::LocationSetter locationSetter(m_context, _identifier); Declaration const* declaration = _identifier.getReferencedDeclaration(); 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" if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper()) 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)) m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); - else if (dynamic_cast<VariableDeclaration const*>(declaration)) - setLValueFromDeclaration(*declaration, _identifier); + else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) + { + if (!variable->isConstant()) + setLValueFromDeclaration(*declaration, _identifier); + else + variable->getValue()->accept(*this); + } else if (dynamic_cast<ContractDefinition const*>(declaration)) { // no-op @@ -860,11 +834,12 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Literal const& _literal) { + CompilerContext::LocationSetter locationSetter(m_context, _literal); switch (_literal.getType()->getCategory()) { case Type::Category::IntegerConstant: case Type::Category::Bool: - case Type::Category::String: + case Type::Category::FixedBytes: m_context << _literal.getType()->literalValue(&_literal); break; default: @@ -1115,7 +1090,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio if (m_context.isLocalVariable(&_declaration)) setLValue<StackVariable>(_expression, _declaration); else if (m_context.isStateVariable(&_declaration)) - setLValue<StorageItem>(_expression, _declaration); + setLValue<StorageItem>(_expression, _declaration); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h index 9cab757e..2577d21b 100644 --- a/ExpressionCompiler.h +++ b/ExpressionCompiler.h @@ -42,7 +42,6 @@ class CompilerContext; class Type; class IntegerType; class ArrayType; -class StaticStringType; /** * 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); else lvalue->retrieveValue(_expression.getLocation(), true); - } } diff --git a/GlobalContext.cpp b/GlobalContext.cpp index 60de5105..80cebd76 100644 --- a/GlobalContext.cpp +++ b/GlobalContext.cpp @@ -37,26 +37,27 @@ GlobalContext::GlobalContext(): 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>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), + make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), 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<FunctionType>(strings{"hash"},strings{}, FunctionType::Location::Log0)), + make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), 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<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<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<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<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), 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<FunctionType>(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) + make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))}) { } diff --git a/InterfaceHandler.cpp b/InterfaceHandler.cpp index 99a7db96..aacbbfd7 100644 --- a/InterfaceHandler.cpp +++ b/InterfaceHandler.cpp @@ -70,6 +70,7 @@ std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinitio Json::Value event; event["type"] = "event"; event["name"] = it->getName(); + event["anonymous"] = it->isAnonymous(); Json::Value params(Json::arrayValue); for (auto const& p: it->getParameters()) { @@ -128,7 +129,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi if (!m_notice.empty()) {// since @notice is the only user tag if missing function should not appear 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; Json::Value params(Json::objectValue); + std::vector<std::string> paramNames = it.second->getParameterNames(); 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; + } if (!m_params.empty()) method["params"] = params; @@ -184,7 +194,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin method["return"] = m_return; 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; @@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): 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): @@ -86,62 +87,78 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): if (m_dataType.isValueType()) { solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); - solAssert(m_dataType.getStorageSize() <= numeric_limits<unsigned>::max(), - "The storage size of " + m_dataType.toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType.getStorageSize()); + solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); } - else - m_size = 0; // unused } void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { + // stack: storage_key storage_offset if (!m_dataType.isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) - m_context << eth::Instruction::DUP1; - if (m_size == 1) - m_context << eth::Instruction::SLOAD; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + m_context << eth::Instruction::POP << eth::Instruction::SLOAD; else - for (unsigned i = 0; i < m_size; ++i) - { - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_size) - m_context << u256(1) << eth::Instruction::ADD; - else - m_context << eth::Instruction::POP; - } + { + m_context + << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 + << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (m_dataType.getCategory() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL; + 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 { - // stack layout: value value ... value target_ref + // stack: value storage_key storage_offset 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) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_size; ++i) - m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + // offset should be zero + m_context << eth::Instruction::POP; + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SSTORE; } - if (m_size > 1) // store high index value first - m_context << u256(m_size - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_size; ++i) + else { - if (i + 1 >= m_size) - m_context << eth::Instruction::SSTORE; - else - // stack here: value value ... value value (target_ref+offset) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // OR the value into the other values in the storage slot + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: value storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: value storege_ref multiplier old_full_value + // 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 { - solAssert(_sourceType.getCategory() == m_dataType.getCategory(), + solAssert( + _sourceType.getCategory() == m_dataType.getCategory(), "Wrong type conversation for assignment."); 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&>(_sourceType)); if (_move) - m_context << eth::Instruction::POP; + CompilerUtils(m_context).popStackElement(_sourceType); } 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); solAssert(structType == _sourceType, "Struct assignment with conversion."); for (auto const& member: structType.getMembers()) @@ -162,26 +180,37 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_member_ref + pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << 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); - // stack: source_ref target_ref member_offset source_value... - m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) - << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_value... target_member_ref + // stack: source_ref source_off target_ref target_off member_offset source_value... + solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep."); + m_context + << 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); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } if (_move) - m_context << eth::Instruction::POP; + m_context + << eth::Instruction::POP << eth::Instruction::POP + << eth::Instruction::POP << eth::Instruction::POP; else - m_context << eth::Instruction::SWAP1; - m_context << eth::Instruction::POP; + m_context + << eth::Instruction::SWAP2 << eth::Instruction::POP + << eth::Instruction::SWAP2 << eth::Instruction::POP; } else - BOOST_THROW_EXCEPTION(InternalCompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); + BOOST_THROW_EXCEPTION( + 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 (!_removeReference) - m_context << eth::Instruction::DUP1; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); } 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); for (auto const& member: structType.getMembers()) { @@ -203,38 +233,48 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP2 << eth::Instruction::ADD; + pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD + << u256(offsets.second); StorageItem(m_context, *memberType).setToZero(); } if (_removeReference) - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } else { solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); - if (m_size == 0 && _removeReference) - m_context << eth::Instruction::POP; - else if (m_size == 1) + // @todo actually use offset + if (!_removeReference) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + { + // offset should be zero m_context - << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) - << eth::Instruction::SSTORE; + << eth::Instruction::POP << u256(0) + << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } else { - if (!_removeReference) - m_context << eth::Instruction::DUP1; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << 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 -static IntegerType byteType(8, IntegerType::Modifier::Hash); +static FixedBytesType byteType(1); StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): LValue(_compilerContext, byteType) @@ -250,6 +290,7 @@ void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove) else m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD << 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 @@ -265,8 +306,9 @@ void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, boo m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL << eth::Instruction::NOT << eth::Instruction::AND; // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::MUL - << eth::Instruction::OR; + m_context << eth::Instruction::SWAP1; + m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV + << eth::Instruction::MUL << eth::Instruction::OR; // stack: value ref new_full_value m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; if (_move) @@ -297,6 +339,8 @@ StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); + // storage byte offset must be zero + m_context << eth::Instruction::POP; } void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const @@ -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 { @@ -107,6 +109,7 @@ public: StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. 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 storeValue( Type const& _sourceType, @@ -117,11 +120,6 @@ public: SourceLocation const& _location = SourceLocation(), bool _removeReference = true ) const override; - -private: - /// Number of stack elements occupied by the value (not the reference). - /// Only used for value types. - unsigned m_size; }; /** @@ -164,8 +164,17 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition() } nodeFactory.markEndPosition(); expectToken(Token::RBrace); - return nodeFactory.createNode<ContractDefinition>(name, docString, baseContracts, structs, enums, - stateVariables, functions, modifiers, events); + return nodeFactory.createNode<ContractDefinition>( + name, + docString, + baseContracts, + structs, + enums, + stateVariables, + functions, + modifiers, + events + ); } ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier() @@ -247,8 +256,15 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* } else returnParameters = createEmptyParameterList(); - ASTPointer<Block> block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + ASTPointer<Block> block = ASTPointer<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); return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring, parameters, isDeclaredConst, modifiers, @@ -317,6 +333,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( nodeFactory.setEndPositionFromNode(type); } bool isIndexed = false; + bool isDeclaredConst = false; ASTPointer<ASTString> identifier; Token::Value token = m_scanner->getCurrentToken(); Declaration::Visibility visibility(Declaration::Visibility::Default); @@ -327,7 +344,13 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( isIndexed = true; m_scanner->next(); } + if (token == Token::Const) + { + isDeclaredConst = true; + m_scanner->next(); + } nodeFactory.markEndPosition(); + if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier) { identifier = make_shared<ASTString>(""); @@ -348,7 +371,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( } return nodeFactory.createNode<VariableDeclaration>(type, identifier, value, visibility, _options.isStateVariable, - isIndexed); + isIndexed, isDeclaredConst); } ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() @@ -387,9 +410,15 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition() parameters = parseParameterList(true, true); else parameters = createEmptyParameterList(); + bool anonymous = false; + if (m_scanner->getCurrentToken() == Token::Anonymous) + { + anonymous = true; + m_scanner->next(); + } nodeFactory.markEndPosition(); expectToken(Token::Semicolon); - return nodeFactory.createNode<EventDefinition>(name, docstring, parameters); + return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous); } ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() @@ -913,6 +942,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const // In all other cases, we have an expression statement. Token::Value token(m_scanner->getCurrentToken()); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); + if (token == Token::Mapping || token == Token::Var || (mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier)) return LookAheadInfo::VariableDeclarationStatement; @@ -34,6 +34,8 @@ class Scanner; class Parser { public: + Parser() {} + ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner); std::shared_ptr<std::string const> const& getSourceName() const; @@ -64,8 +66,7 @@ private: ASTPointer<StructDefinition> parseStructDefinition(); ASTPointer<EnumDefinition> parseEnumDefinition(); ASTPointer<EnumValue> parseEnumValue(); - ASTPointer<VariableDeclaration> parseVariableDeclaration( - VarDeclParserOptions const& _options = VarDeclParserOptions(), + ASTPointer<VariableDeclaration> parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions(), ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>()); ASTPointer<ModifierDefinition> parseModifierDefinition(); ASTPointer<EventDefinition> parseEventDefinition(); @@ -143,8 +143,8 @@ namespace solidity \ /* Keywords */ \ K(Break, "break", 0) \ - K(Case, "case", 0) \ K(Const, "constant", 0) \ + K(Anonymous, "anonymous", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Default, "default", 0) \ @@ -167,7 +167,6 @@ namespace solidity K(Return, "return", 0) \ K(Returns, "returns", 0) \ K(Struct, "struct", 0) \ - K(Switch, "switch", 0) \ K(Var, "var", 0) \ K(While, "while", 0) \ K(Enum, "enum", 0) \ @@ -252,77 +251,43 @@ namespace solidity K(UInt240, "uint240", 0) \ K(UInt248, "uint248", 0) \ K(UInt256, "uint256", 0) \ - K(Hash, "hash", 0) \ - K(Hash8, "hash8", 0) \ - K(Hash16, "hash16", 0) \ - K(Hash24, "hash24", 0) \ - K(Hash32, "hash32", 0) \ - K(Hash40, "hash40", 0) \ - K(Hash48, "hash48", 0) \ - K(Hash56, "hash56", 0) \ - K(Hash64, "hash64", 0) \ - K(Hash72, "hash72", 0) \ - K(Hash80, "hash80", 0) \ - K(Hash88, "hash88", 0) \ - K(Hash96, "hash96", 0) \ - K(Hash104, "hash104", 0) \ - K(Hash112, "hash112", 0) \ - K(Hash120, "hash120", 0) \ - K(Hash128, "hash128", 0) \ - K(Hash136, "hash136", 0) \ - K(Hash144, "hash144", 0) \ - K(Hash152, "hash152", 0) \ - K(Hash160, "hash160", 0) \ - K(Hash168, "hash168", 0) \ - K(Hash176, "hash178", 0) \ - K(Hash184, "hash184", 0) \ - K(Hash192, "hash192", 0) \ - K(Hash200, "hash200", 0) \ - K(Hash208, "hash208", 0) \ - K(Hash216, "hash216", 0) \ - K(Hash224, "hash224", 0) \ - K(Hash232, "hash232", 0) \ - K(Hash240, "hash240", 0) \ - K(Hash248, "hash248", 0) \ - K(Hash256, "hash256", 0) \ + K(Bytes0, "bytes0", 0) \ + K(Bytes1, "bytes1", 0) \ + K(Bytes2, "bytes2", 0) \ + K(Bytes3, "bytes3", 0) \ + K(Bytes4, "bytes4", 0) \ + K(Bytes5, "bytes5", 0) \ + K(Bytes6, "bytes6", 0) \ + K(Bytes7, "bytes7", 0) \ + K(Bytes8, "bytes8", 0) \ + K(Bytes9, "bytes9", 0) \ + K(Bytes10, "bytes10", 0) \ + K(Bytes11, "bytes11", 0) \ + K(Bytes12, "bytes12", 0) \ + K(Bytes13, "bytes13", 0) \ + K(Bytes14, "bytes14", 0) \ + K(Bytes15, "bytes15", 0) \ + K(Bytes16, "bytes16", 0) \ + K(Bytes17, "bytes17", 0) \ + K(Bytes18, "bytes18", 0) \ + K(Bytes19, "bytes19", 0) \ + K(Bytes20, "bytes20", 0) \ + K(Bytes21, "bytes21", 0) \ + K(Bytes22, "bytes22", 0) \ + K(Bytes23, "bytes23", 0) \ + K(Bytes24, "bytes24", 0) \ + K(Bytes25, "bytes25", 0) \ + K(Bytes26, "bytes26", 0) \ + K(Bytes27, "bytes27", 0) \ + K(Bytes28, "bytes28", 0) \ + K(Bytes29, "bytes29", 0) \ + K(Bytes30, "bytes30", 0) \ + K(Bytes31, "bytes31", 0) \ + K(Bytes32, "bytes32", 0) \ + K(Bytes, "bytes", 0) \ + K(Byte, "byte", 0) \ K(Address, "address", 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(UReal, "ureal", 0) \ T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ @@ -338,6 +303,16 @@ namespace solidity /* Identifiers (not keywords or future reserved words). */ \ 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. */ \ T(Illegal, "ILLEGAL", 0) \ \ @@ -20,14 +20,14 @@ * Solidity data types */ +#include <libsolidity/Types.h> +#include <limits> +#include <boost/range/adaptor/reversed.hpp> #include <libdevcore/CommonIO.h> #include <libdevcore/CommonData.h> #include <libsolidity/Utils.h> -#include <libsolidity/Types.h> #include <libsolidity/AST.h> -#include <limits> - using namespace std; namespace dev @@ -35,28 +35,113 @@ namespace dev 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) { - 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 bytes = offset % 33; - if (bytes == 0) + if (bytes == 0 && _typeToken != Token::Bytes0) bytes = 32; int modifier = offset / 33; - return make_shared<IntegerType>(bytes * 8, - modifier == 0 ? IntegerType::Modifier::Signed : - modifier == 1 ? IntegerType::Modifier::Unsigned : - IntegerType::Modifier::Hash); + switch(modifier) + { + case 0: + 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) return make_shared<IntegerType>(0, IntegerType::Modifier::Address); else if (_typeToken == Token::Bool) 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) return make_shared<ArrayType>(ArrayType::Location::Storage); else @@ -99,6 +184,8 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length TypePointer baseType = _baseTypeName.toType(); if (!baseType) 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->getType()) @@ -123,7 +210,7 @@ TypePointer Type::forLiteral(Literal const& _literal) return make_shared<IntegerConstantType>(_literal); case Token::StringLiteral: //@todo put larger strings into dynamic strings - return StaticStringType::smallestTypeForLiteral(_literal.getValue()); + return FixedBytesType::smallestTypeForLiteral(_literal.getValue()); default: return shared_ptr<Type>(); } @@ -139,7 +226,7 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) return TypePointer(); } -const MemberList Type::EmptyMemberList = MemberList(); +const MemberList Type::EmptyMemberList; IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) @@ -159,8 +246,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; if (isAddress()) return convertTo.isAddress(); - else if (isHash()) - return convertTo.isHash(); else if (isSigned()) return convertTo.isSigned(); else @@ -169,14 +254,10 @@ bool IntegerType::isImplicitlyConvertibleTo(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() || - _convertTo.getCategory() == Category::Contract || - _convertTo.getCategory() == Category::Enum; + _convertTo.getCategory() == Category::Contract || + _convertTo.getCategory() == Category::Enum || + _convertTo.getCategory() == Category::FixedBytes; } TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const @@ -187,16 +268,10 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const // no further unary operators for addresses else if (isAddress()) return TypePointer(); - // "~" is ok for all other types - 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 -- + // for non-address integers, we allow +, -, ++ and -- else if (_operator == Token::Add || _operator == Token::Sub || _operator == Token::Inc || _operator == Token::Dec || - _operator == Token::After) + _operator == Token::After || _operator == Token::BitNot) return shared_from_this(); else return TypePointer(); @@ -214,7 +289,7 @@ string IntegerType::toString() const { if (isAddress()) return "address"; - string prefix = isHash() ? "hash" : (isSigned() ? "int" : "uint"); + string prefix = isSigned() ? "int" : "uint"; return prefix + dev::toString(m_bits); } @@ -230,20 +305,18 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe // All integer types can be compared if (Token::isCompareOp(_operator)) return commonType; - - // Nothing else can be done with addresses, but hashes can receive bit operators + // Nothing else can be done with addresses if (commonType->isAddress()) return TypePointer(); - else if (commonType->isHash() && !Token::isBitOp(_operator)) - return TypePointer(); - else - return commonType; + + return commonType; } -const MemberList IntegerType::AddressMemberList = - MemberList({{"balance", make_shared<IntegerType >(256)}, - {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)}, - {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); +const MemberList IntegerType::AddressMemberList({ + {"balance", make_shared<IntegerType >(256)}, + {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)}, + {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)} +}); IntegerConstantType::IntegerConstantType(Literal const& _literal) { @@ -284,8 +357,17 @@ IntegerConstantType::IntegerConstantType(Literal const& _literal) bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - TypePointer integerType = getIntegerType(); - return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); + shared_ptr<IntegerType const> integerType = getIntegerType(); + 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 @@ -433,50 +515,67 @@ shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const : IntegerType::Modifier::Unsigned); } -shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal) +shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal) { if (_literal.length() <= 32) - return make_shared<StaticStringType>(_literal.length()); - return shared_ptr<StaticStringType>(); + return make_shared<FixedBytesType>(_literal.length()); + 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, - "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()) return false; - StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo); + FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo); 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 true; - if (_convertTo.getCategory() == Category::Integer) - { - IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); - if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) - return true; - } + return _convertTo.getCategory() == Category::Integer || + _convertTo.getCategory() == Category::Contract || + _convertTo.getCategory() == getCategory(); +} - return false; +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 StaticStringType::operator==(Type const& _other) const +bool FixedBytesType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) return false; - StaticStringType const& other = dynamic_cast<StaticStringType const&>(_other); + FixedBytesType const& other = dynamic_cast<FixedBytesType const&>(_other); return other.m_bytes == m_bytes; } -u256 StaticStringType::literalValue(const Literal* _literal) const +u256 FixedBytesType::literalValue(const Literal* _literal) const { solAssert(_literal, ""); u256 value = 0; @@ -603,13 +702,21 @@ u256 ArrayType::getStorageSize() const { if (isDynamicallySized()) return 1; - else + + bigint size; + unsigned baseBytes = getBaseType()->getStorageBytes(); + if (baseBytes == 0) + size = 1; + else if (baseBytes < 32) { - bigint 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 itemsPerSlot = 32 / baseBytes; + size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot; } + 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 @@ -617,6 +724,9 @@ unsigned ArrayType::getSizeOnStack() const if (m_location == Location::CallData) // offset [length] (stack top) return 1 + (isDynamicallySized() ? 1 : 0); + else if (m_location == Location::Storage) + // storage_key storage_offset + return 2; else // offset return 1; @@ -632,6 +742,23 @@ string ArrayType::toString() const 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 { auto copy = make_shared<ArrayType>(_location); @@ -645,7 +772,7 @@ shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) 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 { @@ -706,6 +833,26 @@ u256 ContractType::getFunctionIdentifier(string const& _functionName) const 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 { return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer(); @@ -721,12 +868,7 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { - bigint size = 0; - 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)); + return max<u256>(1, getMembers().getStorageSize()); } bool StructType::canLiveOutsideStorage() const @@ -755,17 +897,11 @@ MemberList const& StructType::getMembers() const return *m_members; } -u256 StructType::getStorageOffsetOfMember(string const& _name) const +pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const { - //@todo cache member offset? - u256 offset; - for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers()) - { - if (variable->getName() == _name) - return offset; - offset += variable->getType()->getStorageSize(); - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); + auto const* offsets = getMembers().getMemberStorageOffset(_name); + solAssert(offsets, "Storage offset of non-existing member requested."); + return *offsets; } TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const @@ -781,6 +917,15 @@ bool EnumType::operator==(Type const& _other) const 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 { return string("enum ") + m_enum.getName(); @@ -925,6 +1070,13 @@ string FunctionType::toString() const return name + ")"; } +u256 FunctionType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + unsigned FunctionType::getSizeOnStack() const { Location location = m_location; @@ -946,6 +1098,26 @@ unsigned FunctionType::getSizeOnStack() const 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 { 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; if (_name == "") @@ -985,8 +1157,12 @@ string FunctionType::getCanonicalSignature(std::string const& _name) const } string ret = funcName + "("; - for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) - ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ","); + TypePointers externalParameterTypes = dynamic_cast<FunctionType const&>(*externalType()).getParameterTypes(); + 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 + ")"; } @@ -1047,6 +1223,13 @@ string MappingType::toString() const 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 { if (_other.getCategory() != getCategory()) @@ -1055,6 +1238,13 @@ bool TypeType::operator==(Type const& _other) const 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 { // We need to lazy-initialize it because of recursive references. @@ -1092,6 +1282,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier) 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 { if (_other.getCategory() != getCategory()) @@ -1122,22 +1319,29 @@ MagicType::MagicType(MagicType::Kind _kind): switch (m_kind) { case Kind::Block: - m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, - {"timestamp", make_shared<IntegerType>(256)}, - {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)}, - {"difficulty", make_shared<IntegerType>(256)}, - {"number", make_shared<IntegerType>(256)}, - {"gaslimit", make_shared<IntegerType>(256)}}); + m_members = move(MemberList({ + {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"timestamp", make_shared<IntegerType>(256)}, + {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, + {"difficulty", make_shared<IntegerType>(256)}, + {"number", make_shared<IntegerType>(256)}, + {"gaslimit", make_shared<IntegerType>(256)} + })); break; case Kind::Message: - m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, - {"gas", make_shared<IntegerType>(256)}, - {"value", make_shared<IntegerType>(256)}, - {"data", make_shared<ArrayType>(ArrayType::Location::CallData)}}); + m_members = move(MemberList({ + {"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"gas", make_shared<IntegerType>(256)}, + {"value", make_shared<IntegerType>(256)}, + {"data", make_shared<ArrayType>(ArrayType::Location::CallData)}, + {"sig", make_shared<FixedBytesType>(4)} + })); break; case Kind::Transaction: - m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, - {"gasprice", make_shared<IntegerType>(256)}}); + m_members = move(MemberList({ + {"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"gasprice", make_shared<IntegerType>(256)} + })); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); @@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr<Type const>; using FunctionTypePointer = std::shared_ptr<FunctionType const>; 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. */ @@ -53,6 +73,7 @@ public: MemberList() {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + MemberList& operator=(MemberList&& _other); TypePointer getMemberType(std::string const& _name) const { for (auto const& it: m_memberTypes) @@ -60,12 +81,18 @@ public: return it.second; 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 end() const { return m_memberTypes.end(); } private: MemberMap m_memberTypes; + mutable std::unique_ptr<StorageOffsets> m_storageOffsets; }; /** @@ -77,12 +104,12 @@ public: enum class Category { Integer, IntegerConstant, Bool, Real, Array, - String, Contract, Struct, Function, OverloadedFunctions, Enum, + FixedBytes, Contract, Struct, Function, OverloadedFunctions, Enum, Mapping, Void, TypeType, Modifier, Magic }; - ///@{ - ///@name Factory functions + /// @{ + /// @name Factory functions /// Factory functions that convert an AST @ref TypeName to a Type. static TypePointer fromElementaryTypeName(Token::Value _typeToken); 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 static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); + /// Calculates the + virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -126,9 +155,15 @@ public: unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } /// @returns true if the type is dynamically encoded in calldata 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, 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. virtual bool canBeStored() const { return true; } /// 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.")); } + /// @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: /// Convenience object used when returning an empty member list. 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 { public: enum class Modifier { - Unsigned, Signed, Hash, Address + Unsigned, Signed, Address }; virtual Category getCategory() const override { return Category::Integer; } @@ -179,14 +218,16 @@ public: virtual bool operator==(Type const& _other) const override; 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 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 TypePointer externalType() const override { return shared_from_this(); } + 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 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: - 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. - 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 isExplicitlyConvertibleTo(Type const& _convertTo) 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 getStorageBytes() const override { return m_bytes; } 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 TypePointer externalType() const override { return shared_from_this(); } int getNumBytes() const { return m_bytes; } @@ -273,16 +318,21 @@ public: 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 { 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 std::string toString() const override { return "bool"; } 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>]) * 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 { @@ -293,13 +343,22 @@ public: /// Constructor for a byte array ("bytes") 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[]") 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]") 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 TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -310,6 +369,7 @@ public: virtual unsigned getSizeOnStack() const override; virtual std::string toString() const override; virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } + virtual TypePointer externalType() const override; Location getLocation() const { return m_location; } bool isByteArray() const { return m_isByteArray; } @@ -344,10 +404,12 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) 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 std::string toString() 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; } ContractDefinition const& getContractDefinition() const { return m_contract; } @@ -360,6 +422,10 @@ public: /// not exist. 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: ContractDefinition const& m_contract; /// 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 u256 getStorageSize() 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 MemberList const& getMembers() const override; - u256 getStorageOffsetOfMember(std::string const& _name) const; + std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const; private: StructDefinition const& m_struct; @@ -407,10 +473,12 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getSizeOnStack() const override { return 1; } + virtual unsigned getStorageBytes() const override; virtual std::string toString() const override; virtual bool isValueType() const override { return true; } 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; } /// @returns the value that the string has in the Enum @@ -443,6 +511,11 @@ public: Bare }; 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(VariableDeclaration const& _varDecl); explicit FunctionType(EventDefinition const& _event); @@ -476,16 +549,16 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; 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 unsigned getSizeOnStack() const override; virtual MemberList const& getMembers() const override; 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 /// function type is used - std::string getCanonicalSignature(std::string const& _name = "") const; + std::string externalSignature(std::string const& _name = "") const; Declaration const& getDeclaration() const { 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_returnParameterNames; 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_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 @@ -540,6 +613,7 @@ private: /** * 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 { @@ -550,6 +624,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; + virtual unsigned getSizeOnStack() const override { return 2; } virtual bool canLiveOutsideStorage() const override { return false; } TypePointer const& getKeyType() const { return m_keyType; } @@ -573,7 +648,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } 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 unsigned getSizeOnStack() const override { return 0; } }; @@ -593,7 +668,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; 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 unsigned getSizeOnStack() const override { return 0; } 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 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 unsigned getSizeOnStack() const override { return 0; } virtual bool operator==(Type const& _other) const override; @@ -22,34 +22,9 @@ #pragma once -#include <string> -#include <libsolidity/Exceptions.h> - -namespace dev -{ -namespace solidity -{ +#include <libdevcore/Assertions.h> /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ - ::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC) - -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); -} + assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) -} -} |