Browse Source

Simple copy of bytes to storage.

cl-refactor
Christian 10 years ago
parent
commit
f9d853fe90
  1. 118
      libsolidity/ExpressionCompiler.cpp
  2. 5
      libsolidity/ExpressionCompiler.h
  3. 18
      libsolidity/Types.cpp
  4. 11
      libsolidity/Types.h
  5. 65
      test/SolidityEndToEndTest.cpp

118
libsolidity/ExpressionCompiler.cpp

@ -59,13 +59,15 @@ void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context,
bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(Assignment const& _assignment)
{ {
_assignment.getRightHandSide().accept(*this); _assignment.getRightHandSide().accept(*this);
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); if (_assignment.getType()->isValueType())
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
_assignment.getLeftHandSide().accept(*this); _assignment.getLeftHandSide().accept(*this);
solAssert(m_currentLValue.isValid(), "LValue not retrieved."); solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
Token::Value op = _assignment.getAssignmentOperator(); Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::Assign) // compound assignment if (op != Token::Assign) // compound assignment
{ {
solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types.");
if (m_currentLValue.storesReferenceOnStack()) if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true); m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true);
@ -73,7 +75,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
if (m_currentLValue.storesReferenceOnStack()) if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
} }
m_currentLValue.storeValue(_assignment); m_currentLValue.storeValue(_assignment, *_assignment.getRightHandSide().getType());
m_currentLValue.reset(); m_currentLValue.reset();
return false; return false;
@ -126,7 +128,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
// Stack for postfix: *ref [ref] (*ref)+-1 // Stack for postfix: *ref [ref] (*ref)+-1
if (m_currentLValue.storesReferenceOnStack()) if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation()); m_currentLValue.storeValue(_unaryOperation, *_unaryOperation.getType(), !_unaryOperation.isPrefixOperation());
m_currentLValue.reset(); m_currentLValue.reset();
break; break;
case Token::Add: // + case Token::Add: // +
@ -472,6 +474,10 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << eth::Instruction::GAS; m_context << eth::Instruction::GAS;
else if (member == "gasprice") else if (member == "gasprice")
m_context << eth::Instruction::GASPRICE; m_context << eth::Instruction::GASPRICE;
else if (member == "data")
{
// nothing to store on the stack
}
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
break; break;
@ -1014,7 +1020,7 @@ void ExpressionCompiler::LValue::retrieveValueFromStorage(TypePointer const& _ty
} }
} }
void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool _move) const void ExpressionCompiler::LValue::storeValue(Expression const& _expression, Type const& _sourceType, bool _move) const
{ {
switch (m_type) switch (m_type)
{ {
@ -1032,28 +1038,45 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool
break; break;
} }
case LValueType::Storage: case LValueType::Storage:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
// stack layout: value value ... value ref // stack layout: value value ... value ref
if (!_move) // copy values if (_expression.getType()->isValueType())
{ {
if (m_size + 1 > 16) if (!_move) // copy values
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) {
<< errinfo_comment("Stack too deep.")); if (m_size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
for (unsigned i = 0; i < m_size; ++i)
*m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1;
}
if (m_size > 0) // store high index value first
*m_context << u256(m_size - 1) << eth::Instruction::ADD;
for (unsigned i = 0; i < m_size; ++i) for (unsigned i = 0; i < m_size; ++i)
*m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; {
if (i + 1 >= m_size)
*m_context << eth::Instruction::SSTORE;
else
// v v ... v v r+x
*m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2
<< eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
}
} }
if (m_size > 0) // store high index value first else
*m_context << u256(m_size - 1) << eth::Instruction::ADD;
for (unsigned i = 0; i < m_size; ++i)
{ {
if (i + 1 >= m_size) solAssert(!_move, "Move assign for non-value types not implemented.");
*m_context << eth::Instruction::SSTORE; solAssert(_sourceType.getCategory() == _expression.getType()->getCategory(), "");
if (_expression.getType()->getCategory() == Type::Category::ByteArray)
copyByteArrayToStorage(dynamic_cast<ByteArrayType const&>(*_expression.getType()),
dynamic_cast<ByteArrayType const&>(_sourceType));
else if (_expression.getType()->getCategory() == Type::Category::Struct)
{
//@todo
solAssert(false, "Struct copy not yet implemented.");
}
else else
// v v ... v v r+x BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
*m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << errinfo_comment("Invalid non-value type for assignment."));
<< eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
} }
break; break;
case LValueType::Memory: case LValueType::Memory:
@ -1145,5 +1168,60 @@ void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, D
<< errinfo_comment("Identifier type not supported or identifier not found.")); << errinfo_comment("Identifier type not supported or identifier not found."));
} }
void ExpressionCompiler::LValue::copyByteArrayToStorage(ByteArrayType const& _targetType,
ByteArrayType const& _sourceType) const
{
// stack layout: [source_ref] target_ref (head)
// need to leave target_ref on the stack at the end
solAssert(m_type == LValueType::Storage, "");
solAssert(_targetType.getLocation() == ByteArrayType::Location::Storage, "");
switch (_sourceType.getLocation())
{
case ByteArrayType::Location::CallData:
{
// @todo this does not take length into account. It also assumes that after "CALLDATALENGTH" we only have zeros.
// add some useful constants
*m_context << u256(32) << u256(1);
// stack here: target_ref 32 1
// store length (in bytes)
if (_sourceType.getOffset() == 0)
*m_context << eth::Instruction::CALLDATASIZE;
else
*m_context << _sourceType.getOffset() << eth::Instruction::CALLDATASIZE << eth::Instruction::SUB;
*m_context << eth::Instruction::DUP1 << eth::Instruction::DUP5 << eth::Instruction::SSTORE;
// jump to end if length is zero
*m_context << eth::Instruction::ISZERO;
eth::AssemblyItem loopEnd = m_context->newTag();
m_context->appendConditionalJumpTo(loopEnd);
// actual array data is stored at SHA3(storage_offset)
*m_context << eth::Instruction::DUP3;
CompilerUtils(*m_context).storeInMemory(0);
*m_context << u256(32) << u256(0) << eth::Instruction::SHA3;
*m_context << _sourceType.getOffset();
// stack now: target_ref 32 1 target_data_ref calldata_offset
eth::AssemblyItem loopStart = m_context->newTag();
*m_context << loopStart
// copy from calldata and store
<< eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE
// increment target_data_ref by 1
<< eth::Instruction::SWAP1 << eth::Instruction::DUP3 << eth::Instruction::ADD
// increment calldata_offset by 32
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::ADD
// check for loop condition
<< eth::Instruction::DUP1 << eth::Instruction::CALLDATASIZE << eth::Instruction::GT;
m_context->appendConditionalJumpTo(loopStart);
*m_context << eth::Instruction::POP << eth::Instruction::POP;
*m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP;
break;
}
case ByteArrayType::Location::Storage:
break;
default:
solAssert(false, "Byte array location not implemented.");
}
}
} }
} }

