diff --git a/Changelog.md b/Changelog.md index cadd3884a..1151b3d7c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,6 +14,7 @@ Language Features: * Allow global enums and structs. * Allow underscores as delimiters in hex strings. * Allow explicit conversions from ``address`` to ``address payable`` via ``payable(...)``. + * Introduce syntax for array slices and implement them for dynamic calldata arrays. Compiler Features: diff --git a/docs/grammar.txt b/docs/grammar.txt index cf0ddd104..4433b4a43 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -86,6 +86,7 @@ Expression = Expression ('++' | '--') | NewExpression | IndexAccess + | IndexRangeAccess | MemberAccess | FunctionCall | '(' Expression ')' @@ -123,6 +124,7 @@ FunctionCallArguments = '{' NameValueList? '}' NewExpression = 'new' TypeName MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expression? ']' +IndexRangeAccess = Expression '[' Expression? ':' Expression? ']' BooleanLiteral = 'true' | 'false' NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)? diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d1faa6882..2ac071375 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -136,19 +136,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c ); if (arguments.size() >= 1) - { - BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()); - - if (!result) - m_errorReporter.typeErrorConcatenateDescriptions( + if ( + !type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) && + !type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()) + ) + m_errorReporter.typeError( arguments.front()->location(), - "Invalid type for argument in function call. " - "Invalid implicit conversion from " + + "The first argument to \"abi.decode\" must be implicitly convertible to " + "bytes memory or bytes calldata, but is of type " + type(*arguments.front())->toString() + - " to bytes memory requested.", - result.message() + "." ); - } if (arguments.size() < 2) return {}; @@ -2245,6 +2243,14 @@ bool TypeChecker::visit(IndexAccess const& _access) Expression const* index = _access.indexExpression(); switch (baseType->category()) { + case Type::Category::ArraySlice: + { + auto const& arrayType = dynamic_cast(*baseType).arrayType(); + if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized()) + m_errorReporter.typeError(_access.location(), "Index access is only implemented for slices of dynamic calldata arrays."); + baseType = &arrayType; + [[fallthrough]]; + } case Type::Category::Array: { ArrayType const& actualType = dynamic_cast(*baseType); @@ -2341,6 +2347,50 @@ bool TypeChecker::visit(IndexAccess const& _access) return false; } +bool TypeChecker::visit(IndexRangeAccess const& _access) +{ + _access.baseExpression().accept(*this); + + bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays + bool isPure = _access.baseExpression().annotation().isPure; + + if (Expression const* start = _access.startExpression()) + { + expectType(*start, *TypeProvider::uint256()); + if (!start->annotation().isPure) + isPure = false; + } + if (Expression const* end = _access.endExpression()) + { + expectType(*end, *TypeProvider::uint256()); + if (!end->annotation().isPure) + isPure = false; + } + + TypePointer exprType = type(_access.baseExpression()); + if (exprType->category() == Type::Category::TypeType) + { + m_errorReporter.typeError(_access.location(), "Types cannot be sliced."); + _access.annotation().type = exprType; + return false; + } + + ArrayType const* arrayType = nullptr; + if (auto const* arraySlice = dynamic_cast(exprType)) + arrayType = &arraySlice->arrayType(); + else if (!(arrayType = dynamic_cast(exprType))) + m_errorReporter.fatalTypeError(_access.location(), "Index range access is only possible for arrays and array slices."); + + + if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized()) + m_errorReporter.typeError(_access.location(), "Index range access is only supported for dynamic calldata arrays."); + _access.annotation().type = TypeProvider::arraySlice(*arrayType); + _access.annotation().isLValue = isLValue; + _access.annotation().isPure = isPure; + + return false; +} + bool TypeChecker::visit(Identifier const& _identifier) { IdentifierAnnotation& annotation = _identifier.annotation(); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 8f532a89c..c962ad2ed 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -136,6 +136,7 @@ private: void endVisit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; + bool visit(IndexRangeAccess const& _indexRangeAccess) override; bool visit(Identifier const& _identifier) override; void endVisit(ElementaryTypeNameExpression const& _expr) override; void endVisit(Literal const& _literal) override; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index cf35a4889..7ead355e1 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -413,6 +413,13 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess) } } +void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess) +{ + bool writes = _indexRangeAccess.annotation().lValueRequested; + if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage)) + reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location()); +} + void ViewPureChecker::endVisit(ModifierInvocation const& _modifier) { solAssert(_modifier.name(), ""); diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index fd2432a7d..b697253f1 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -58,6 +58,7 @@ private: bool visit(MemberAccess const& _memberAccess) override; void endVisit(MemberAccess const& _memberAccess) override; void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(IndexRangeAccess const& _indexAccess) override; void endVisit(ModifierInvocation const& _modifier) override; void endVisit(FunctionCall const& _functionCall) override; void endVisit(InlineAssembly const& _inlineAssembly) override; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index e808003ec..741262682 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1642,6 +1642,32 @@ private: ASTPointer m_index; }; +/** + * Index range access to an array. Example: a[2:3] + */ +class IndexRangeAccess: public Expression +{ +public: + IndexRangeAccess( + SourceLocation const& _location, + ASTPointer const& _base, + ASTPointer const& _start, + ASTPointer const& _end + ): + Expression(_location), m_base(_base), m_start(_start), m_end(_end) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Expression const& baseExpression() const { return *m_base; } + Expression const* startExpression() const { return m_start.get(); } + Expression const* endExpression() const { return m_end.get(); } + +private: + ASTPointer m_base; + ASTPointer m_start; + ASTPointer m_end; +}; + /** * Primary expression, i.e. an expression that cannot be divided any further. Examples are literals * or variable references. diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index c6f7ecf9b..ed5b56d71 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -694,6 +694,18 @@ bool ASTJsonConverter::visit(IndexAccess const& _node) return false; } +bool ASTJsonConverter::visit(IndexRangeAccess const& _node) +{ + std::vector> attributes = { + make_pair("baseExpression", toJson(_node.baseExpression())), + make_pair("startExpression", toJsonOrNull(_node.startExpression())), + make_pair("endExpression", toJsonOrNull(_node.endExpression())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "IndexRangeAccess", std::move(attributes)); + return false; +} + bool ASTJsonConverter::visit(Identifier const& _node) { Json::Value overloads(Json::arrayValue); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 7cd1dde39..0c79d63d1 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -111,6 +111,7 @@ public: bool visit(NewExpression const& _node) override; bool visit(MemberAccess const& _node) override; bool visit(IndexAccess const& _node) override; + bool visit(IndexRangeAccess const& _node) override; bool visit(Identifier const& _node) override; bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(Literal const& _node) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 6184e5f42..901b93ef8 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -87,6 +87,7 @@ public: virtual bool visit(NewExpression& _node) { return visitNode(_node); } virtual bool visit(MemberAccess& _node) { return visitNode(_node); } virtual bool visit(IndexAccess& _node) { return visitNode(_node); } + virtual bool visit(IndexRangeAccess& _node) { return visitNode(_node); } virtual bool visit(Identifier& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); } @@ -134,6 +135,7 @@ public: virtual void endVisit(NewExpression& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); } + virtual void endVisit(IndexRangeAccess& _node) { endVisitNode(_node); } virtual void endVisit(Identifier& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); } @@ -194,6 +196,7 @@ public: virtual bool visit(NewExpression const& _node) { return visitNode(_node); } virtual bool visit(MemberAccess const& _node) { return visitNode(_node); } virtual bool visit(IndexAccess const& _node) { return visitNode(_node); } + virtual bool visit(IndexRangeAccess const& _node) { return visitNode(_node); } virtual bool visit(Identifier const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); } @@ -241,6 +244,7 @@ public: virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); } + virtual void endVisit(IndexRangeAccess const& _node) { endVisitNode(_node); } virtual void endVisit(Identifier const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index a95c22d7b..fd676c1ea 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -783,6 +783,32 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void IndexRangeAccess::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_base->accept(_visitor); + if (m_start) + m_start->accept(_visitor); + if (m_end) + m_end->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void IndexRangeAccess::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_base->accept(_visitor); + if (m_start) + m_start->accept(_visitor); + if (m_end) + m_end->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Identifier::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 7d7824da3..a9b4fafb6 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -31,6 +31,7 @@ InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; /// they rely on `byte` being available which we cannot guarantee in the static init context. unique_ptr TypeProvider::m_bytesStorage; unique_ptr TypeProvider::m_bytesMemory; +unique_ptr TypeProvider::m_bytesCalldata; unique_ptr TypeProvider::m_stringStorage; unique_ptr TypeProvider::m_stringMemory; @@ -177,6 +178,7 @@ void TypeProvider::reset() clearCache(m_inaccessibleDynamic); clearCache(m_bytesStorage); clearCache(m_bytesMemory); + clearCache(m_bytesCalldata); clearCache(m_stringStorage); clearCache(m_stringMemory); clearCache(m_emptyTuple); @@ -314,6 +316,13 @@ ArrayType const* TypeProvider::bytesMemory() return m_bytesMemory.get(); } +ArrayType const* TypeProvider::bytesCalldata() +{ + if (!m_bytesCalldata) + m_bytesCalldata = make_unique(DataLocation::CallData, false); + return m_bytesCalldata.get(); +} + ArrayType const* TypeProvider::stringStorage() { if (!m_stringStorage) @@ -500,6 +509,11 @@ ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseTy return createAndGet(_location, _baseType, _length); } +ArraySliceType const* TypeProvider::arraySlice(ArrayType const& _arrayType) +{ + return createAndGet(_arrayType); +} + ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper) { return createAndGet(_contractDef, _isSuper); diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index f6f4e1f74..f65563e2a 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -68,6 +68,7 @@ public: static ArrayType const* bytesStorage(); static ArrayType const* bytesMemory(); + static ArrayType const* bytesCalldata(); static ArrayType const* stringStorage(); static ArrayType const* stringMemory(); @@ -80,6 +81,8 @@ public: /// Constructor for a fixed-size array type ("type[20]") static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length); + static ArraySliceType const* arraySlice(ArrayType const& _arrayType); + static AddressType const* payableAddress() noexcept { return &m_payableAddress; } static AddressType const* address() noexcept { return &m_address; } @@ -203,6 +206,7 @@ private: /// These are lazy-initialized because they depend on `byte` being available. static std::unique_ptr m_bytesStorage; static std::unique_ptr m_bytesMemory; + static std::unique_ptr m_bytesCalldata; static std::unique_ptr m_stringStorage; static std::unique_ptr m_stringMemory; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 79d41bfe9..b509c2495 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1869,6 +1869,30 @@ std::unique_ptr ArrayType::copyForLocation(DataLocation _location return copy; } +BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const +{ + if (m_arrayType.location() == DataLocation::CallData && m_arrayType.isDynamicallySized() && m_arrayType == _other) + return true; + return (*this) == _other; +} + +string ArraySliceType::richIdentifier() const +{ + return m_arrayType.richIdentifier() + "_slice"; +} + +bool ArraySliceType::operator==(Type const& _other) const +{ + if (auto const* other = dynamic_cast(&_other)) + return m_arrayType == other->m_arrayType; + return false; +} + +string ArraySliceType::toString(bool _short) const +{ + return m_arrayType.toString(_short) + " slice"; +} + string ContractType::richIdentifier() const { return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 992d95552..8563c839f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -161,7 +161,7 @@ public: enum class Category { - Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, + Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice, FixedBytes, Contract, Struct, Function, Enum, Tuple, Mapping, TypeType, Modifier, Magic, Module, InaccessibleDynamic @@ -773,6 +773,35 @@ private: mutable boost::optional m_interfaceType_library; }; +class ArraySliceType: public ReferenceType +{ +public: + explicit ArraySliceType(ArrayType const& _arrayType): ReferenceType(_arrayType.location()), m_arrayType(_arrayType) {} + Category category() const override { return Category::ArraySlice; } + + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); } + unsigned calldataEncodedTailSize() const override { return 32; } + bool isDynamicallySized() const override { return true; } + bool isDynamicallyEncoded() const override { return true; } + bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } + unsigned sizeOnStack() const override { return 2; } + std::string toString(bool _short) const override; + + /// @returns true if this is valid to be stored in calldata + bool validForCalldata() const { return m_arrayType.validForCalldata(); } + + ArrayType const& arrayType() const { return m_arrayType; } + u256 memoryDataSize() const override { solAssert(false, ""); } + + std::unique_ptr copyForLocation(DataLocation, bool) const override { solAssert(false, ""); } + +private: + ArrayType const& m_arrayType; +}; + /** * The type of a contract instance or library, there is one distinct type for each contract definition. */ diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 0e10b3691..dfeb28db2 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -981,6 +981,17 @@ void CompilerUtils::convertType( } break; } + case Type::Category::ArraySlice: + { + auto& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(_targetType == typeOnStack.arrayType(), ""); + solUnimplementedAssert( + typeOnStack.arrayType().location() == DataLocation::CallData && + typeOnStack.arrayType().isDynamicallySized(), + "" + ); + break; + } case Type::Category::Struct: { solAssert(targetTypeCategory == stackTypeCategory, ""); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 71c998c09..87f1a9d39 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1079,10 +1079,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) else targetTypes = TypePointers{_functionCall.annotation().type}; if ( - *firstArgType == ArrayType(DataLocation::CallData) || - *firstArgType == ArrayType(DataLocation::CallData, true) + auto referenceType = dynamic_cast(firstArgType); + referenceType && referenceType->dataStoredIn(DataLocation::CallData) ) + { + solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), ""); + utils().convertType(*referenceType, *TypeProvider::bytesCalldata()); utils().abiDecode(targetTypes, false); + } else { utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); @@ -1503,90 +1507,149 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) Type const& baseType = *_indexAccess.baseExpression().annotation().type; - if (baseType.category() == Type::Category::Mapping) + switch (baseType.category()) { - // stack: storage_base_ref - TypePointer keyType = dynamic_cast(baseType).keyType(); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); - if (keyType->isDynamicallySized()) + case Type::Category::Mapping: { - _indexAccess.indexExpression()->accept(*this); - utils().fetchFreeMemoryPointer(); - // stack: base index mem - // note: the following operations must not allocate memory! - utils().packedEncode( - TypePointers{_indexAccess.indexExpression()->annotation().type}, - TypePointers{keyType} - ); - m_context << Instruction::SWAP1; - utils().storeInMemoryDynamic(*TypeProvider::uint256()); - utils().toSizeAfterFreeMemoryPointer(); - } - else - { - m_context << u256(0); // memory position - appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); - m_context << Instruction::SWAP1; - solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - utils().storeInMemoryDynamic(*TypeProvider::uint256()); - m_context << u256(0); - } - m_context << Instruction::KECCAK256; - m_context << u256(0); - setLValueToStorageItem(_indexAccess); - } - else if (baseType.category() == Type::Category::Array) - { - ArrayType const& arrayType = dynamic_cast(baseType); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); - - acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); - // stack layout: [] - switch (arrayType.location()) - { - case DataLocation::Storage: - ArrayUtils(m_context).accessIndex(arrayType); - if (arrayType.isByteArray()) + // stack: storage_base_ref + TypePointer keyType = dynamic_cast(baseType).keyType(); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + if (keyType->isDynamicallySized()) { - solAssert(!arrayType.isString(), "Index access to string is not allowed."); - setLValue(_indexAccess); + _indexAccess.indexExpression()->accept(*this); + utils().fetchFreeMemoryPointer(); + // stack: base index mem + // note: the following operations must not allocate memory! + utils().packedEncode( + TypePointers{_indexAccess.indexExpression()->annotation().type}, + TypePointers{keyType} + ); + m_context << Instruction::SWAP1; + utils().storeInMemoryDynamic(*TypeProvider::uint256()); + utils().toSizeAfterFreeMemoryPointer(); } else - setLValueToStorageItem(_indexAccess); - break; - case DataLocation::Memory: - ArrayUtils(m_context).accessIndex(arrayType); - setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); - break; - case DataLocation::CallData: - ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + { + m_context << u256(0); // memory position + appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); + m_context << Instruction::SWAP1; + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); + m_context << u256(0); + } + m_context << Instruction::KECCAK256; + m_context << u256(0); + setLValueToStorageItem(_indexAccess); break; } - } - else if (baseType.category() == Type::Category::FixedBytes) - { - FixedBytesType const& fixedBytesType = dynamic_cast(baseType); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); + case Type::Category::ArraySlice: + { + auto const& arrayType = dynamic_cast(baseType).arrayType(); + solAssert(arrayType.location() == DataLocation::CallData && arrayType.isDynamicallySized(), ""); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); - acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); - // stack layout: - // check out-of-bounds access - m_context << u256(fixedBytesType.numBytes()); - m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; - // out-of-bounds access throws exception - m_context.appendConditionalInvalid(); + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + break; - m_context << Instruction::BYTE; - utils().leftShiftNumberOnStack(256 - 8); - } - else if (baseType.category() == Type::Category::TypeType) - { - solAssert(baseType.sizeOnStack() == 0, ""); - solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); - // no-op - this seems to be a lone array type (`structType[];`) + } + case Type::Category::Array: + { + ArrayType const& arrayType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + // stack layout: [] + switch (arrayType.location()) + { + case DataLocation::Storage: + ArrayUtils(m_context).accessIndex(arrayType); + if (arrayType.isByteArray()) + { + solAssert(!arrayType.isString(), "Index access to string is not allowed."); + setLValue(_indexAccess); + } + else + setLValueToStorageItem(_indexAccess); + break; + case DataLocation::Memory: + ArrayUtils(m_context).accessIndex(arrayType); + setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); + break; + case DataLocation::CallData: + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + break; + } + break; + } + case Type::Category::FixedBytes: + { + FixedBytesType const& fixedBytesType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + // stack layout: + // check out-of-bounds access + m_context << u256(fixedBytesType.numBytes()); + m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; + // out-of-bounds access throws exception + m_context.appendConditionalInvalid(); + + m_context << Instruction::BYTE; + utils().leftShiftNumberOnStack(256 - 8); + break; + } + case Type::Category::TypeType: + { + solAssert(baseType.sizeOnStack() == 0, ""); + solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); + // no-op - this seems to be a lone array type (`structType[];`) + break; + } + default: + solAssert(false, "Index access only allowed for mappings or arrays."); + break; } + + return false; +} + +bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess) +{ + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); + _indexAccess.baseExpression().accept(*this); + + Type const& baseType = *_indexAccess.baseExpression().annotation().type; + + ArrayType const *arrayType = dynamic_cast(&baseType); + if (!arrayType) + if (ArraySliceType const* sliceType = dynamic_cast(&baseType)) + arrayType = &sliceType->arrayType(); + + solAssert(arrayType, ""); + solUnimplementedAssert(arrayType->location() == DataLocation::CallData && arrayType->isDynamicallySized(), ""); + + if (_indexAccess.startExpression()) + acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256()); else - solAssert(false, "Index access only allowed for mappings or arrays."); + m_context << u256(0); + if (_indexAccess.endExpression()) + acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256()); + else + m_context << Instruction::DUP2; + + m_context.appendInlineAssembly( + Whiskers(R"({ + if gt(sliceStart, sliceEnd) { revert(0, 0) } + if gt(sliceEnd, length) { revert(0, 0) } + + offset := add(offset, mul(sliceStart, )) + length := sub(sliceEnd, sliceStart) + })")("stride", toString(arrayType->calldataStride())).render(), + {"offset", "length", "sliceStart", "sliceEnd"} + ); + + m_context << Instruction::POP << Instruction::POP; return false; } diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 645d67067..8bdb1479a 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -80,6 +80,7 @@ private: bool visit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; + bool visit(IndexRangeAccess const& _indexAccess) override; void endVisit(Identifier const& _identifier) override; void endVisit(Literal const& _literal) override; diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 9bb4a9984..75d79db2f 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -823,11 +823,6 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type) }); } -string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/) -{ - solUnimplemented("Calldata arrays not yet implemented!"); -} - string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 2258707fa..f978a8f60 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -941,6 +941,11 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) solAssert(false, "Index access only allowed for mappings or arrays."); } +void IRGeneratorForStatements::endVisit(IndexRangeAccess const&) +{ + solUnimplementedAssert(false, "Index range accesses not yet implemented."); +} + void IRGeneratorForStatements::endVisit(Identifier const& _identifier) { Declaration const* declaration = _identifier.annotation().referencedDeclaration; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index d64ab9e0a..74cef0b8a 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -63,6 +63,7 @@ public: void endVisit(MemberAccess const& _memberAccess) override; bool visit(InlineAssembly const& _inlineAsm) override; void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(IndexRangeAccess const& _indexRangeAccess) override; void endVisit(Identifier const& _identifier) override; bool visit(Literal const& _literal) override; diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 421bfb3b6..5df464548 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -767,6 +767,15 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) m_uninterpretedTerms.insert(&_indexAccess); } +void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess) +{ + createExpr(_indexRangeAccess); + m_errorReporter.warning( + _indexRangeAccess.location(), + "Assertion checker does not yet implement this expression." + ); +} + void SMTEncoder::arrayAssignment() { m_arrayAssignmentHappened = true; diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 6ccbe9bcc..35a45e83d 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -86,6 +86,7 @@ protected: void endVisit(Return const& _node) override; bool visit(MemberAccess const& _node) override; void endVisit(IndexAccess const& _node) override; + void endVisit(IndexRangeAccess const& _node) override; bool visit(InlineAssembly const& _node) override; /// Do not visit subtree if node is a RationalNumber. diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 14c4a29fc..9601389c7 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1607,11 +1607,24 @@ ASTPointer Parser::parseLeftHandSideExpression( { m_scanner->next(); ASTPointer index; - if (m_scanner->currentToken() != Token::RBrack) + ASTPointer endIndex; + if (m_scanner->currentToken() != Token::RBrack && m_scanner->currentToken() != Token::Colon) index = parseExpression(); - nodeFactory.markEndPosition(); - expectToken(Token::RBrack); - expression = nodeFactory.createNode(expression, index); + if (m_scanner->currentToken() == Token::Colon) + { + expectToken(Token::Colon); + if (m_scanner->currentToken() != Token::RBrack) + endIndex = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + expression = nodeFactory.createNode(expression, index, endIndex); + } + else + { + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + expression = nodeFactory.createNode(expression, index); + } break; } case Token::Period: @@ -1861,12 +1874,25 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath() { expectToken(Token::LBrack); ASTPointer index; - if (m_scanner->currentToken() != Token::RBrack) + if (m_scanner->currentToken() != Token::RBrack && m_scanner->currentToken() != Token::Colon) index = parseExpression(); SourceLocation indexLocation = iap.path.front()->location(); - indexLocation.end = endPosition(); - iap.indices.emplace_back(index, indexLocation); - expectToken(Token::RBrack); + if (m_scanner->currentToken() == Token::Colon) + { + expectToken(Token::Colon); + ASTPointer endIndex; + if (m_scanner->currentToken() != Token::RBrack) + endIndex = parseExpression(); + indexLocation.end = endPosition(); + iap.indices.emplace_back(IndexAccessedPath::Index{index, {endIndex}, indexLocation}); + expectToken(Token::RBrack); + } + else + { + indexLocation.end = endPosition(); + iap.indices.emplace_back(IndexAccessedPath::Index{index, {}, indexLocation}); + expectToken(Token::RBrack); + } } return iap; @@ -1898,8 +1924,10 @@ ASTPointer Parser::typeNameFromIndexAccessStructure(Parser::IndexAcces } for (auto const& lengthExpression: _iap.indices) { - nodeFactory.setLocation(lengthExpression.second); - type = nodeFactory.createNode(type, lengthExpression.first); + if (lengthExpression.end) + parserError(lengthExpression.location, "Expected array length expression."); + nodeFactory.setLocation(lengthExpression.location); + type = nodeFactory.createNode(type, lengthExpression.start); } return type; } @@ -1927,8 +1955,11 @@ ASTPointer Parser::expressionFromIndexAccessStructure( } for (auto const& index: _iap.indices) { - nodeFactory.setLocation(index.second); - expression = nodeFactory.createNode(expression, index.first); + nodeFactory.setLocation(index.location); + if (index.end) + expression = nodeFactory.createNode(expression, index.start, *index.end); + else + expression = nodeFactory.createNode(expression, index.start); } return expression; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index b0e1c6716..479306fb0 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -162,8 +162,14 @@ private: /// or to a type name. For this to be valid, path cannot be empty, but indices can be empty. struct IndexAccessedPath { + struct Index + { + ASTPointer start; + std::optional> end; + langutil::SourceLocation location; + }; std::vector> path; - std::vector, langutil::SourceLocation>> indices; + std::vector indices; bool empty() const; }; diff --git a/test/libsolidity/semanticTests/abiDecodeV1/decode_slice.sol b/test/libsolidity/semanticTests/abiDecodeV1/decode_slice.sol new file mode 100644 index 000000000..35cb15077 --- /dev/null +++ b/test/libsolidity/semanticTests/abiDecodeV1/decode_slice.sol @@ -0,0 +1,9 @@ +contract C { + function f(uint256 a, uint256 b) external returns (uint256 c, uint256 d, uint256 e, uint256 f) { + (c, d) = abi.decode(msg.data[4:], (uint256, uint256)); + e = abi.decode(msg.data[4 : 4 + 32], (uint256)); + f = abi.decode(msg.data[4 + 32 : 4 + 32 + 32], (uint256)); + } +} +// ---- +// f(uint256,uint256): 42, 23 -> 42, 23, 42, 23 diff --git a/test/libsolidity/semanticTests/array/calldata_slice_access.sol b/test/libsolidity/semanticTests/array/calldata_slice_access.sol new file mode 100644 index 000000000..8e8a398de --- /dev/null +++ b/test/libsolidity/semanticTests/array/calldata_slice_access.sol @@ -0,0 +1,44 @@ +contract C { + function f(uint256[] calldata x, uint256 start, uint256 end) external pure { + x[start:end]; + } + function g(uint256[] calldata x, uint256 start, uint256 end, uint256 index) external pure returns (uint256, uint256, uint256) { + return (x[start:end][index], x[start:][0:end-start][index], x[:end][start:][index]); + } +} +// ---- +// f(uint256[],uint256,uint256): 0x80, 0, 0, 0, 1, 42 -> +// f(uint256[],uint256,uint256): 0x80, 0, 1, 0, 1, 42 -> +// f(uint256[],uint256,uint256): 0x80, 0, 2, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 1, 0, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 1, 1, 0, 1, 42 -> +// f(uint256[],uint256,uint256): 0x80, 1, 2, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 2, 0, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 2, 1, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 2, 2, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 0, 2, 1, 0, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, 1, 2, 0, 2, 42, 23 -> +// f(uint256[],uint256,uint256): 0x80, 1, 3, 0, 2, 42, 23 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, -1, 0, 0, 1, 42 -> FAILURE +// f(uint256[],uint256,uint256): 0x80, -1, -1, 0, 1, 42 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 0, 1, 42 -> 42, 42, 42 +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 1, 1, 42 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 0, 0, 1, 42 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 1, 0, 1, 42 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 5, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4201, 0x4201, 0x4201 +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 5, 4, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4205, 0x4205, 0x4205 +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 5, 5, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 5, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4202, 0x4202, 0x4202 +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 5, 3, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4205, 0x4205, 0x4205 +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 5, 4, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 4, 5, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4205, 0x4205, 0x4205 +// g(uint256[],uint256,uint256,uint256): 0x80, 4, 5, 1, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 5, 5, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4201, 0x4201, 0x4201 +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 1, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4201, 0x4201, 0x4201 +// g(uint256[],uint256,uint256,uint256): 0x80, 0, 1, 1, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 2, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4202, 0x4202, 0x4202 +// g(uint256[],uint256,uint256,uint256): 0x80, 1, 2, 1, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE +// g(uint256[],uint256,uint256,uint256): 0x80, 4, 5, 0, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> 0x4205, 0x4205, 0x4205 +// g(uint256[],uint256,uint256,uint256): 0x80, 4, 5, 1, 5, 0x4201, 0x4202, 0x4203, 0x4204, 0x4205 -> FAILURE diff --git a/test/libsolidity/syntaxTests/array/slice/bytes_calldata.sol b/test/libsolidity/syntaxTests/array/slice/bytes_calldata.sol new file mode 100644 index 000000000..64126f53c --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/bytes_calldata.sol @@ -0,0 +1,5 @@ +contract C { + function f(bytes calldata x) external pure { + x[1:2]; + } +} diff --git a/test/libsolidity/syntaxTests/array/slice/bytes_memory.sol b/test/libsolidity/syntaxTests/array/slice/bytes_memory.sol new file mode 100644 index 000000000..c2bfc4f62 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/bytes_memory.sol @@ -0,0 +1,7 @@ +contract C { + function f(bytes memory x) public pure { + x[1:2]; + } +} +// ---- +// TypeError: (66-72): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/bytes_storage.sol b/test/libsolidity/syntaxTests/array/slice/bytes_storage.sol new file mode 100644 index 000000000..dd7ed0893 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/bytes_storage.sol @@ -0,0 +1,8 @@ +contract C { + bytes x; + function f() public view { + x[1:2]; + } +} +// ---- +// TypeError: (65-71): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic.sol new file mode 100644 index 000000000..ea0672a43 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic.sol @@ -0,0 +1,5 @@ +contract C { + function f(uint256[] calldata x) external pure { + x[1:2]; + } +} diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_access.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_access.sol new file mode 100644 index 000000000..65088f4c2 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_access.sol @@ -0,0 +1,9 @@ +contract C { + function f(uint256[] calldata x) external pure { + x[1:2][0]; + x[1:][0]; + x[1:][1:2][0]; + x[1:2][1:][0]; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_convert_to_memory.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_convert_to_memory.sol new file mode 100644 index 000000000..a3e2fccc1 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_convert_to_memory.sol @@ -0,0 +1,7 @@ +contract C { + function f(bytes calldata x) external { + bytes memory y = x[1:2]; + } +} +// ---- +// TypeError: (65-88): Type bytes calldata slice is not implicitly convertible to expected type bytes memory. diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol new file mode 100644 index 000000000..a31e5ab85 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[] calldata x) external pure { + abi.encode(x[1:2]); + } +} +// ---- +// TypeError: (85-91): This type cannot be encoded. diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_forward.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_forward.sol new file mode 100644 index 000000000..a2d6e0f02 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_forward.sol @@ -0,0 +1,7 @@ +contract C { + function f(bytes calldata x) external { + return this.f(x[1:2]); + } +} +// ---- +// TypeError: (79-85): Invalid type for argument in function call. Invalid implicit conversion from bytes calldata slice to bytes memory requested. diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_static.sol b/test/libsolidity/syntaxTests/array/slice/calldata_static.sol new file mode 100644 index 000000000..0fca7ff3c --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/calldata_static.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[42] calldata x) external pure { + x[1:2]; + } +} +// ---- +// TypeError: (76-82): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/memory_dynamic.sol b/test/libsolidity/syntaxTests/array/slice/memory_dynamic.sol new file mode 100644 index 000000000..d6dbbc270 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/memory_dynamic.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[] memory x) public pure { + x[1:2]; + } +} +// ---- +// TypeError: (70-76): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/memory_static.sol b/test/libsolidity/syntaxTests/array/slice/memory_static.sol new file mode 100644 index 000000000..dc12cb660 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/memory_static.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[42] memory x) public pure { + x[1:2]; + } +} +// ---- +// TypeError: (72-78): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/slice_literal.sol b/test/libsolidity/syntaxTests/array/slice/slice_literal.sol new file mode 100644 index 000000000..9164c10db --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/slice_literal.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + 1[1:]; + } +} +// ---- +// TypeError: (52-57): Index range access is only possible for arrays and array slices. diff --git a/test/libsolidity/syntaxTests/array/slice/slice_memory_bytes.sol b/test/libsolidity/syntaxTests/array/slice/slice_memory_bytes.sol new file mode 100644 index 000000000..88e7f0596 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/slice_memory_bytes.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + bytes memory y; + y[1:2]; + } +} +// ---- +// TypeError: (76-82): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/slice_memory_string.sol b/test/libsolidity/syntaxTests/array/slice/slice_memory_string.sol new file mode 100644 index 000000000..cb0b1f4c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/slice_memory_string.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + string memory y; + y[1:2]; + } +} +// ---- +// TypeError: (77-83): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/slice_string.sol b/test/libsolidity/syntaxTests/array/slice/slice_string.sol new file mode 100644 index 000000000..593fbe9fa --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/slice_string.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + ""[1:]; + } +} +// ---- +// TypeError: (52-58): Index range access is only possible for arrays and array slices. diff --git a/test/libsolidity/syntaxTests/array/slice/storage_dynamic.sol b/test/libsolidity/syntaxTests/array/slice/storage_dynamic.sol new file mode 100644 index 000000000..c02afd55a --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/storage_dynamic.sol @@ -0,0 +1,8 @@ +contract C { + uint256[] x; + function f() public view { + x[1:2]; + } +} +// ---- +// TypeError: (69-75): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/array/slice/storage_static.sol b/test/libsolidity/syntaxTests/array/slice/storage_static.sol new file mode 100644 index 000000000..77d619d77 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/storage_static.sol @@ -0,0 +1,8 @@ +contract C { + uint256[42] x; + function f() public view { + x[1:2]; + } +} +// ---- +// TypeError: (71-77): Index range access is only supported for dynamic calldata arrays. diff --git a/test/libsolidity/syntaxTests/parsing/array_range_and_ternary.sol b/test/libsolidity/syntaxTests/parsing/array_range_and_ternary.sol new file mode 100644 index 000000000..36733898e --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/array_range_and_ternary.sol @@ -0,0 +1,8 @@ +contract C { + function f(bool cond, bytes calldata x) external pure { + bytes1 a = x[cond ? 1 : 2]; a; + abi.decode(x[cond ? 1 : 2 : ], (uint256)); + abi.decode(x[cond ? 1 : 2 : cond ? 3 : 4], (uint256)); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/parsing/array_range_conversion.sol b/test/libsolidity/syntaxTests/parsing/array_range_conversion.sol new file mode 100644 index 000000000..65595f17b --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/array_range_conversion.sol @@ -0,0 +1,14 @@ +contract C { + function f() public pure { + uint[] memory x; + uint[1:](x); + uint[1:2](x); + uint[][1:](x); + } +} +// ---- +// TypeError: (77-85): Types cannot be sliced. +// TypeError: (77-88): Explicit type conversion not allowed from "uint256[] memory" to "uint256". +// TypeError: (98-107): Types cannot be sliced. +// TypeError: (98-110): Explicit type conversion not allowed from "uint256[] memory" to "uint256". +// TypeError: (120-130): Types cannot be sliced. diff --git a/test/libsolidity/syntaxTests/parsing/array_range_nested.sol b/test/libsolidity/syntaxTests/parsing/array_range_nested.sol new file mode 100644 index 000000000..8c0b98e57 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/array_range_nested.sol @@ -0,0 +1,12 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][] calldata x) external pure { + x[0][1:2]; + x[1:2][1:2]; + uint256 a = x[1:2][1:2][1:][3:][0][2]; + uint256 b = x[1:][3:4][1][1:][2:3][0]; + a; b; + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/parsing/array_range_no_start.sol b/test/libsolidity/syntaxTests/parsing/array_range_no_start.sol new file mode 100644 index 000000000..a0aba3e10 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/array_range_no_start.sol @@ -0,0 +1,6 @@ +contract C { + function f(uint256[] calldata x) external pure { + x[:][:10]; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/parsing/array_type_range.sol b/test/libsolidity/syntaxTests/parsing/array_type_range.sol new file mode 100644 index 000000000..6f98e7efd --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/array_type_range.sol @@ -0,0 +1,14 @@ +contract C { + function f() public pure { + uint[][1:] memory x; + uint[][1:2] memory x; + uint[1:] memory x; + uint[1:2] memory x; + } +} + +// ---- +// ParserError: (52-62): Expected array length expression. +// ParserError: (81-92): Expected array length expression. +// ParserError: (111-119): Expected array length expression. +// ParserError: (138-147): Expected array length expression. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_type.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_type.sol index e418390c5..ed1f07402 100644 --- a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_type.sol +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_type.sol @@ -4,5 +4,5 @@ contract C { } } // ---- -// TypeError: (57-61): Invalid type for argument in function call. Invalid implicit conversion from type(uint256) to bytes memory requested. +// TypeError: (57-61): The first argument to "abi.decode" must be implicitly convertible to bytes memory or bytes calldata, but is of type type(uint256). // TypeError: (63-67): The second argument to "abi.decode" has to be a tuple of types.