Browse Source

Merge pull request #584 from chriseth/sol_strings

String types.
cl-refactor
chriseth 10 years ago
parent
commit
f3ecfc9999
  1. 17
      libsolidity/Compiler.cpp
  2. 40
      libsolidity/CompilerUtils.cpp
  3. 12
      libsolidity/CompilerUtils.h
  4. 40
      libsolidity/ExpressionCompiler.cpp
  5. 3
      libsolidity/ExpressionCompiler.h
  6. 33
      libsolidity/Token.h
  7. 43
      libsolidity/Types.cpp
  8. 31
      libsolidity/Types.h
  9. 70
      test/solidityEndToEndTest.cpp
  10. 8
      test/solidityNameAndTypeResolution.cpp

17
libsolidity/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;
}

40
libsolidity/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));

12
libsolidity/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.

40
libsolidity/ExpressionCompiler.cpp

@ -225,14 +225,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
@ -240,11 +240,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:
@ -267,7 +267,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:
@ -283,13 +284,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:
@ -373,7 +374,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());
@ -411,10 +413,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."));
}
}
@ -550,6 +553,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."));

3
libsolidity/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);

33
libsolidity/Token.h

@ -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) \

43
libsolidity/Types.cpp

@ -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

31
libsolidity/Types.h

@ -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>;
@ -178,6 +178,35 @@ private:
static const MemberList AddressMemberList;
};
/**
* 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.
*/

70
test/solidityEndToEndTest.cpp

@ -363,6 +363,48 @@ BOOST_AUTO_TEST_CASE(small_signed_types)
testSolidityAgainstCpp(0, small_signed_types_cpp);
}
BOOST_AUTO_TEST_CASE(strings)
{
char const* sourceCode = "contract test {\n"
" function fixed() returns(string32 ret) {\n"
" return \"abc\\x00\\xff__\";\n"
" }\n"
" function pipeThrough(string2 small, bool one) returns(string16 large, bool oneRet) {\n"
" oneRet = one;\n"
" large = small;\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
bytes expectation(32, 0);
expectation[0] = byte('a');
expectation[1] = byte('b');
expectation[2] = byte('c');
expectation[3] = byte(0);
expectation[4] = byte(0xff);
expectation[5] = byte('_');
expectation[6] = byte('_');
BOOST_CHECK(callContractFunction(0, bytes()) == expectation);
expectation = bytes(17, 0);
expectation[0] = 0;
expectation[1] = 2;
expectation[16] = 1;
BOOST_CHECK(callContractFunction(1, bytes({0x00, 0x02, 0x01})) == expectation);
}
BOOST_AUTO_TEST_CASE(empty_string_on_stack)
{
char const* sourceCode = "contract test {\n"
" function run(string0 empty, uint8 inp) returns(uint16 a, string0 b, string4 c) {\n"
" var x = \"abc\";\n"
" var y = \"\";\n"
" var z = inp;\n"
" a = z; b = y; c = x;"
" }\n"
"}\n";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction(0, bytes({0x02})) == bytes({0x00, 0x02, 'a', 'b', 'c', 0x00}));
}
BOOST_AUTO_TEST_CASE(state_smoke_test)
{
char const* sourceCode = "contract test {\n"
@ -941,6 +983,34 @@ BOOST_AUTO_TEST_CASE(inter_contract_calls_with_local_vars)
BOOST_REQUIRE(callContractFunction(0, a, b) == toBigEndian(a * b + 9));
}
BOOST_AUTO_TEST_CASE(strings_in_calls)
{
char const* sourceCode = R"(
contract Helper {
function invoke(string3 x, bool stop) returns (string4 ret) {
return x;
}
}
contract Main {
Helper h;
function callHelper(string2 x, bool stop) returns (string5 ret) {
return h.invoke(x, stop);
}
function getHelper() returns (address addr) {
return address(h);
}
function setHelper(address addr) {
h = Helper(addr);
}
})";
compileAndRun(sourceCode, 0, "Helper");
u160 const helperAddress = m_contractAddress;
compileAndRun(sourceCode, 0, "Main");
BOOST_REQUIRE(callContractFunction(2, helperAddress) == bytes());
BOOST_REQUIRE(callContractFunction(1, helperAddress) == toBigEndian(helperAddress));
BOOST_CHECK(callContractFunction(0, bytes({0, 'a', 1})) == bytes({0, 'a', 0, 0, 0}));
}
BOOST_AUTO_TEST_SUITE_END()
}

8
test/solidityNameAndTypeResolution.cpp

@ -226,6 +226,14 @@ BOOST_AUTO_TEST_CASE(type_inference_explicit_conversion)
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(large_string_literal)
{
char const* text = "contract test {\n"
" function f() { var x = \"123456789012345678901234567890123\"; }"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(balance)
{
char const* text = "contract test {\n"

Loading…
Cancel
Save