mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Allow calldata arrays with dynamically encoded base type.
This commit is contained in:
parent
cdf29277f7
commit
f7c6eda2c3
@ -1,6 +1,7 @@
|
||||
### 0.6.0 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* Allow calldata arrays with dynamically encoded base types with ABIEncoderV2.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
|
@ -372,16 +372,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
||||
{
|
||||
TypePointer baseType = type(*var);
|
||||
if (auto const* arrayType = dynamic_cast<ArrayType const*>(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<ArrayType const*>(baseType.get()))
|
||||
baseType = arrayType->baseType();
|
||||
|
||||
|
@ -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(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), 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();
|
||||
});
|
||||
}
|
||||
|
@ -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: <base_ref> <index>
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: <index> <base_ref>
|
||||
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: <base_ref> <index * size>
|
||||
|
||||
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: [<base_ref>] <index> <base_ref>
|
||||
|
||||
eth::AssemblyItem endTag = m_context.newTag();
|
||||
if (_arrayType.isByteArray())
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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: <base_ref> [<length>] <index>
|
||||
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<MemoryItem>(_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: <base_ref> <length> <index>
|
||||
ArrayUtils(m_context).accessIndex(arrayType, true, true);
|
||||
// stack layout: <base_ref> <ptr_to_tail>
|
||||
|
||||
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(<neededLength>, 1)))) { revert(0, 0) }
|
||||
base_ref := add(base_ref, rel_offset_of_tail)
|
||||
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
||||
|
||||
if (!arrayType.baseType()->isDynamicallySized())
|
||||
{
|
||||
m_context << Instruction::POP;
|
||||
// stack layout: <absolute_offset_of_element>
|
||||
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 const*>(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, <calldataStride>))) { revert(0, 0) }
|
||||
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
||||
{"base_ref", "length"}
|
||||
);
|
||||
// stack layout: <absolute_offset_of_element> <length_of_element>
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user