Flexible string literals, convertible to bytesX, bytes and string.

This commit is contained in:
chriseth 2015-07-08 01:13:56 +02:00
parent aa6182ab87
commit 46dde467e7
6 changed files with 186 additions and 51 deletions

View File

@ -113,6 +113,16 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
solAssert(ref->location() == DataLocation::Memory, ""); solAssert(ref->location() == DataLocation::Memory, "");
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
} }
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
{
m_context << eth::Instruction::DUP1;
storeStringData(bytesConstRef(str->value()));
if (_padToWordBoundaries)
m_context << u256(((str->value().size() + 31) / 32) * 32);
else
m_context << u256(str->value().size());
m_context << eth::Instruction::ADD;
}
else else
{ {
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
@ -169,7 +179,8 @@ void CompilerUtils::encodeToMemory(
TypePointer type = targetType; TypePointer type = targetType;
if ( if (
_givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::Storage) ||
_givenTypes[i]->dataStoredIn(DataLocation::CallData) _givenTypes[i]->dataStoredIn(DataLocation::CallData) ||
_givenTypes[i]->getCategory() == Type::Category::StringLiteral
) )
type = _givenTypes[i]; // delay conversion type = _givenTypes[i]; // delay conversion
else else
@ -192,36 +203,48 @@ void CompilerUtils::encodeToMemory(
solAssert(!!targetType, "Externalable type expected."); solAssert(!!targetType, "Externalable type expected.");
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{ {
solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type.");
auto const& arrayType = dynamic_cast<ArrayType const&>(*_givenTypes[i]);
// copy tail pointer (=mem_end - mem_start) to memory // copy tail pointer (=mem_end - mem_start) to memory
m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2;
m_context << eth::Instruction::SUB; m_context << eth::Instruction::SUB;
m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer);
m_context << eth::Instruction::MSTORE; m_context << eth::Instruction::MSTORE;
// now copy the array // stack: ... <end_of_mem>
copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack()); if (_givenTypes[i]->getCategory() == Type::Category::StringLiteral)
// stack: ... <end_of_mem> <value...> {
// copy length to memory auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]);
m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); m_context << u256(strType.value().size());
if (arrayType.location() == DataLocation::CallData) storeInMemoryDynamic(IntegerType(256), true);
m_context << eth::Instruction::DUP2; // length is on stack // stack: ... <end_of_mem'>
else if (arrayType.location() == DataLocation::Storage) storeInMemoryDynamic(strType, _padToWordBoundaries);
m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; }
else else
{ {
solAssert(arrayType.location() == DataLocation::Memory, ""); solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type.");
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; auto const& arrayType = dynamic_cast<ArrayType const&>(*_givenTypes[i]);
// now copy the array
copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack());
// stack: ... <end_of_mem> <value...>
// copy length to memory
m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack());
if (arrayType.location() == DataLocation::CallData)
m_context << eth::Instruction::DUP2; // length is on stack
else if (arrayType.location() == DataLocation::Storage)
m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD;
else
{
solAssert(arrayType.location() == DataLocation::Memory, "");
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD;
}
// stack: ... <end_of_mem> <value...> <end_of_mem'> <length>
storeInMemoryDynamic(IntegerType(256), true);
// stack: ... <end_of_mem> <value...> <end_of_mem''>
// copy the new memory pointer
m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP;
// stack: ... <end_of_mem''> <value...>
// copy data part
ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries);
// stack: ... <end_of_mem'''>
} }
// stack: ... <end_of_mem> <value...> <end_of_mem'> <length>
storeInMemoryDynamic(IntegerType(256), true);
// stack: ... <end_of_mem> <value...> <end_of_mem''>
// copy the new memory pointer
m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP;
// stack: ... <end_of_mem''> <value...>
// copy data part
ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries);
// stack: ... <end_of_mem'''>
thisDynPointer++; thisDynPointer++;
} }
@ -269,22 +292,22 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
// conversion from bytes to integer. no need to clean the high bit // conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment // only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) if (targetIntegerType.getNumBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
} }
else else
{ {
// clear lower-order bytes for conversion to shorter bytes - we always clean // clear lower-order bytes for conversion to shorter bytes - we always clean
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType); FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
if (targetType.getNumBytes() < typeOnStack.getNumBytes()) if (targetType.numBytes() < typeOnStack.numBytes())
{ {
if (targetType.getNumBytes() == 0) if (targetType.numBytes() == 0)
m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; m_context << eth::Instruction::DUP1 << eth::Instruction::XOR;
else else
{ {
m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)); m_context << (u256(1) << (256 - targetType.numBytes() * 8));
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2; m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2;
m_context << eth::Instruction::DIV << eth::Instruction::MUL; m_context << eth::Instruction::DIV << eth::Instruction::MUL;
} }
@ -306,9 +329,9 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
// only to shift left because of opposite alignment // only to shift left because of opposite alignment
FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType); FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack)) if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) if (targetBytesType.numBytes() * 8 > typeOnStack->getNumBits())
cleanHigherOrderBits(*typeOnStack); cleanHigherOrderBits(*typeOnStack);
m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << eth::Instruction::MUL;
} }
else if (targetTypeCategory == Type::Category::Enum) else if (targetTypeCategory == Type::Category::Enum)
// just clean // just clean
@ -340,6 +363,37 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
} }
} }
break; break;
case Type::Category::StringLiteral:
{
auto const& literalType = dynamic_cast<StringLiteralType const&>(_typeOnStack);
string const& value = literalType.value();
bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes)
{
solAssert(data.size() <= 32, "");
m_context << h256::Arith(h256(data, h256::AlignLeft));
}
else if (targetTypeCategory == Type::Category::Array)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(_targetType);
solAssert(arrayType.isByteArray(), "");
u256 storageSize(32 + ((data.size() + 31) / 32) * 32);
m_context << storageSize;
allocateMemory();
// stack: mempos
m_context << eth::Instruction::DUP1 << u256(data.size());
storeInMemoryDynamic(IntegerType(256));
// stack: mempos datapos
storeStringData(data);
break;
}
else
solAssert(
false,
"Invalid conversion from string literal to " + _targetType.toString(false) + " requested."
);
break;
}
case Type::Category::Array: case Type::Category::Array:
{ {
solAssert(targetTypeCategory == stackTypeCategory, ""); solAssert(targetTypeCategory == stackTypeCategory, "");
@ -606,6 +660,28 @@ void CompilerUtils::computeHashStatic()
m_context << u256(32) << u256(0) << eth::Instruction::SHA3; m_context << u256(32) << u256(0) << eth::Instruction::SHA3;
} }
void CompilerUtils::storeStringData(bytesConstRef _data)
{
//@todo provide both alternatives to the optimiser
// stack: mempos
if (_data.size() <= 128)
{
for (unsigned i = 0; i < _data.size(); i += 32)
{
m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft));
storeInMemoryDynamic(IntegerType(256));
}
m_context << eth::Instruction::POP;
}
else
{
// stack: mempos mempos_data
m_context.appendData(_data.toBytes());
m_context << u256(_data.size()) << eth::Instruction::SWAP2;
m_context << eth::Instruction::CODECOPY;
}
}
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);

