diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index f696c265c..0472dd588 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -908,6 +908,58 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type) }); } +string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + string functionName = "calldata_array_index_access_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (base_ref, length, index) -> addr, len { + if iszero(lt(index, length)) { invalid() } + addr := add(base_ref, mul(index, )) + + addr, len := (base_ref, addr) + + } + )") + ("functionName", functionName) + ("stride", to_string(_type.calldataStride())) + ("dynamicallySized", _type.isDynamicallySized()) + ("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded()) + ("dynamicallySizedBase", _type.baseType()->isDynamicallySized()) + ("arrayLen", toCompactHexWithPrefix(_type.length())) + ("accessCalldataTail", _type.baseType()->isDynamicallyEncoded() ? accessCalldataTailFunction(*_type.baseType()): "") + .render(); + }); +} + +string YulUtilFunctions::accessCalldataTailFunction(Type const& _type) +{ + solAssert(_type.isDynamicallyEncoded(), ""); + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + string functionName = "access_calldata_tail_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (base_ref, ptr_to_tail) -> addr, length { + 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) } + addr := add(base_ref, rel_offset_of_tail) + + length := calldataload(addr) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + addr := add(addr, 32) + + } + )") + ("functionName", functionName) + ("dynamicallySized", _type.isDynamicallySized()) + ("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize())) + ("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast(_type).calldataStride() : 0)) + .render(); + }); +} + string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); @@ -1932,7 +1984,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC function (memPtr) -> value { value := (memPtr) - value := (value) + (value) } )") diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 859290ffa..8c92d7b4f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -172,9 +172,14 @@ public: /// @returns the name of a function that returns the calldata address for the /// given array base ref and index. - /// signature: (baseRef, index) -> address + /// signature: (baseRef, index) -> offset[, length] std::string calldataArrayIndexAccessFunction(ArrayType const& _type); + /// @returns the name of a function that follows a calldata tail while performing + /// bounds checks. + /// signature: (baseRef, tailPointer) -> offset[, length] + std::string accessCalldataTailFunction(Type const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. /// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots. std::string nextArrayElementFunction(ArrayType const& _type); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 3ed5abad0..09a366160 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -815,7 +815,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) else if (member == "gasprice") define(_memberAccess) << "gasprice()\n"; else if (member == "data") - solUnimplementedAssert(false, ""); + { + IRVariable var(_memberAccess); + declare(var); + define(var.part("offset")) << "0\n"; + define(var.part("length")) << "calldatasize()\n"; + } else if (member == "sig") define(_memberAccess) << "and(calldataload(0), " << @@ -862,8 +867,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) switch (type.location()) { case DataLocation::CallData: - solUnimplementedAssert(false, ""); - //m_context << Instruction::SWAP1 << Instruction::POP; + define(_memberAccess, IRVariable(_memberAccess.expression()).part("length")); break; case DataLocation::Storage: { @@ -997,8 +1001,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) } case DataLocation::CallData: { - solUnimplemented("calldata not yet implemented!"); - + IRVariable var(m_context.newYulVariable(), *arrayType.baseType()); + define(var) << + m_utils.calldataArrayIndexAccessFunction(arrayType) << + "(" << + IRVariable(_indexAccess.baseExpression()).commaSeparatedList() << + ", " << + expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) << + ")\n"; + if (arrayType.isByteArray()) + define(_indexAccess) << + m_utils.cleanupFunction(*arrayType.baseType()) << + "(calldataload(" << + var.name() << + "))\n"; + else if (arrayType.baseType()->isValueType()) + define(_indexAccess) << + m_utils.readFromCalldata(*arrayType.baseType()) << + "(" << + var.commaSeparatedList() << + ")\n"; + else + define(_indexAccess, var); + break; } } } @@ -1534,7 +1559,10 @@ void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue solAssert(!m_currentLValue, ""); if (_expression.annotation().lValueRequested) + { m_currentLValue.emplace(std::move(_lvalue)); + solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), ""); + } else // Only define the expression, if it will not be written to. define(_expression, readFromLValue(_lvalue)); diff --git a/test/libsolidity/semanticTests/viaYul/calldata_array_access.sol b/test/libsolidity/semanticTests/viaYul/calldata_array_access.sol new file mode 100644 index 000000000..115015f03 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/calldata_array_access.sol @@ -0,0 +1,20 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[] calldata x, uint256 i) external returns (uint256) { + return x[i]; + } + function f(uint256[][] calldata x, uint256 i, uint256 j) external returns (uint256) { + return x[i][j]; + } +} +// ==== +// compileViaYul: also +// ---- +// f(uint256[],uint256): 0x40, 0, 0 -> FAILURE +// f(uint256[],uint256): 0x40, 0, 1, 23 -> 23 +// f(uint256[],uint256): 0x40, 1, 1, 23 -> FAILURE +// f(uint256[],uint256): 0x40, 0, 2, 23, 42 -> 23 +// f(uint256[],uint256): 0x40, 1, 2, 23, 42 -> 42 +// f(uint256[],uint256): 0x40, 2, 2, 23, 42 -> FAILURE +// f(uint256[][],uint256,uint256): 0x60, 0, 0 -> FAILURE +// f(uint256[][],uint256,uint256): 0x60, 0, 0, 1, 0x20, 1, 23 -> 23 diff --git a/test/libsolidity/semanticTests/viaYul/calldata_array_length.sol b/test/libsolidity/semanticTests/viaYul/calldata_array_length.sol new file mode 100644 index 000000000..507380667 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/calldata_array_length.sol @@ -0,0 +1,34 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[] calldata x) external returns (uint256) { + return x.length; + } + function f(uint256[][] calldata x) external returns (uint256 l1, uint256 l2, uint256 l3) { + l1 = x.length; + if (l1 > 0) l2 = x[0].length; + if (l1 > 1) l3 = x[1].length; + } + function f(uint256[2] calldata x) external returns (uint256) { + return x.length; + } +} +// ==== +// compileViaYul: also +// ---- +// f(uint256[]): 0x20, 0 -> 0 +// f(uint256[]): 0x20, 1, 23 -> 1 +// f(uint256[]): 0x20, 2, 23, 42 -> 2 +// f(uint256[]): 0x20, 3, 23, 42, 17 -> 3 +// f(uint256[2]): 23, 42 -> 2 +// f(uint256[][]): 0x20, 0 -> 0, 0, 0 +// f(uint256[][]): 0x20, 1, 0x20, 0 -> 1, 0, 0 +// f(uint256[][]): 0x20, 1, 0x00 -> 1, 0, 0 +// f(uint256[][]): 0x20, 1, 0x20, 1, 23 -> 1, 1, 0 +// f(uint256[][]): 0x20, 1, 0x20, 2, 23, 42 -> 1, 2, 0 +// f(uint256[][]): 0x20, 1, 0x40, 0, 2, 23, 42 -> 1, 2, 0 +// f(uint256[][]): 0x20, 1, -32 -> 1, 1, 0 +// f(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 2, 2 +// f(uint256[][]): 0x20, 2, 0x40, 0xa0, 2, 23, 42, 0 -> 2, 2, 0 +// f(uint256[][]): 0x20, 2, 0xA0, 0x40, 2, 23, 42, 0 -> 2, 0, 2 +// f(uint256[][]): 0x20, 2, 0x40, 0xA0, 2, 23, 42, 1, 17 -> 2, 2, 1 +// f(uint256[][]): 0x20, 2, 0x40, 0xA0, 2, 23, 42, 2, 17, 13 -> 2, 2, 2 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/viaYul/calldata_array_three_dimensional.sol b/test/libsolidity/semanticTests/viaYul/calldata_array_three_dimensional.sol new file mode 100644 index 000000000..6742b995a --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/calldata_array_three_dimensional.sol @@ -0,0 +1,20 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f(uint256[][2][] calldata x, uint256 i, uint256 j, uint256 k) external returns (uint256 a, uint256 b, uint256 c, uint256 d) { + a = x.length; + b = x[i].length; + c = x[i][j].length; + d = x[i][j][k]; + } +} +// ==== +// compileViaYul: also +// ---- +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 0, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> 1, 2, 1, 42 +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> 1, 2, 1, 23 +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 0, 1, 0x20, 0x40, 0x80, 1, 42, 2, 23, 17 -> 1, 2, 2, 23 +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 1, 1, 1, 0x20, 0x40, 0x80, 1, 42, 2, 23, 17 -> 1, 2, 2, 17 +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 1, 0, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 2, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE +// f(uint256[][2][],uint256,uint256,uint256): 0x80, 0, 2, 0, 1, 0x20, 0x40, 0x80, 1, 42, 1, 23 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/calldata_bytes_array_bounds.sol b/test/libsolidity/semanticTests/viaYul/calldata_bytes_array_bounds.sol new file mode 100644 index 000000000..f9eebf14e --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/calldata_bytes_array_bounds.sol @@ -0,0 +1,12 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(bytes[] calldata a, uint256 i) external returns (uint) { + return uint8(a[0][i]); + } +} +// ==== +// compileViaYul: also +// ---- +// f(bytes[],uint256): 0x40, 0, 1, 0x20, 2, hex"6162" -> 0x61 +// f(bytes[],uint256): 0x40, 1, 1, 0x20, 2, hex"6162" -> 0x62 +// f(bytes[],uint256): 0x40, 2, 1, 0x20, 2, hex"6162" -> FAILURE