diff --git a/Changelog.md b/Changelog.md index c13eebb7b..6f97f3841 100644 --- a/Changelog.md +++ b/Changelog.md @@ -22,6 +22,7 @@ Compiler Features: Bugfixes: * ABI decoder: Ensure that decoded arrays always point to distinct memory locations. + * Code Generator: Treat dynamically encoded but statically sized arrays and structs in calldata properly. * SMTChecker: Fix internal error when inlining functions that contain tuple expressions. * SMTChecker: Fix pointer knowledge erasing in loops. * SMTChecker: Fix internal error when using compound bitwise assignment operators inside branches. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 98d3b075b..556347980 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1647,15 +1647,13 @@ bool ArrayType::validForCalldata() const if (auto arrayBaseType = dynamic_cast(baseType())) if (!arrayBaseType->validForCalldata()) return false; - return unlimitedCalldataEncodedSize(true) <= numeric_limits::max(); + return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits::max(); } -bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const +bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const { - if (isDynamicallySized()) - return 32; - // Array elements are always padded. - bigint size = bigint(length()) * (isByteArray() ? 1 : baseType()->calldataEncodedSize(true)); + solAssert(!isDynamicallySized(), ""); + bigint size = bigint(length()) * calldataStride(); if (_padded) size = ((size + 31) / 32) * 32; return size; @@ -1663,7 +1661,20 @@ bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const unsigned ArrayType::calldataEncodedSize(bool _padded) const { - bigint size = unlimitedCalldataEncodedSize(_padded); + solAssert(!isDynamicallyEncoded(), ""); + bigint size = unlimitedStaticCalldataSize(_padded); + solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); + return unsigned(size); +} + +unsigned ArrayType::calldataEncodedTailSize() const +{ + solAssert(isDynamicallyEncoded(), ""); + if (isDynamicallySized()) + // We do not know the dynamic length itself, but at least the uint256 containing the + // length must still be present. + return 32; + bigint size = unlimitedStaticCalldataSize(false); solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); return unsigned(size); } @@ -1832,10 +1843,11 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const return result; } -u256 ArrayType::memorySize() const +u256 ArrayType::memoryDataSize() const { solAssert(!isDynamicallySized(), ""); solAssert(m_location == DataLocation::Memory, ""); + solAssert(!isByteArray(), ""); bigint size = bigint(m_length) * m_baseType->memoryHeadSize(); solAssert(size <= numeric_limits::max(), "Array size does not fit u256."); return u256(size); @@ -1992,20 +2004,33 @@ bool StructType::operator==(Type const& _other) const return ReferenceType::operator==(other) && other.m_struct == m_struct; } + unsigned StructType::calldataEncodedSize(bool) const { + solAssert(!isDynamicallyEncoded(), ""); + unsigned size = 0; for (auto const& member: members(nullptr)) - if (!member.type->canLiveOutsideStorage()) - return 0; - else - { - // Struct members are always padded. - unsigned memberSize = member.type->calldataEncodedSize(true); - if (memberSize == 0) - return 0; - size += memberSize; - } + { + solAssert(member.type->canLiveOutsideStorage(), ""); + // Struct members are always padded. + size += member.type->calldataEncodedSize(); + } + return size; +} + + +unsigned StructType::calldataEncodedTailSize() const +{ + solAssert(isDynamicallyEncoded(), ""); + + unsigned size = 0; + for (auto const& member: members(nullptr)) + { + solAssert(member.type->canLiveOutsideStorage(), ""); + // Struct members are always padded. + size += member.type->calldataHeadSize(); + } return size; } @@ -2017,12 +2042,8 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const solAssert(member.type->canLiveOutsideStorage(), ""); if (member.name == _member) return offset; - { - // Struct members are always padded. - unsigned memberSize = member.type->calldataEncodedSize(true); - solAssert(memberSize != 0, ""); - offset += memberSize; - } + // Struct members are always padded. + offset += member.type->calldataHeadSize(); } solAssert(false, "Struct member not found."); } @@ -2040,7 +2061,7 @@ bool StructType::isDynamicallyEncoded() const return false; } -u256 StructType::memorySize() const +u256 StructType::memoryDataSize() const { u256 size; for (auto const& t: memoryMemberTypes()) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 11c9175d8..992d95552 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -208,16 +208,32 @@ public: virtual bool operator==(Type const& _other) const { return category() == _other.category(); } virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } - /// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type, - /// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded - /// in calldata. + /// @returns number of bytes used by this type when encoded for CALL. Cannot be used for + /// dynamically encoded types. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata + /// (or is dynamically encoded). /// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes. - virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; } + virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; solAssert(false, ""); } + /// Convenience version of @see calldataEncodedSize(bool) + unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } + /// @returns the distance between two elements of this type in a calldata array, tuple or struct. + /// For statically encoded types this is the same as calldataEncodedSize(true). + /// For dynamically encoded types this is the distance between two tail pointers, i.e. 32. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata. + unsigned calldataHeadSize() const { return isDynamicallyEncoded() ? 32 : calldataEncodedSize(true); } + /// @returns the (minimal) size of the calldata tail for this type. Can only be used for + /// dynamically encoded types. For dynamically-sized arrays this is 32 (the size of the length), + /// for statically-sized, but dynamically encoded arrays this is 32*length(), for structs + /// this is the sum of the calldataHeadSize's of its members. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata + /// (or is not dynamically encoded). + virtual unsigned calldataEncodedTailSize() const { solAssert(false, ""); } /// @returns the size of this data type in bytes when stored in memory. For memory-reference /// types, this is the size of the memory pointer. virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); } - /// Convenience version of @see calldataEncodedSize(bool) - unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } + /// @returns the size of this data type in bytes when stored in memory. For memory-reference + /// types, this is the size of the actual data area, if it is statically-sized. + virtual u256 memoryDataSize() const { return calldataEncodedSize(); } /// @returns true if the type is a dynamic array virtual bool isDynamicallySized() const { return false; } /// @returns true if the type is dynamically encoded in the ABI @@ -634,6 +650,10 @@ public: return nullptr; } unsigned memoryHeadSize() const override { return 32; } + u256 memoryDataSize() const override = 0; + + unsigned calldataEncodedSize(bool) const override = 0; + unsigned calldataEncodedTailSize() const override = 0; /// @returns a copy of this type with location (recursively) changed to @a _location, /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. @@ -701,7 +721,8 @@ public: BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - unsigned calldataEncodedSize(bool _padded) const override; + unsigned calldataEncodedSize(bool) const override; + unsigned calldataEncodedTailSize() const override; bool isDynamicallySized() const override { return m_hasDynamicLength; } bool isDynamicallyEncoded() const override; u256 storageSize() const override; @@ -724,12 +745,12 @@ public: bool isString() const { return m_arrayKind == ArrayKind::String; } Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; } u256 const& length() const { return m_length; } - u256 memorySize() const; + u256 memoryDataSize() const override; std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const override; /// The offset to advance in calldata to move from one array element to the next. - unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataEncodedSize(); } + unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataHeadSize(); } /// The offset to advance in memory to move from one array element to the next. unsigned memoryStride() const { return isByteArray() ? 1 : m_baseType->memoryHeadSize(); } /// The offset to advance in storage to move from one array element to the next. @@ -741,7 +762,7 @@ private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; - bigint unlimitedCalldataEncodedSize(bool _padded) const; + bigint unlimitedStaticCalldataSize(bool _padded) const; ///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays. ArrayKind m_arrayKind = ArrayKind::Ordinary; @@ -829,9 +850,10 @@ public: BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - unsigned calldataEncodedSize(bool _padded) const override; + unsigned calldataEncodedSize(bool) const override; + unsigned calldataEncodedTailSize() const override; bool isDynamicallyEncoded() const override; - u256 memorySize() const; + u256 memoryDataSize() const override; u256 storageSize() const override; bool canLiveOutsideStorage() const override { return true; } std::string toString(bool _short) const override; diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 2a845bfe3..03dc40c5b 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -88,7 +88,7 @@ string ABIFunctions::tupleEncoder( elementTempl("pos", to_string(headPos)); elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); encodeElements += elementTempl.render(); - headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); + headPos += _targetTypes[i]->calldataHeadSize(); stackPos += sizeOnStack; } solAssert(headPos == headSize_, ""); @@ -225,7 +225,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) elementTempl("pos", to_string(headPos)); elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); decodeElements += elementTempl.render(); - headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); + headPos += decodingTypes[i]->calldataHeadSize(); } templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); templ("arrow", valueReturnParams.empty() ? "" : "->"); @@ -691,6 +691,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( // Multiple items per slot solAssert(_from.baseType()->storageBytes() <= 16, ""); solAssert(!_from.baseType()->isDynamicallyEncoded(), ""); + solAssert(!_to.baseType()->isDynamicallyEncoded(), ""); solAssert(_from.baseType()->isValueType(), ""); bool dynamic = _to.isDynamicallyEncoded(); size_t storageBytes = _from.baseType()->storageBytes(); @@ -715,7 +716,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( let data := sload(srcPtr) <#items> ((data), pos) - pos := add(pos, ) + pos := add(pos, ) srcPtr := add(srcPtr, 1) } @@ -726,7 +727,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( <#items> if { ((data), pos) - pos := add(pos, ) + pos := add(pos, ) itemCounter := add(itemCounter, 1) } @@ -753,9 +754,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( else templ("useSpill", "0"); templ("itemsPerSlot", to_string(itemsPerSlot)); - // We use padded size because array elements are always padded. - string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); - templ("elementEncodedSize", elementEncodedSize); + templ("stride", toCompactHexWithPrefix(_to.calldataStride())); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; @@ -914,7 +913,7 @@ string ABIFunctions::abiEncodingFunctionStruct( ); encodeTempl("memberValues", memberValues); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); - encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); + encodingOffset += memberTypeTo->calldataHeadSize(); encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); encode = encodeTempl.render(); } @@ -1080,8 +1079,8 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM solAssert(decodingType, ""); solAssert(decodingType->sizeOnStack() == 1, ""); solAssert(decodingType->isValueType(), ""); - solAssert(decodingType->calldataEncodedSize() == 32, ""); solAssert(!decodingType->isDynamicallyEncoded(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); string functionName = "abi_decode_" + @@ -1135,7 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from let elementPos := mstore(dst, (elementPos, end)) dst := add(dst, 0x20) - src := add(src, ) + src := add(src, ) } } )" @@ -1145,6 +1144,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); templ("allocate", m_utils.allocationFunction()); templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + string calldataStride = toCompactHexWithPrefix(_type.calldataStride()); + templ("stride", calldataStride); if (_type.isDynamicallySized()) templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); else @@ -1153,14 +1154,11 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from { templ("staticBoundsCheck", ""); templ("retrieveElementPos", "add(offset, " + load + "(src))"); - templ("baseEncodedSize", "0x20"); } else { - string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); - templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }"); + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }"); templ("retrieveElementPos", "src"); - templ("baseEncodedSize", baseEncodedSize); } templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); return templ.render(); @@ -1172,8 +1170,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - solAssert(_type.baseType()->calldataEncodedSize() > 0, ""); - solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); + solAssert(_type.calldataStride() > 0, ""); + solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), ""); string functionName = "abi_decode_" + @@ -1188,7 +1186,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(length, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } } )"; else @@ -1196,13 +1194,13 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) // function (offset, end) -> arrayPos { arrayPos := offset - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } } )"; Whiskers w{templ}; w("functionName", functionName); w("readableTypeName", _type.toString(true)); - w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); + w("stride", toCompactHexWithPrefix(_type.calldataStride())); if (!_type.isDynamicallySized()) w("length", toCompactHexWithPrefix(_type.length())); return w.render(); @@ -1246,7 +1244,6 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) { solAssert(_type.dataStoredIn(DataLocation::CallData), ""); - solAssert(_type.calldataEncodedSize(true) != 0, ""); string functionName = "abi_decode_" + _type.identifier(); @@ -1261,7 +1258,7 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) )"}; w("functionName", functionName); w("readableTypeName", _type.toString(true)); - w("minimumSize", to_string(_type.calldataEncodedSize(true))); + w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); return w.render(); }); } @@ -1291,8 +1288,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("allocate", m_utils.allocationFunction()); - solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); - templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); + solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize())); size_t headPos = 0; vector> members; for (auto const& member: _type.members(nullptr)) @@ -1322,7 +1319,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr members.push_back({}); members.back()["decode"] = memberTempl.render(); members.back()["memberName"] = member.name; - headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); + headPos += decodingType->calldataHeadSize(); } templ("members", members); templ("minimumSize", toCompactHexWithPrefix(headPos)); @@ -1376,8 +1373,8 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) return createFunction(functionName, [&]() { if (_type.isDynamicallyEncoded()) { - unsigned int baseEncodedSize = _type.calldataEncodedSize(); - solAssert(baseEncodedSize > 1, ""); + unsigned int tailSize = _type.calldataEncodedTailSize(); + solAssert(tailSize > 1, ""); Whiskers w(R"( function (base_ref, ptr) -> { let rel_offset_of_tail := calldataload(ptr) @@ -1390,13 +1387,12 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) { auto const* arrayType = dynamic_cast(&_type); solAssert(!!arrayType, ""); - unsigned int calldataStride = arrayType->calldataStride(); w("handleLength", Whiskers(R"( length := calldataload(value) value := add(value, 0x20) if gt(length, 0xffffffffffffffff) { revert(0, 0) } if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } - )")("calldataStride", toCompactHexWithPrefix(calldataStride)).render()); + )")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render()); w("return", "value, length"); } else @@ -1404,7 +1400,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) w("handleLength", ""); w("return", "value"); } - w("neededLength", toCompactHexWithPrefix(baseEncodedSize)); + w("neededLength", toCompactHexWithPrefix(tailSize)); w("functionName", functionName); return w.render(); } @@ -1483,12 +1479,7 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes) { size_t headSize = 0; for (auto const& t: _targetTypes) - { - if (t->isDynamicallyEncoded()) - headSize += 0x20; - else - headSize += t->calldataEncodedSize(); - } + headSize += t->calldataHeadSize(); return headSize; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 7c01a8c52..b1294bcef 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -243,7 +243,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons else if (_sourceType.location() == DataLocation::Memory) _context << sourceBaseType->memoryHeadSize(); else - _context << sourceBaseType->calldataEncodedSize(true); + _context << sourceBaseType->calldataHeadSize(); _context << Instruction::ADD << swapInstruction(2 + byteOffsetSize); @@ -294,20 +294,13 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord "Nested dynamic arrays not implemented here." ); CompilerUtils utils(m_context); - unsigned baseSize = 1; - if (!_sourceType.isByteArray()) - { - // We always pad the elements, regardless of _padToWordBoundaries. - baseSize = _sourceType.baseType()->calldataEncodedSize(); - solAssert(baseSize >= 0x20, ""); - } if (_sourceType.location() == DataLocation::CallData) { if (!_sourceType.isDynamicallySized()) m_context << _sourceType.length(); - if (baseSize > 1) - m_context << u256(baseSize) << Instruction::MUL; + if (!_sourceType.isByteArray()) + convertLengthToSize(_sourceType); string routine = "calldatacopy(target, source, len)\n"; if (_padToWordBoundaries) @@ -358,15 +351,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; m_context << Instruction::SWAP1; } - // convert length to size - if (baseSize > 1) - m_context << u256(baseSize) << Instruction::MUL; + if (!_sourceType.isByteArray()) + convertLengthToSize(_sourceType); // stack: m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; // We can resort to copying full 32 bytes only if // - the length is known to be a multiple of 32 or // - we will pad to full 32 bytes later anyway. - if (((baseSize % 32) == 0) || _padToWordBoundaries) + if (!_sourceType.isByteArray() || _padToWordBoundaries) utils.memoryCopy32(); else utils.memoryCopy(); @@ -374,11 +366,8 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::SWAP1 << Instruction::POP; // stack: - bool paddingNeeded = false; - if (_sourceType.isDynamicallySized()) - paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); - else - paddingNeeded = _padToWordBoundaries && (((_sourceType.length() * baseSize) % 32) != 0); + bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArray(); + if (paddingNeeded) { // stack: @@ -455,10 +444,10 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context.appendJumpTo(loopEnd); m_context << longByteArray; } - // compute memory end offset - if (baseSize > 1) + else // convert length to memory size - m_context << u256(baseSize) << Instruction::MUL; + m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL; + m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; if (_sourceType.isDynamicallySized()) { @@ -515,7 +504,12 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset if (haveByteOffset) m_context << Instruction::SWAP1 << Instruction::POP; - if (_padToWordBoundaries && baseSize % 32 != 0) + if (!_sourceType.isByteArray()) + { + solAssert(_sourceType.calldataStride() % 32 == 0, ""); + solAssert(_sourceType.memoryStride() % 32 == 0, ""); + } + if (_padToWordBoundaries && _sourceType.isByteArray()) { // memory_end_offset - start is the actual length (we want to compute the ceil of). // memory_offset - start is its next multiple of 32, but it might be off by 32. @@ -987,9 +981,9 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con if (!_arrayType.isByteArray()) { if (_arrayType.location() == DataLocation::Memory) - m_context << _arrayType.baseType()->memoryHeadSize(); + m_context << _arrayType.memoryStride(); else - m_context << _arrayType.baseType()->calldataEncodedSize(); + m_context << _arrayType.calldataStride(); m_context << Instruction::MUL; } else if (_pad) @@ -1067,10 +1061,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b case DataLocation::CallData: if (!_arrayType.isByteArray()) { - if (_arrayType.baseType()->isDynamicallyEncoded()) - m_context << u256(0x20); - else - m_context << _arrayType.baseType()->calldataEncodedSize(); + m_context << _arrayType.calldataStride(); m_context << Instruction::MUL; } // stack: diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index a20bfbe30..a275a6e03 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -98,16 +98,17 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType) void CompilerUtils::accessCalldataTail(Type const& _type) { solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + solAssert(_type.isDynamicallyEncoded(), ""); - unsigned int baseEncodedSize = _type.calldataEncodedSize(); - solAssert(baseEncodedSize > 1, ""); + unsigned int tailSize = _type.calldataEncodedTailSize(); + solAssert(tailSize > 1, ""); // returns the absolute offset of the tail 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"}); + })")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"}); // stack layout: if (!_type.isDynamicallySized()) @@ -170,7 +171,7 @@ void CompilerUtils::loadFromMemoryDynamic( solAssert(!_fromCalldata, ""); solAssert(_padToWordBoundaries, ""); if (_keepUpdatedMemoryOffset) - m_context << arrayType->memorySize() << Instruction::ADD; + m_context << arrayType->memoryDataSize() << Instruction::ADD; } else { @@ -251,7 +252,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem //@todo this does not yet support nested dynamic arrays size_t encodedSize = 0; for (auto const& t: _typeParameters) - encodedSize += t->decodingType()->calldataEncodedSize(true); + encodedSize += t->decodingType()->calldataHeadSize(); m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"}); m_context << Instruction::DUP2 << Instruction::ADD; @@ -321,7 +322,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // Size has already been checked for this one. moveIntoStack(2); m_context << Instruction::DUP3; - m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD; + m_context << u256(arrayType.calldataHeadSize()) << Instruction::ADD; } } else @@ -358,7 +359,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // size has already been checked // stack: input_end base_offset data_offset m_context << Instruction::DUP1; - m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD; + m_context << u256(calldataType->calldataHeadSize()) << Instruction::ADD; } if (arrayType.location() == DataLocation::Memory) { @@ -588,11 +589,6 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) } else { - // TODO: Potential optimization: - // When we create a new multi-dimensional dynamic array, each element - // is initialized to an empty array. It actually does not hurt - // to re-use exactly the same empty array for all elements. Currently, - // a new one is created each time. auto repeat = m_context.newTag(); m_context << repeat; pushZeroValue(*_type.baseType()); @@ -1014,7 +1010,7 @@ void CompilerUtils::convertType( { CompilerUtils utils(_context); // stack: - utils.allocateMemory(typeOnStack->memorySize()); + utils.allocateMemory(typeOnStack->memoryDataSize()); _context << Instruction::SWAP1 << Instruction::DUP2; // stack: for (auto const& member: typeOnStack->members(nullptr)) @@ -1197,7 +1193,8 @@ void CompilerUtils::pushZeroValue(Type const& _type) 1, [type](CompilerContext& _context) { CompilerUtils utils(_context); - utils.allocateMemory(max(32u, type->calldataEncodedSize())); + + utils.allocateMemory(max(32u, type->memoryDataSize())); _context << Instruction::DUP1; if (auto structType = dynamic_cast(type)) @@ -1364,6 +1361,8 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) { + solAssert(_type.isValueType(), ""); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast(&_type)) @@ -1436,6 +1435,8 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) "Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")." ); + solAssert(!_type.isDynamicallyEncoded(), ""); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); solAssert( diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index f393c6a6a..c34684840 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -317,7 +317,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) ArrayType const& arrayType = dynamic_cast(*_tuple.annotation().type); solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); - utils().allocateMemory(max(u256(32u), arrayType.memorySize())); + utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize())); m_context << Instruction::DUP1; for (auto const& component: _tuple.components()) @@ -526,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); - utils().allocateMemory(max(u256(32u), structType.memorySize())); + utils().allocateMemory(max(u256(32u), structType.memoryDataSize())); m_context << Instruction::DUP1; for (unsigned i = 0; i < arguments.size(); ++i) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 14dcc201c..63e264185 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -652,11 +652,11 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) size := length - size := (length, ) + size := (length, ) })") ("functionName", functionName) - ("elementSize", to_string(_type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize())) + ("stride", to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride())) ("byteArray", _type.isByteArray()) ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) .render(); @@ -812,10 +812,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) } case DataLocation::CallData: { - u256 size = - _type.baseType()->isDynamicallyEncoded() ? - 32 : - _type.baseType()->calldataEncodedSize(); + u256 size = _type.calldataStride(); solAssert(size >= 32 && size % 32 == 0, ""); templ("advance", toCompactHexWithPrefix(size)); break; diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_decode.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_decode.sol new file mode 100644 index 000000000..a89b6336a --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_decode.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][2][] calldata x) external returns (uint256) { + x[0]; // trigger bounds checks + return 23; + } +} +// ---- +// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x40, 0x60, 0x00, 0x00 -> 23 # this is the common encoding for x.length == 1 && x[0][0].length == 0 && x[0][1].length == 0 # +// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00, 0x00 -> 23 # exotic, but still valid encoding # +// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE # invalid (too short) encoding, but no failure due to this PR # diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_reencode.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_reencode.sol new file mode 100644 index 000000000..d5802d1b1 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_array_dynamic_static_short_reencode.sol @@ -0,0 +1,13 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][2][] calldata x) external returns (uint256) { + return 42; + } + function g(uint256[][2][] calldata x) external returns (uint256) { + return this.f(x); + } +} +// ---- +// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x40, 0x60, 0x00, 0x00 -> 42 +// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00, 0x00 -> 42 +// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_member_offset.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_member_offset.sol new file mode 100644 index 000000000..fb8e6c3c5 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_struct_member_offset.sol @@ -0,0 +1,23 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct A { + uint256 a; + uint256[] b; + } + struct B { + A a; + uint256 b; + } + function g(B calldata b) external pure returns(uint256) { + return b.b; + } + function f() public view returns(uint256, uint256) { + uint256[] memory arr = new uint256[](20); + arr[0] = 31; arr[2] = 84; + B memory b = B(A(420, arr), 11); + return (b.b, this.g(b)); + } +} +// ---- +// f() -> 11, 11