diff --git a/Changelog.md b/Changelog.md index b55713488..755c958a3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.6.0 (unreleased) Language Features: + * Allow calldata arrays with dynamically encoded base types with ABIEncoderV2. Compiler Features: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 0109a7b03..41c3003df 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -372,16 +372,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function) for (ASTPointer const& var: _function.parameters()) { TypePointer baseType = type(*var); - if (auto const* arrayType = dynamic_cast(baseType.get())) - { - baseType = arrayType->baseType(); - if ( - !m_scope->isInterface() && - baseType->dataStoredIn(DataLocation::CallData) && - baseType->isDynamicallyEncoded() - ) - m_errorReporter.typeError(var->location(), "Calldata arrays with dynamically encoded base types are not yet supported."); - } while (auto const* arrayType = dynamic_cast(baseType.get())) baseType = arrayType->baseType(); diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 72c361c39..e96b871b5 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -1354,13 +1354,10 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) { - // This does not work with arrays of complex types - the array access - // is not yet implemented in Solidity. solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - if (_type.baseType()->isDynamicallyEncoded()) - solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity."); + solAssert(_type.baseType()->calldataEncodedSize() > 0, ""); solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); string functionName = @@ -1376,7 +1373,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) length := calldataload(offset) if gt(length, 0xffffffffffffffff) { revert(0, 0) } arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } } )"; else @@ -1391,7 +1388,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); - w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + if (!_type.isDynamicallySized()) + w("length", toCompactHexWithPrefix(_type.length())); return w.render(); }); } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 39189046f..5c4a65323 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1030,7 +1030,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept } } -void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const +void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const { /// Stack: reference [length] index DataLocation location = _arrayType.location(); @@ -1050,28 +1050,41 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c m_context << Instruction::SWAP1 << Instruction::POP; // stack: - m_context << Instruction::SWAP1; - // stack: switch (location) { case DataLocation::Memory: case DataLocation::CallData: - if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - if (!_arrayType.isByteArray()) { - m_context << Instruction::SWAP1; if (location == DataLocation::CallData) - m_context << _arrayType.baseType()->calldataEncodedSize(); + { + if (_arrayType.baseType()->isDynamicallyEncoded()) + m_context << u256(0x20); + else + m_context << _arrayType.baseType()->calldataEncodedSize(); + } else m_context << u256(_arrayType.memoryHeadSize()); m_context << Instruction::MUL; } + // stack: + + if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) + m_context << u256(32) << Instruction::ADD; + + if (_keepReference) + m_context << Instruction::DUP2; + m_context << Instruction::ADD; break; case DataLocation::Storage: { + if (_keepReference) + m_context << Instruction::DUP2; + else + m_context << Instruction::SWAP1; + // stack: [] + eth::AssemblyItem endTag = m_context.newTag(); if (_arrayType.isByteArray()) { diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index daf50bf52..af8e3e966 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -97,11 +97,13 @@ public: /// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef. /// If @a _arrayType is a byte array, takes tight coding into account. void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const; - /// Performs bounds checking and returns a reference on the stack. + /// Checks whether the index is out of range and returns the absolute offset of the element reference[index] + /// (i.e. reference + index * size_of_base_type). + /// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack. /// Stack pre: reference [length] index - /// Stack post (storage): storage_slot byte_offset - /// Stack post: memory/calldata_offset - void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; + /// Stack post (storage): [reference] storage_slot byte_offset + /// Stack post: [reference] memory/calldata_offset + void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; private: /// Adds the given number of bytes to a storage byte offset counter and also increments diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a72b31dfe..91912758f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1551,10 +1551,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) _indexAccess.indexExpression()->accept(*this); utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true); // stack layout: [] - ArrayUtils(m_context).accessIndex(arrayType); switch (arrayType.location()) { case DataLocation::Storage: + ArrayUtils(m_context).accessIndex(arrayType); if (arrayType.isByteArray()) { solAssert(!arrayType.isString(), "Index access to string is not allowed."); @@ -1564,18 +1564,75 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) setLValueToStorageItem(_indexAccess); break; case DataLocation::Memory: + ArrayUtils(m_context).accessIndex(arrayType); setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); break; case DataLocation::CallData: - //@todo if we implement this, the value in calldata has to be added to the base offset - solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); - if (arrayType.baseType()->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); + if (arrayType.baseType()->isDynamicallyEncoded()) + { + // stack layout: + ArrayUtils(m_context).accessIndex(arrayType, true, true); + // stack layout: + + unsigned int baseEncodedSize = arrayType.baseType()->calldataEncodedSize(); + solAssert(baseEncodedSize > 1, ""); + + // returns the absolute offset of the accessed element in "base_ref" + m_context.appendInlineAssembly(Whiskers(R"({ + let rel_offset_of_tail := calldataload(ptr_to_tail) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + base_ref := add(base_ref, rel_offset_of_tail) + })")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"}); + // stack layout: + + if (!arrayType.baseType()->isDynamicallySized()) + { + m_context << Instruction::POP; + // stack layout: + solAssert( + arrayType.baseType()->category() == Type::Category::Struct || + arrayType.baseType()->category() == Type::Category::Array, + "Invalid dynamically encoded base type on array access." + ); + } + else + { + auto const* baseArrayType = dynamic_cast(arrayType.baseType().get()); + solAssert(!!baseArrayType, "Invalid dynamically sized type."); + unsigned int calldataStride = baseArrayType->calldataStride(); + solAssert(calldataStride > 0, ""); + + // returns the absolute offset of the accessed element in "base_ref" + // and the length of the accessed element in "ptr_to_length" + m_context.appendInlineAssembly( + Whiskers(R"({ + length := calldataload(base_ref) + base_ref := add(base_ref, 0x20) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + })")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), + {"base_ref", "length"} + ); + // stack layout: + } + } + else + { + ArrayUtils(m_context).accessIndex(arrayType, true); + if (arrayType.baseType()->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic( + *arrayType.baseType(), + true, + !arrayType.isByteArray(), + false + ); + else + solAssert( + arrayType.baseType()->category() == Type::Category::Struct || + arrayType.baseType()->category() == Type::Category::Array, + "Invalid statically sized non-value base type on array access." + ); + } break; } } diff --git a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol index a732b4462..1fc586868 100644 --- a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol +++ b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic.sol @@ -4,4 +4,3 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (65-82): Calldata arrays with dynamically encoded base types are not yet supported. diff --git a/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol new file mode 100644 index 000000000..0ee5135a6 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/calldata_multi_dynamic_V1.sol @@ -0,0 +1,7 @@ +contract Test { + function f(uint[][] calldata) external { } + function g(uint[][1] calldata) external { } +} +// ---- +// TypeError: (31-48): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature. +// TypeError: (78-96): This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature. diff --git a/test/libsolidity/syntaxTests/structs/array_calldata.sol b/test/libsolidity/syntaxTests/structs/array_calldata.sol index 8da097884..9e1071a0e 100644 --- a/test/libsolidity/syntaxTests/structs/array_calldata.sol +++ b/test/libsolidity/syntaxTests/structs/array_calldata.sol @@ -6,4 +6,3 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (131-145): Calldata arrays with dynamically encoded base types are not yet supported.