View File

@ -148,7 +148,12 @@ private:
/// Address of the precompiled identity contract. /// Address of the precompiled identity contract.
static const unsigned identityContractAddress; static const unsigned identityContractAddress;
//// Appends code that cleans higher-order bits for integer types. /// Stores the given string in memory.
/// Stack pre: mempos
/// Stack post:
void storeStringData(bytesConstRef _data);
/// Appends code that cleans higher-order bits for integer types.
void cleanHigherOrderBits(IntegerType const& _typeOnStack); void cleanHigherOrderBits(IntegerType const& _typeOnStack);
/// Prepares the given type for storing in memory by shifting it if necessary. /// Prepares the given type for storing in memory by shifting it if necessary.

View File

@ -158,6 +158,11 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
utils().convertType(*type, *_assignment.getType()); utils().convertType(*type, *_assignment.getType());
type = _assignment.getType(); type = _assignment.getType();
} }
else
{
utils().convertType(*type, *type->mobileType());
type = type->mobileType();
}
_assignment.getLeftHandSide().accept(*this); _assignment.getLeftHandSide().accept(*this);
solAssert(!!m_currentLValue, "LValue not retrieved."); solAssert(!!m_currentLValue, "LValue not retrieved.");
@ -898,13 +903,15 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
void ExpressionCompiler::endVisit(Literal const& _literal) void ExpressionCompiler::endVisit(Literal const& _literal)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _literal); CompilerContext::LocationSetter locationSetter(m_context, _literal);
switch (_literal.getType()->getCategory()) TypePointer type = _literal.getType();
switch (type->getCategory())
{ {
case Type::Category::IntegerConstant: case Type::Category::IntegerConstant:
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::FixedBytes: m_context << type->literalValue(&_literal);
m_context << _literal.getType()->literalValue(&_literal);
break; break;
case Type::Category::StringLiteral:
break; // will be done during conversion
default: default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now."));
} }

