diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 14884254e..248abfdb9 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -890,6 +890,8 @@ void IndexAccess::checkTypeRequirements(TypePointers const*) ArrayType const& type = dynamic_cast(*m_base->getType()); if (!m_index) BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); + if (type.isString()) + BOOST_THROW_EXCEPTION(createTypeError("Index access for string is not possible.")); m_index->expectType(IntegerType(256)); if (type.isByteArray()) m_type = make_shared(1); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 2e513b7fc..5a9782858 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -824,7 +824,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) if (arrayType.getLocation() == ArrayType::Location::Storage) { if (arrayType.isByteArray()) + { + solAssert(!arrayType.isString(), "Index access to string is not allowed."); setLValue(_indexAccess); + } else setLValueToStorageItem(_indexAccess); } diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 8a373da34..bce16ed17 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -286,6 +286,7 @@ namespace solidity K(Bytes32, "bytes32", 0) \ K(Bytes, "bytes", 0) \ K(Byte, "byte", 0) \ + K(String, "string", 0) \ K(Address, "address", 0) \ K(Bool, "bool", 0) \ K(Real, "real", 0) \ @@ -312,7 +313,6 @@ namespace solidity K(Match, "match", 0) \ K(Of, "of", 0) \ K(Relocatable, "relocatable", 0) \ - T(String, "string", 0) \ K(Switch, "switch", 0) \ K(Throw, "throw", 0) \ K(Try, "try", 0) \ diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 7577b83a1..0e9ea9876 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -145,6 +145,8 @@ TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) return make_shared(); else if (_typeToken == Token::Bytes) return make_shared(ArrayType::Location::Storage); + else if (_typeToken == Token::String) + return make_shared(ArrayType::Location::Storage, true); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type.")); @@ -663,7 +665,7 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const // let us not allow assignment to memory arrays for now if (convertTo.getLocation() != Location::Storage) return false; - if (convertTo.isByteArray() != isByteArray()) + if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) return false; if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType())) return false; @@ -684,8 +686,12 @@ bool ArrayType::operator==(Type const& _other) const if (_other.getCategory() != getCategory()) return false; ArrayType const& other = dynamic_cast(_other); - if (other.m_location != m_location || other.isByteArray() != isByteArray() || - other.isDynamicallySized() != isDynamicallySized()) + if ( + other.m_location != m_location || + other.isByteArray() != isByteArray() || + other.isString() != isString() || + other.isDynamicallySized() != isDynamicallySized() + ) return false; return isDynamicallySized() || getLength() == other.getLength(); } @@ -736,7 +742,9 @@ unsigned ArrayType::getSizeOnStack() const string ArrayType::toString() const { - if (isByteArray()) + if (isString()) + return "string"; + else if (isByteArray()) return "bytes"; string ret = getBaseType()->toString() + "["; if (!isDynamicallySized()) @@ -746,7 +754,7 @@ string ArrayType::toString() const TypePointer ArrayType::externalType() const { - if (m_isByteArray) + if (m_arrayKind != ArrayKind::Ordinary) return shared_from_this(); if (!m_baseType->externalType()) return TypePointer(); @@ -762,7 +770,7 @@ TypePointer ArrayType::externalType() const shared_ptr ArrayType::copyForLocation(ArrayType::Location _location) const { auto copy = make_shared(_location); - copy->m_isByteArray = m_isByteArray; + copy->m_arrayKind = m_arrayKind; if (m_baseType->getCategory() == Type::Category::Array) copy->m_baseType = dynamic_cast(*m_baseType).copyForLocation(_location); else diff --git a/libsolidity/Types.h b/libsolidity/Types.h index a69df964c..65f6e4474 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -367,10 +367,10 @@ public: virtual Category getCategory() const override { return Category::Array; } - /// Constructor for a byte array ("bytes") - explicit ArrayType(Location _location): + /// Constructor for a byte array ("bytes") and string. + explicit ArrayType(Location _location, bool _isString = false): m_location(_location), - m_isByteArray(true), + m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), m_baseType(std::make_shared(1)) {} /// Constructor for a dynamically sized array type ("type[]") @@ -394,11 +394,17 @@ public: virtual u256 getStorageSize() const override; virtual unsigned getSizeOnStack() const override; virtual std::string toString() const override; - virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } + virtual MemberList const& getMembers() const override + { + return isString() ? EmptyMemberList : s_arrayTypeMemberList; + } virtual TypePointer externalType() const override; Location getLocation() const { return m_location; } - bool isByteArray() const { return m_isByteArray; } + /// @returns true if this is a byte array or a string + bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } + /// @returns true if this is a string + bool isString() const { return m_arrayKind == ArrayKind::String; } TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} u256 const& getLength() const { return m_length; } @@ -407,8 +413,12 @@ public: std::shared_ptr copyForLocation(Location _location) const; private: + /// String is interpreted as a subtype of Bytes. + enum class ArrayKind { Ordinary, Bytes, String }; + Location m_location; - bool m_isByteArray = false; ///< Byte arrays ("bytes") have different semantics from ordinary arrays. + ///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays. + ArrayKind m_arrayKind = ArrayKind::Ordinary; TypePointer m_baseType; bool m_hasDynamicLength = true; u256 m_length; diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 2b4e332c0..bfa4dada9 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -507,7 +507,9 @@ SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) case Type::Category::Array: { ArrayType const* array = dynamic_cast(_type); - if (array->isByteArray()) + if (array->isString()) + r.type = SolidityType::Type::String; + else if (array->isByteArray()) r.type = SolidityType::Type::Bytes; else { diff --git a/mix/SolidityType.h b/mix/SolidityType.h index accdb14b4..75f47e7fa 100644 --- a/mix/SolidityType.h +++ b/mix/SolidityType.h @@ -45,6 +45,7 @@ struct SolidityType Bool, Address, Bytes, + String, Enum, Struct }; diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index f9bf78d0a..f7390dc93 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -568,6 +568,33 @@ BOOST_AUTO_TEST_CASE(return_param_in_abi) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(strings_and_arrays) +{ + // bug #1801 + char const* sourceCode = R"( + contract test { + function f(string a, bytes b, uint[] c) external {} + } + )"; + + char const* interface = R"( + [ + { + "constant" : false, + "name": "f", + "inputs": [ + { "name": "a", "type": "string" }, + { "name": "b", "type": "bytes" }, + { "name": "c", "type": "uint256[]" } + ], + "outputs": [], + "type" : "function" + } + ] + )"; + checkInterface(sourceCode, interface); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index c52bbf9de..48404aaac 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1783,6 +1783,39 @@ BOOST_AUTO_TEST_CASE(uninitialized_var) BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); } +BOOST_AUTO_TEST_CASE(string) +{ + char const* sourceCode = R"( + contract C { + string s; + function f(string x) external { s = x; } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(sourceCode)); +} + +BOOST_AUTO_TEST_CASE(string_index) +{ + char const* sourceCode = R"( + contract C { + string s; + function f() { var a = s[2]; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + +BOOST_AUTO_TEST_CASE(string_length) +{ + char const* sourceCode = R"( + contract C { + string s; + function f() { var a = s.length; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() }