5
libsolidity/ExpressionCompiler.h

@ -148,7 +148,8 @@ private:
/// be on the top of the stack, if any) in the lvalue and removes the reference. /// be on the top of the stack, if any) in the lvalue and removes the reference.
/// Also removes the stored value from the stack if @a _move is /// Also removes the stored value from the stack if @a _move is
/// true. @a _expression is the current expression, used for error reporting. /// true. @a _expression is the current expression, used for error reporting.
void storeValue(Expression const& _expression, bool _move = false) const; /// @a _sourceType is the type of the expression that is assigned.
void storeValue(Expression const& _expression, Type const& _sourceType, bool _move = false) const;
/// Stores zero in the lvalue. /// Stores zero in the lvalue.
/// @a _expression is the current expression, used for error reporting. /// @a _expression is the current expression, used for error reporting.
void setToZero(Expression const& _expression) const; void setToZero(Expression const& _expression) const;
@ -159,6 +160,8 @@ private:
private: private:
/// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue
void retrieveValueFromStorage(TypePointer const& _type, bool _remove = false) const; void retrieveValueFromStorage(TypePointer const& _type, bool _remove = false) const;
/// Copies from a byte array to a byte array in storage, both references on the stack.
void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const;
CompilerContext* m_context; CompilerContext* m_context;
LValueType m_type = LValueType::None; LValueType m_type = LValueType::None;

18
libsolidity/Types.cpp

@ -518,6 +518,13 @@ bool ByteArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
return (m_dynamicLength == other.m_dynamicLength || m_length == other.m_length); return (m_dynamicLength == other.m_dynamicLength || m_length == other.m_length);
} }
TypePointer ByteArrayType::unaryOperatorResult(Token::Value _operator) const
{
if (_operator == Token::Delete)
return make_shared<VoidType>();
return TypePointer();
}
bool ByteArrayType::operator==(Type const& _other) const bool ByteArrayType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -527,6 +534,14 @@ bool ByteArrayType::operator==(Type const& _other) const
&& other.m_length == m_length && other.m_offset == m_offset; && other.m_length == m_length && other.m_offset == m_offset;
} }
unsigned ByteArrayType::getSizeOnStack() const
{
if (m_location == Location::CallData)
return 0;
else
return 1;
}
bool ContractType::operator==(Type const& _other) const bool ContractType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
@ -963,8 +978,7 @@ MagicType::MagicType(MagicType::Kind _kind):
m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"gas", make_shared<IntegerType>(256)}, {"gas", make_shared<IntegerType>(256)},
{"value", make_shared<IntegerType>(256)}, {"value", make_shared<IntegerType>(256)},
{"data", make_shared<ByteArrayType>(ByteArrayType::Location::CallData, {"data", make_shared<ByteArrayType>(ByteArrayType::Location::CallData)}});
0, 0, true)}});
break; break;
case Kind::Transaction: case Kind::Transaction:
m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},

