diff options
-rw-r--r-- | Compiler.cpp | 17 | ||||
-rw-r--r-- | CompilerUtils.cpp | 40 | ||||
-rw-r--r-- | CompilerUtils.h | 12 | ||||
-rw-r--r-- | ExpressionCompiler.cpp | 40 | ||||
-rw-r--r-- | ExpressionCompiler.h | 3 | ||||
-rw-r--r-- | Token.h | 33 | ||||
-rw-r--r-- | Types.cpp | 43 | ||||
-rw-r--r-- | Types.h | 31 |
8 files changed, 189 insertions, 30 deletions
diff --git a/Compiler.cpp b/Compiler.cpp index 97431341..25833691 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -130,21 +130,17 @@ unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, b { // We do not check the calldata size, everything is zero-padded. unsigned dataOffset = 1; - eth::Instruction load = _fromMemory ? eth::Instruction::MLOAD : eth::Instruction::CALLDATALOAD; //@todo this can be done more efficiently, saving some CALLDATALOAD calls for (ASTPointer<VariableDeclaration> const& var: _function.getParameters()) { unsigned const numBytes = var->getType()->getCalldataEncodedSize(); - if (numBytes == 0 || numBytes > 32) + if (numBytes > 32) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(var->getLocation()) << errinfo_comment("Type " + var->getType()->toString() + " not yet supported.")); - if (numBytes == 32) - m_context << u256(dataOffset) << load; - else - m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset) - << load << eth::Instruction::DIV; + bool leftAligned = var->getType()->getCategory() == Type::Category::STRING; + CompilerUtils(m_context).loadFromMemory(dataOffset, numBytes, leftAligned, !_fromMemory); dataOffset += numBytes; } return dataOffset; @@ -160,14 +156,13 @@ void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) { Type const& paramType = *parameters[i]->getType(); unsigned numBytes = paramType.getCalldataEncodedSize(); - if (numBytes == 0 || numBytes > 32) + if (numBytes > 32) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(parameters[i]->getLocation()) << errinfo_comment("Type " + paramType.toString() + " not yet supported.")); CompilerUtils(m_context).copyToStackTop(stackDepth, paramType); - if (numBytes != 32) - m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; - m_context << u256(dataOffset) << eth::Instruction::MSTORE; + bool const leftAligned = paramType.getCategory() == Type::Category::STRING; + CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); stackDepth -= paramType.getSizeOnStack(); dataOffset += numBytes; } diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index d4dfbe3c..9f474896 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -31,6 +31,46 @@ namespace dev namespace solidity { +void CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _leftAligned, bool _fromCalldata) +{ + if (_bytes == 0) + { + m_context << u256(0); + return; + } + eth::Instruction load = _fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD; + if (asserts(_bytes <= 32)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory load of more than 32 bytes requested.")); + if (_bytes == 32) + m_context << u256(_offset) << load; + else + { + // load data and add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - _bytes) * 8); + m_context << shiftFactor; + if (_leftAligned) + m_context << eth::Instruction::DUP1; + m_context << u256(_offset) << load << eth::Instruction::DIV; + if (_leftAligned) + m_context << eth::Instruction::MUL; + } +} + +void CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftAligned) +{ + if (_bytes == 0) + { + m_context << eth::Instruction::POP; + return; + } + if (asserts(_bytes <= 32)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory store of more than 32 bytes requested.")); + if (_bytes != 32 && !_leftAligned) + // shift the value accordingly before storing + m_context << (u256(1) << ((32 - _bytes) * 8)) << eth::Instruction::MUL; + m_context << u256(_offset) << eth::Instruction::MSTORE; +} + void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); diff --git a/CompilerUtils.h b/CompilerUtils.h index 4da53375..928f0e2d 100644 --- a/CompilerUtils.h +++ b/CompilerUtils.h @@ -35,6 +35,18 @@ class CompilerUtils public: CompilerUtils(CompilerContext& _context): m_context(_context) {} + /// Loads data from memory to the stack. + /// @param _offset offset in memory (or calldata) + /// @param _bytes number of bytes to load + /// @param _leftAligned if true, store left aligned on stack (otherwise right aligned) + /// @param _fromCalldata if true, load from calldata, not from memory + void loadFromMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false, bool _fromCalldata = false); + /// Stores data from stack in memory. + /// @param _offset offset in memory + /// @param _bytes number of bytes to store + /// @param _leftAligned if true, data is left aligned on stack (otherwise right aligned) + void storeInMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false); + /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); /// Copies a variable of type @a _type from a stack depth of @a _stackDepth to the top of the stack. diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index a19c9cfd..3beb423d 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -239,14 +239,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(arguments[i]->getLocation()) << errinfo_comment("Type " + type.toString() + " not yet supported.")); - if (numBytes != 32) - m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; - m_context << u256(dataOffset) << eth::Instruction::MSTORE; + bool const leftAligned = type.getCategory() == Type::Category::STRING; + CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); dataOffset += numBytes; } //@todo only return the first return value for now - unsigned retSize = function.getReturnParameterTypes().empty() ? 0 - : function.getReturnParameterTypes().front()->getCalldataEncodedSize(); + Type const* firstType = function.getReturnParameterTypes().empty() ? nullptr : + function.getReturnParameterTypes().front().get(); + unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; // CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top) m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0) << u256(0); _functionCall.getExpression().accept(*this); // pushes addr and function index @@ -254,11 +254,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB << eth::Instruction::CALL << eth::Instruction::POP; // @todo do not ignore failure indicator - if (retSize == 32) - m_context << u256(0) << eth::Instruction::MLOAD; - else if (retSize > 0) - m_context << (u256(1) << ((32 - retSize) * 8)) - << u256(0) << eth::Instruction::MLOAD << eth::Instruction::DIV; + if (retSize > 0) + { + bool const leftAligned = firstType->getCategory() == Type::Category::STRING; + CompilerUtils(m_context).loadFromMemory(0, retSize, leftAligned); + } break; } case Location::SEND: @@ -281,7 +281,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); // @todo move this once we actually use memory - m_context << u256(0) << eth::Instruction::MSTORE << u256(32) << u256(0) << eth::Instruction::SHA3; + CompilerUtils(m_context).storeInMemory(0); + m_context << u256(32) << u256(0) << eth::Instruction::SHA3; break; case Location::ECRECOVER: case Location::SHA256: @@ -297,13 +298,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments[i]->accept(*this); appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i], true); // @todo move this once we actually use memory - m_context << u256(i * 32) << eth::Instruction::MSTORE; + CompilerUtils(m_context).storeInMemory(i * 32); } m_context << u256(32) << u256(0) << u256(arguments.size() * 32) << u256(0) << u256(0) << contractAddress << u256(500) //@todo determine actual gas requirement << eth::Instruction::CALL - << eth::Instruction::POP - << u256(0) << eth::Instruction::MLOAD; + << eth::Instruction::POP; + CompilerUtils(m_context).loadFromMemory(0); break; } default: @@ -387,7 +388,8 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) *dynamic_cast<MappingType const&>(*_indexAccess.getBaseExpression().getType()).getKeyType(), true); // @todo move this once we actually use memory - m_context << u256(32) << eth::Instruction::MSTORE << u256(0) << eth::Instruction::MSTORE; + CompilerUtils(m_context).storeInMemory(0); + CompilerUtils(m_context).storeInMemory(32); m_context << u256(64) << u256(0) << eth::Instruction::SHA3; m_currentLValue = LValue(m_context, LValue::STORAGE, *_indexAccess.getType()); @@ -425,10 +427,11 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { case Type::Category::INTEGER: case Type::Category::BOOL: + case Type::Category::STRING: m_context << _literal.getType()->literalValue(_literal); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer and boolean literals implemented for now.")); + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); } } @@ -564,6 +567,11 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con return; if (_typeOnStack.getCategory() == Type::Category::INTEGER) appendHighBitsCleanup(dynamic_cast<IntegerType const&>(_typeOnStack)); + else if (_typeOnStack.getCategory() == Type::Category::STRING) + { + // nothing to do, strings are high-order-bit-aligned + //@todo clear lower-order bytes if we allow explicit conversion to shorter strings + } else if (_typeOnStack != _targetType) // All other types should not be convertible to non-equal types. BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h index fb4577c8..c0b173d4 100644 --- a/ExpressionCompiler.h +++ b/ExpressionCompiler.h @@ -35,6 +35,7 @@ namespace solidity { class CompilerContext; class Type; class IntegerType; +class StaticStringType; /** * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream @@ -75,7 +76,7 @@ private: /// @} /// Appends an implicit or explicit type conversion. For now this comprises only erasing - /// higher-order bits (@see appendHighBitCleanup) when widening integer types. + /// higher-order bits (@see appendHighBitCleanup) when widening integer. /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. void appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); @@ -269,6 +269,39 @@ namespace solidity K(ADDRESS, "address", 0) \ K(BOOL, "bool", 0) \ K(STRING_TYPE, "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) \ @@ -53,6 +53,8 @@ shared_ptr<Type const> Type::fromElementaryTypeName(Token::Value _typeToken) return make_shared<IntegerType const>(0, IntegerType::Modifier::ADDRESS); else if (_typeToken == Token::BOOL) return make_shared<BoolType const>(); + else if (Token::STRING0 <= _typeToken && _typeToken <= Token::STRING32) + return make_shared<StaticStringType const>(int(_typeToken) - int(Token::STRING0)); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type.")); @@ -91,7 +93,8 @@ shared_ptr<Type const> Type::forLiteral(Literal const& _literal) case Token::NUMBER: return IntegerType::smallestTypeForLiteral(_literal.getValue()); case Token::STRING_LITERAL: - return shared_ptr<Type const>(); // @todo add string literals + //@todo put larger strings into dynamic strings + return StaticStringType::smallestTypeForLiteral(_literal.getValue()); default: return shared_ptr<Type const>(); } @@ -194,6 +197,44 @@ const MemberList IntegerType::AddressMemberList = {"send", make_shared<FunctionType const>(TypePointers({make_shared<IntegerType const>(256)}), TypePointers(), FunctionType::Location::SEND)}}); +shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal) +{ + if (_literal.length() <= 32) + return make_shared<StaticStringType>(_literal.length()); + return shared_ptr<StaticStringType>(); +} + +StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes) +{ + if (asserts(m_bytes >= 0 && m_bytes <= 32)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid byte number for static string type: " + + dev::toString(m_bytes))); +} + +bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() != getCategory()) + return false; + StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo); + return convertTo.m_bytes >= m_bytes; +} + +bool StaticStringType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + StaticStringType const& other = dynamic_cast<StaticStringType const&>(_other); + return other.m_bytes == m_bytes; +} + +u256 StaticStringType::literalValue(const Literal& _literal) const +{ + u256 value = 0; + for (char c: _literal.getValue()) + value = (value << 8) | byte(c); + return value << ((32 - _literal.getValue().length()) * 8); +} + bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const { // conversion to integer is fine, but not to address @@ -36,7 +36,7 @@ namespace dev namespace solidity { -// @todo realMxN, string<N> +// @todo realMxN, dynamic strings, text, arrays class Type; // forward using TypePointer = std::shared_ptr<Type const>; @@ -179,6 +179,35 @@ private: }; /** + * String type with fixed length, up to 32 bytes. + */ +class StaticStringType: public Type +{ +public: + virtual Category getCategory() const override { return Category::STRING; } + + /// @returns the smallest string type for the given literal or an empty pointer + /// if no type fits. + static std::shared_ptr<StaticStringType> smallestTypeForLiteral(std::string const& _literal); + + StaticStringType(int _bytes); + + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool operator==(Type const& _other) const override; + + virtual unsigned getCalldataEncodedSize() 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 u256 literalValue(Literal const& _literal) const override; + + int getNumBytes() const { return m_bytes; } + +private: + int m_bytes; +}; + +/** * The boolean type. */ class BoolType: public Type |