View File

@ -209,7 +209,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
// stack: value storage_ref cleared_value multiplier value // stack: value storage_ref cleared_value multiplier value
if (m_dataType.getCategory() == Type::Category::FixedBytes) if (m_dataType.getCategory() == Type::Category::FixedBytes)
m_context m_context
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).getNumBytes())) << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).numBytes()))
<< eth::Instruction::SWAP1 << eth::Instruction::DIV; << eth::Instruction::SWAP1 << eth::Instruction::DIV;
else if ( else if (
m_dataType.getCategory() == Type::Category::Integer && m_dataType.getCategory() == Type::Category::Integer &&

View File

@ -212,8 +212,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
case Token::Number: case Token::Number:
return make_shared<IntegerConstantType>(_literal); return make_shared<IntegerConstantType>(_literal);
case Token::StringLiteral: case Token::StringLiteral:
//@todo put larger strings into dynamic strings return make_shared<StringLiteralType>(_literal);
return FixedBytesType::smallestTypeForLiteral(_literal.getValue());
default: default:
return shared_ptr<Type>(); return shared_ptr<Type>();
} }
@ -378,7 +377,7 @@ bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) cons
else if (_convertTo.getCategory() == Category::FixedBytes) else if (_convertTo.getCategory() == Category::FixedBytes)
{ {
FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo); FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo);
return fixedBytes.getNumBytes() * 8 >= getIntegerType()->getNumBits(); return fixedBytes.numBytes() * 8 >= getIntegerType()->getNumBits();
} }
else else
return false; return false;
@ -530,6 +529,33 @@ shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const
); );
} }
StringLiteralType::StringLiteralType(Literal const& _literal):
m_value(_literal.getValue())
{
}
bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (auto fixedBytes = dynamic_cast<FixedBytesType const*>(&_convertTo))
return size_t(fixedBytes->numBytes()) >= m_value.size();
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_convertTo))
return arrayType->isByteArray();
else
return false;
}
bool StringLiteralType::operator==(const Type& _other) const
{
if (_other.getCategory() != getCategory())
return false;
return m_value == dynamic_cast<StringLiteralType const&>(_other).m_value;
}
TypePointer StringLiteralType::mobileType() const
{
return make_shared<ArrayType>(DataLocation::Memory, true);
}
shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal) shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
{ {
if (_literal.length() <= 32) if (_literal.length() <= 32)
@ -590,15 +616,6 @@ bool FixedBytesType::operator==(Type const& _other) const
return other.m_bytes == m_bytes; return other.m_bytes == m_bytes;
} }
u256 FixedBytesType::literalValue(const Literal* _literal) const
{
solAssert(_literal, "");
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 bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
// conversion to integer is fine, but not to address // conversion to integer is fine, but not to address

36
Types.h
View File

@ -131,7 +131,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this<Type
public: public:
enum class Category enum class Category
{ {
Integer, IntegerConstant, Bool, Real, Array, Integer, IntegerConstant, StringLiteral, Bool, Real, Array,
FixedBytes, Contract, Struct, Function, Enum, FixedBytes, Contract, Struct, Function, Enum,
Mapping, Void, TypeType, Modifier, Magic Mapping, Void, TypeType, Modifier, Magic
}; };
@ -311,6 +311,37 @@ private:
bigint m_value; bigint m_value;
}; };
/**
* Literal string, can be converted to bytes, bytesX or string.
*/
class StringLiteralType: public Type
{
public:
virtual Category getCategory() const override { return Category::StringLiteral; }
explicit StringLiteralType(Literal const& _literal);
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
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 bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; }
virtual std::string toString(bool) const override { return "literal_string \"" + m_value + "\""; }
virtual TypePointer mobileType() const override;
std::string const& value() const { return m_value; }
private:
std::string m_value;
};
/** /**
* Bytes type with fixed length of up to 32 bytes. * Bytes type with fixed length of up to 32 bytes.
*/ */
@ -336,10 +367,9 @@ public:
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } virtual std::string toString(bool) 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(); } virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBytes() const { return m_bytes; } int numBytes() const { return m_bytes; }
private: private:
int m_bytes; int m_bytes;