Merge pull request #7178 from ethereum/calldataSizeTest

Split calldataEncodedSize.
This commit is contained in:
chriseth 2019-08-08 16:20:52 +02:00 committed by GitHub
commit 16efcfdbb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 194 additions and 123 deletions

View File

@ -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.

View File

@ -1647,15 +1647,13 @@ bool ArrayType::validForCalldata() const
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
if (!arrayBaseType->validForCalldata())
return false;
return unlimitedCalldataEncodedSize(true) <= numeric_limits<unsigned>::max();
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::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<unsigned>::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<unsigned>::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<unsigned>::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())

View File

@ -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<ReferenceType> 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;

View File

@ -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>
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
pos := add(pos, <elementEncodedSize>)
pos := add(pos, <stride>)
</items>
srcPtr := add(srcPtr, 1)
}
@ -726,7 +727,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
<#items>
if <inRange> {
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
pos := add(pos, <elementEncodedSize>)
pos := add(pos, <stride>)
itemCounter := add(itemCounter, 1)
}
</items>
@ -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 := <retrieveElementPos>
mstore(dst, <decodingFun>(elementPos, end))
dst := add(dst, 0x20)
src := add(src, <baseEncodedSize>)
src := add(src, <stride>)
}
}
)"
@ -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, <baseEncodedSize>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
}
)";
else
@ -1196,13 +1194,13 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos {
arrayPos := offset
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(<length>, <stride>)), 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<map<string, string>> 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 <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr)
@ -1390,13 +1387,12 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
{
auto const* arrayType = dynamic_cast<ArrayType const*>(&_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, <calldataStride>))) { 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;
}

View File

@ -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: <target> <source> <size>
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: <target> <size>
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: <target> <size>
@ -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: <base_ref> <index * size>

View File

@ -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(<neededLength>, 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: <absolute_offset_of_tail> <garbage>
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: <source ref>
utils.allocateMemory(typeOnStack->memorySize());
utils.allocateMemory(typeOnStack->memoryDataSize());
_context << Instruction::SWAP1 << Instruction::DUP2;
// stack: <memory ptr> <source ref> <memory ptr>
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<u256>(32u, type->memoryDataSize()));
_context << Instruction::DUP1;
if (auto structType = dynamic_cast<StructType const*>(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<FunctionType const*>(&_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(

View File

@ -317,7 +317,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_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<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*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)

View File

@ -652,11 +652,11 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
<?byteArray>
size := length
<!byteArray>
size := <mul>(length, <elementSize>)
size := <mul>(length, <stride>)
</byteArray>
})")
("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;

View File

@ -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 #

View File

@ -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

View File

@ -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