diff options
-rw-r--r-- | libsolidity/ExpressionCompiler.cpp | 81 | ||||
-rw-r--r-- | libsolidity/Types.cpp | 26 | ||||
-rw-r--r-- | libsolidity/Types.h | 12 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 40 |
4 files changed, 135 insertions, 24 deletions
diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f6eed0de..85302afc 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -623,6 +623,44 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) appendExternalFunctionCall(function, arguments); break; } + case Location::ByteArrayPush: + case Location::ArrayPush: + { + _functionCall.expression().accept(*this); + solAssert(function.parameterTypes().size() == 1, ""); + solAssert(!!function.parameterTypes()[0], ""); + TypePointer const& paramType = function.parameterTypes()[0]; + shared_ptr<ArrayType> arrayType = + function.location() == Location::ArrayPush ? + make_shared<ArrayType>(DataLocation::Storage, paramType) : + make_shared<ArrayType>(DataLocation::Storage); + // get the current length + ArrayUtils(m_context).retrieveLength(*arrayType); + m_context << eth::Instruction::DUP1; + // stack: ArrayReference currentLength currentLength + m_context << u256(1) << eth::Instruction::ADD; + // stack: ArrayReference currentLength newLength + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP2; + ArrayUtils(m_context).resizeDynamicArray(*arrayType); + m_context << eth::Instruction::SWAP2 << eth::Instruction::SWAP1; + // stack: newLength ArrayReference oldLength + ArrayUtils(m_context).accessIndex(*arrayType, false); + + // stack: newLength storageSlot slotOffset + arguments[0]->accept(*this); + // stack: newLength storageSlot slotOffset argValue + TypePointer type = arguments[0]->annotation().type; + utils().convertType(*type, *arrayType->baseType()); + type = arrayType->baseType(); + utils().moveToStackTop(1 + type->sizeOnStack()); + utils().moveToStackTop(1 + type->sizeOnStack()); + // stack: newLength argValue storageSlot slotOffset + if (function.location() == Location::ArrayPush) + StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); + else + StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); + break; + } default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type.")); } @@ -784,26 +822,37 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) } case Type::Category::Array: { - solAssert(member == "length", "Illegal array member."); auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type); - if (!type.isDynamicallySized()) + if (member == "length") { - utils().popStackElement(type); - m_context << type.length(); - } - else - switch (type.location()) + if (!type.isDynamicallySized()) { - case DataLocation::CallData: - m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; - break; - case DataLocation::Storage: - setLValue<StorageArrayLength>(_memberAccess, type); - break; - case DataLocation::Memory: - m_context << eth::Instruction::MLOAD; - break; + utils().popStackElement(type); + m_context << type.length(); } + else + switch (type.location()) + { + case DataLocation::CallData: + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + break; + case DataLocation::Storage: + setLValue<StorageArrayLength>(_memberAccess, type); + break; + case DataLocation::Memory: + m_context << eth::Instruction::MLOAD; + break; + } + } + else if (member == "push") + { + solAssert( + type.isDynamicallySized() && type.location() == DataLocation::Storage, + "Tried to use .push() on a non-dynamically sized array" + ); + } + else + solAssert(false, "Illegal array member."); break; } default: diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 3ccaaf5d..51df5fbf 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -858,6 +858,28 @@ string ArrayType::canonicalName(bool _addDataLocation) const return ret; } +MemberList const& ArrayType::members() const +{ + if (!m_members) + { + MemberList::MemberMap members; + if (!isString()) + { + members.push_back({"length", make_shared<IntegerType>(256)}); + if (isDynamicallySized() && location() == DataLocation::Storage) + members.push_back({"push", make_shared<FunctionType>( + TypePointers{baseType()}, + TypePointers{make_shared<IntegerType>(256)}, + strings{string()}, + strings{string()}, + isByteArray() ? FunctionType::Location::ByteArrayPush : FunctionType::Location::ArrayPush + )}); + } + m_members.reset(new MemberList(members)); + } + return *m_members; +} + TypePointer ArrayType::encodingType() const { if (location() == DataLocation::Storage) @@ -913,8 +935,6 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) return copy; } -const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared<IntegerType>(256)}}); - bool ContractType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1422,6 +1442,8 @@ unsigned FunctionType::sizeOnStack() const size = 1; else if (location == Location::Internal) size = 1; + else if (location == Location::ArrayPush || location == Location::ByteArrayPush) + size = 1; if (m_gasSet) size++; if (m_valueSet) diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e73cd3cd..5c4aacdc 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -505,10 +505,7 @@ public: virtual unsigned sizeOnStack() const override; virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; - virtual MemberList const& members() const override - { - return isString() ? EmptyMemberList : s_arrayTypeMemberList; - } + virtual MemberList const& members() const override; virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; @@ -532,7 +529,8 @@ private: TypePointer m_baseType; bool m_hasDynamicLength = true; u256 m_length; - static const MemberList s_arrayTypeMemberList; + /// List of member types, will be lazy-initialized because of recursive references. + mutable std::unique_ptr<MemberList> m_members; }; /** @@ -736,7 +734,9 @@ public: Event, ///< syntactic sugar for LOG* SetGas, ///< modify the default gas value for the function call SetValue, ///< modify the default value transfer for the function call - BlockHash ///< BLOCKHASH + BlockHash, ///< BLOCKHASH + ArrayPush, ///< .push() to a dynamically sized array in storage + ByteArrayPush ///< .push() to a dynamically sized byte array in storage }; virtual Category category() const override { return Category::Function; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index cccca0ba..bee19010 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3452,6 +3452,7 @@ BOOST_AUTO_TEST_CASE(array_copy_target_leftover2) asString(fromHex("0000000000000000")) )); } + BOOST_AUTO_TEST_CASE(array_copy_storage_storage_struct) { char const* sourceCode = R"( @@ -3476,6 +3477,45 @@ BOOST_AUTO_TEST_CASE(array_copy_storage_storage_struct) BOOST_CHECK(m_state.storage(m_contractAddress).empty()); } +BOOST_AUTO_TEST_CASE(array_push) +{ + char const* sourceCode = R"( + contract c { + uint[] data; + function test() returns (uint x, uint y, uint z, uint l) { + data.push(5); + x = data[0]; + data.push(4); + y = data[1]; + l = data.push(3); + z = data[2]; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(5, 4, 3, 3)); +} + +BOOST_AUTO_TEST_CASE(byte_array_push) +{ + char const* sourceCode = R"( + contract c { + bytes data; + function test() returns (bool x) { + if (data.push(5) != 1) return true; + if (data[0] != 5) return true; + data.push(4); + if (data[1] != 4) return true; + uint l = data.push(3); + if (data[2] != 3) return true; + if (l != 3) return true; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(false)); +} + BOOST_AUTO_TEST_CASE(external_array_args) { char const* sourceCode = R"( |