From 66fc7ffab2f4373882a1964004e3177aea31edb0 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 6 Mar 2019 16:35:05 +0100 Subject: [PATCH] Allow dynamically encoded calldata structs with ABIEncoderV2. --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 12 --- libsolidity/codegen/ABIFunctions.cpp | 1 - libsolidity/codegen/CompilerUtils.cpp | 47 ++++++++++++ libsolidity/codegen/CompilerUtils.h | 7 ++ libsolidity/codegen/ExpressionCompiler.cpp | 73 ++++++------------- .../structs/calldata/dynamic_nested.sol | 11 +++ .../structs/calldata/dynamically_encoded.sol | 10 +++ .../syntaxTests/structs/calldata_dynamic.sol | 7 ++ 9 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol create mode 100644 test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol create mode 100644 test/libsolidity/syntaxTests/structs/calldata_dynamic.sol diff --git a/Changelog.md b/Changelog.md index 77010d205..a09c57b3f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Language Features: * Allow calldata arrays with dynamically encoded base types with ABIEncoderV2. + * Allow dynamically encoded calldata structs with ABIEncoderV2. Compiler Features: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 41c3003df..4620d82b0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -371,18 +371,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function) }; for (ASTPointer const& var: _function.parameters()) { - TypePointer baseType = type(*var); - while (auto const* arrayType = dynamic_cast(baseType.get())) - baseType = arrayType->baseType(); - - if ( - !m_scope->isInterface() && - baseType->dataStoredIn(DataLocation::CallData) - ) - if (auto const* structType = dynamic_cast(baseType.get())) - if (structType->isDynamicallyEncoded()) - m_errorReporter.typeError(var->location(), "Dynamically encoded calldata structs are not yet supported."); - checkArgumentAndReturnParameter(*var); var->accept(*this); } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 19ff66427..d78c6e7fb 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -1212,7 +1212,6 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo if (structType->dataStoredIn(DataLocation::CallData)) { solAssert(!_fromMemory, ""); - solUnimplementedAssert(!structType->isDynamicallyEncoded(), "Dynamically encoded calldata structs are not yet implemented."); return abiDecodingFunctionCalldataStruct(*structType); } else diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index b32e744b1..d007aaf7c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -97,6 +97,53 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType) m_context << Instruction::REVERT; } +void CompilerUtils::accessCalldataTail(Type const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + + unsigned int baseEncodedSize = _type.calldataEncodedSize(); + solAssert(baseEncodedSize > 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"}); + // stack layout: + + if (!_type.isDynamicallySized()) + { + m_context << Instruction::POP; + // stack layout: + solAssert( + _type.category() == Type::Category::Struct || + _type.category() == Type::Category::Array, + "Invalid dynamically encoded base type on tail access." + ); + } + else + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, "Invalid dynamically sized type."); + unsigned int calldataStride = arrayType->calldataStride(); + solAssert(calldataStride > 0, ""); + + // returns the absolute offset of the tail in "base_ref" + // and the length of the tail in "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: + } +} + unsigned CompilerUtils::loadFromMemory( unsigned _offset, Type const& _type, diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 5e07e7bb4..568d2c9a7 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -65,6 +65,13 @@ public: /// Stack post: void revertWithStringData(Type const& _argumentType); + /// Computes the absolute calldata offset of a tail given a base reference and the (absolute) + /// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also + /// returns the array length on the stack. + /// Stack pre: base_ref tail_ptr + /// Stack post: tail_ref [length] + void accessCalldataTail(Type const& _type); + /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) /// @param _type data type to load diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 91912758f..3ed4b702f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1424,20 +1424,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) } case DataLocation::CallData: { - solUnimplementedAssert(!type.isDynamicallyEncoded(), ""); - m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; - // For non-value types the calldata offset is returned directly. - if (_memberAccess.annotation().type->isValueType()) + if (_memberAccess.annotation().type->isDynamicallyEncoded()) { - solAssert(_memberAccess.annotation().type->calldataEncodedSize(false) > 0, ""); - CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); + m_context << Instruction::DUP1; + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type); } else - solAssert( - _memberAccess.annotation().type->category() == Type::Category::Array || - _memberAccess.annotation().type->category() == Type::Category::Struct, - "" - ); + { + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + // For non-value types the calldata offset is returned directly. + if (_memberAccess.annotation().type->isValueType()) + { + solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, ""); + CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false); + } + else + solAssert( + _memberAccess.annotation().type->category() == Type::Category::Array || + _memberAccess.annotation().type->category() == Type::Category::Struct, + "" + ); + } break; } default: @@ -1574,47 +1582,8 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) 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: - } + CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType()); + // stack layout: [length] } else { diff --git a/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol b/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol new file mode 100644 index 000000000..59cde3171 --- /dev/null +++ b/test/libsolidity/semanticTests/structs/calldata/dynamic_nested.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S2 { uint256 b; } + struct S { uint256 a; S2[] children; } + function f(S calldata s) external pure returns (uint256, uint256, uint256, uint256) { + return (s.children.length, s.a, s.children[0].b, s.children[1].b); + } +} +// ---- +// f((uint256,(uint256)[])): 32, 17, 64, 2, 23, 42 -> 2, 17, 23, 42 diff --git a/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol b/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol new file mode 100644 index 000000000..3acea52e7 --- /dev/null +++ b/test/libsolidity/semanticTests/structs/calldata/dynamically_encoded.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { uint256[] a; } + function f(S calldata s) external pure returns (uint256 a, uint256 b, uint256 c) { + return (s.a.length, s.a[0], s.a[1]); + } +} +// ---- +// f((uint256[])): 32, 32, 2, 42, 23 -> 2, 42, 23 diff --git a/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol b/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol new file mode 100644 index 000000000..34b0affae --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata_dynamic.sol @@ -0,0 +1,7 @@ +pragma experimental ABIEncoderV2; +contract Test { + struct S { int[] a; } + function f(S calldata) external { } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.