From f9d853fe909ba443342598aa49c9a7daf8398716 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 10 Feb 2015 14:57:01 +0100 Subject: [PATCH] Simple copy of bytes to storage. --- libsolidity/ExpressionCompiler.cpp | 118 ++++++++++++++++++++++++----- libsolidity/ExpressionCompiler.h | 5 +- libsolidity/Types.cpp | 18 ++++- libsolidity/Types.h | 11 ++- test/SolidityEndToEndTest.cpp | 65 +++++++++++++++- 5 files changed, 189 insertions(+), 28 deletions(-) diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index beda01322..8c9028e45 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -59,13 +59,15 @@ void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context, bool ExpressionCompiler::visit(Assignment const& _assignment) { _assignment.getRightHandSide().accept(*this); - appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + if (_assignment.getType()->isValueType()) + appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); _assignment.getLeftHandSide().accept(*this); solAssert(m_currentLValue.isValid(), "LValue not retrieved."); Token::Value op = _assignment.getAssignmentOperator(); if (op != Token::Assign) // compound assignment { + solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types."); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true); @@ -73,7 +75,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; } - m_currentLValue.storeValue(_assignment); + m_currentLValue.storeValue(_assignment, *_assignment.getRightHandSide().getType()); m_currentLValue.reset(); return false; @@ -126,7 +128,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) // Stack for postfix: *ref [ref] (*ref)+-1 if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation()); + m_currentLValue.storeValue(_unaryOperation, *_unaryOperation.getType(), !_unaryOperation.isPrefixOperation()); m_currentLValue.reset(); break; case Token::Add: // + @@ -472,6 +474,10 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::GAS; else if (member == "gasprice") m_context << eth::Instruction::GASPRICE; + else if (member == "data") + { + // nothing to store on the stack + } else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); 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) { @@ -1032,28 +1038,45 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool break; } case LValueType::Storage: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types // stack layout: value value ... value ref - if (!_move) // copy values + if (_expression.getType()->isValueType()) { - if (m_size + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Stack too deep.")); + if (!_move) // copy values + { + 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) - *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 - *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; + solAssert(!_move, "Move assign for non-value types not implemented."); + solAssert(_sourceType.getCategory() == _expression.getType()->getCategory(), ""); + if (_expression.getType()->getCategory() == Type::Category::ByteArray) + copyByteArrayToStorage(dynamic_cast(*_expression.getType()), + dynamic_cast(_sourceType)); + else if (_expression.getType()->getCategory() == Type::Category::Struct) + { + //@todo + solAssert(false, "Struct copy not yet implemented."); + } 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; + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Invalid non-value type for assignment.")); } break; 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.")); } +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."); + } +} + } } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 3c94d74c2..bd45e067d 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -148,7 +148,8 @@ private: /// 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 /// 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. /// @a _expression is the current expression, used for error reporting. void setToZero(Expression const& _expression) const; @@ -159,6 +160,8 @@ private: private: /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue 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; LValueType m_type = LValueType::None; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 9faa3f3c0..16674742e 100644 --- a/libsolidity/Types.cpp +++ b/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); } +TypePointer ByteArrayType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Delete) + return make_shared(); + return TypePointer(); +} + bool ByteArrayType::operator==(Type const& _other) const { 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; } +unsigned ByteArrayType::getSizeOnStack() const +{ + if (m_location == Location::CallData) + return 0; + else + return 1; +} + bool ContractType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -963,8 +978,7 @@ MagicType::MagicType(MagicType::Kind _kind): m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::Address)}, {"gas", make_shared(256)}, {"value", make_shared(256)}, - {"data", make_shared(ByteArrayType::Location::CallData, - 0, 0, true)}}); + {"data", make_shared(ByteArrayType::Location::CallData)}}); break; case Kind::Transaction: m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::Address)}, diff --git a/libsolidity/Types.h b/libsolidity/Types.h index ddcfad9ed..a23790fa2 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -285,13 +285,20 @@ public: enum class Location { Storage, CallData, Memory }; 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) {} 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 unsigned getSizeOnStack() const override { return 1; /* TODO */ } + virtual unsigned getSizeOnStack() const override; 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: Location m_location; u256 m_offset; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 054b0c6a4..ecf7ba8a5 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -2254,7 +2254,26 @@ BOOST_AUTO_TEST_CASE(generic_call) 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"( contract C { @@ -2267,16 +2286,56 @@ BOOST_AUTO_TEST_CASE(storing_bytes) function clear() { delete savedData; } - function doubleIt(uint a) returns (uint b) { return a * 2; } + function doubleIt(uint a) { val += a * 2; } bytes savedData; + uint public val; } )"; compileAndRun(sourceCode); FixedHash<4> innerHash(dev::sha3("doubleIt(uint256)")); 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("val()") == bytes(14)); 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()