11
libsolidity/Types.h

@ -285,13 +285,20 @@ public:
enum class Location { Storage, CallData, Memory }; enum class Location { Storage, CallData, Memory };
virtual Category getCategory() const override { return Category::ByteArray; } virtual Category getCategory() const override { return Category::ByteArray; }
ByteArrayType(Location _location, u256 const& _offset, u256 const& _length, bool _dynamicLength): explicit ByteArrayType(Location _location, u256 const& _offset = 0, u256 const& _length = 0,
bool _dynamicLength = false):
m_location(_location), m_offset(_offset), m_length(_length), m_dynamicLength(_dynamicLength) {} m_location(_location), m_offset(_offset), m_length(_length), m_dynamicLength(_dynamicLength) {}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(const Type& _other) const override; virtual bool operator==(const Type& _other) const override;
virtual unsigned getSizeOnStack() const override { return 1; /* TODO */ } virtual unsigned getSizeOnStack() const override;
virtual std::string toString() const override { return "bytes"; } virtual std::string toString() const override { return "bytes"; }
Location getLocation() const { return m_location; }
u256 const& getOffset() const { return m_offset; }
u256 const& getLength() const { return m_length; }
bool hasDynamicLength() const { return m_dynamicLength; }
private: private:
Location m_location; Location m_location;
u256 m_offset; u256 m_offset;

65
test/SolidityEndToEndTest.cpp

@ -2254,7 +2254,26 @@ BOOST_AUTO_TEST_CASE(generic_call)
BOOST_CHECK_EQUAL(m_state.balance(m_contractAddress), 50 - 2); BOOST_CHECK_EQUAL(m_state.balance(m_contractAddress), 50 - 2);
} }
BOOST_AUTO_TEST_CASE(storing_bytes) BOOST_AUTO_TEST_CASE(store_bytes)
{
// this test just checks that the copy loop does not mess up the stack
char const* sourceCode = R"(
contract C {
function save() returns (uint r) {
r = 23;
savedData = msg.data;
r = 24;
}
bytes savedData;
}
)";
compileAndRun(sourceCode);
// empty copy loop
BOOST_CHECK(callContractFunction("save()") == encodeArgs(24));
BOOST_CHECK(callContractFunction("save()", "abcdefg") == encodeArgs(24));
}
BOOST_AUTO_TEST_CASE(call_forward_bytes)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
contract C { contract C {
@ -2267,16 +2286,56 @@ BOOST_AUTO_TEST_CASE(storing_bytes)
function clear() { function clear() {
delete savedData; delete savedData;
} }
function doubleIt(uint a) returns (uint b) { return a * 2; } function doubleIt(uint a) { val += a * 2; }
bytes savedData; bytes savedData;
uint public val;
} }
)"; )";
compileAndRun(sourceCode); compileAndRun(sourceCode);
FixedHash<4> innerHash(dev::sha3("doubleIt(uint256)")); FixedHash<4> innerHash(dev::sha3("doubleIt(uint256)"));
BOOST_CHECK(callContractFunction("save()", innerHash.asBytes(), 7) == bytes()); BOOST_CHECK(callContractFunction("save()", innerHash.asBytes(), 7) == bytes());
BOOST_CHECK(callContractFunction("forward()") == encodeArgs(14)); BOOST_CHECK(callContractFunction("val()") == bytes(0));
BOOST_CHECK(callContractFunction("forward()") == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(14));
BOOST_CHECK(callContractFunction("clear()") == bytes()); BOOST_CHECK(callContractFunction("clear()") == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(14));
BOOST_CHECK(callContractFunction("forward()") == bytes()); BOOST_CHECK(callContractFunction("forward()") == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(14));
}
BOOST_AUTO_TEST_CASE(copying_bytes_multiassign)
{
char const* sourceCode = R"(
contract C {
function save() {
data1 = data2 = msg.data;
}
function forward(bool selector) {
if (selector)
{
this.call(data1);
delete data1;
}
else
{
this.call(data2);
delete data2;
}
}
function doubleIt(uint a) returns (uint b) { val += a * 2; }
bytes data1;
bytes data2;
uint public val;
}
)";
compileAndRun(sourceCode);
FixedHash<4> innerHash(dev::sha3("doubleIt(uint256)"));
BOOST_CHECK(callContractFunction("save()", innerHash.asBytes(), 7) == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(0));
BOOST_CHECK(callContractFunction("forward(bool)", true) == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(14));
BOOST_CHECK(callContractFunction("forward(bool)", false) == bytes());
BOOST_CHECK(callContractFunction("val()") == bytes(28));
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

Loading…
Cancel
Save