diff --git a/src/corelib/text/qbytearray.cpp b/src/corelib/text/qbytearray.cpp index 496d6eca7fc..72df37ccee0 100644 --- a/src/corelib/text/qbytearray.cpp +++ b/src/corelib/text/qbytearray.cpp @@ -2011,6 +2011,31 @@ void QByteArray::expand(qsizetype i) resize(qMax(i + 1, size())); } +/*! + \fn QByteArray &QByteArray::nullTerminate() const + \since 6.9 + + If this byte array's data isn't null-terminated, this method will make + a deep-copy of the data and make it null-terminated. + + A QByteArray is null-terminated by default, however in some cases + (e.g. when using fromRawData()), the data doesn't necessarily end with + a \c {\0} character, which could be a problem when calling methods that + expect a null-terminated string (for example, C API). + + \sa nullTerminated(), fromRawData(), setRawData() +*/ + +/*! + \fn QByteArray QByteArray::nullTerminated() const + \since 6.9 + + Returns a copy of this byte array that is always null-terminated. + See nullTerminate(). + + \sa nullTerminate(), fromRawData(), setRawData() +*/ + /*! \fn QByteArray &QByteArray::prepend(QByteArrayView ba) @@ -4419,7 +4444,7 @@ QByteArray QByteArray::number(double n, char format, int precision) byte array to a function accepting a \c{const char *} expected to be '\\0'-terminated will fail. - \sa setRawData(), data(), constData() + \sa setRawData(), data(), constData(), nullTerminate(), nullTerminated() */ /*! @@ -4434,7 +4459,7 @@ QByteArray QByteArray::number(double n, char format, int precision) This function can be used instead of fromRawData() to re-use existing QByteArray objects to save memory re-allocations. - \sa fromRawData(), data(), constData() + \sa fromRawData(), data(), constData(), nullTermiante(), nullTerminated() */ QByteArray &QByteArray::setRawData(const char *data, qsizetype size) { diff --git a/src/corelib/text/qbytearray.h b/src/corelib/text/qbytearray.h index f6d626e9a49..1e1a8c9c5e4 100644 --- a/src/corelib/text/qbytearray.h +++ b/src/corelib/text/qbytearray.h @@ -511,6 +511,30 @@ public: #endif explicit inline QByteArray(DataPointer &&dd) : d(std::move(dd)) {} + [[nodiscard]] QByteArray nullTerminated() const & + { + // Ensure \0-termination for fromRawData() byte arrays + if (!d.isMutable()) + return QByteArray{constData(), size()}; + return *this; + } + + [[nodiscard]] QByteArray nullTerminated() && + { + // Ensure \0-termination for fromRawData() byte arrays + if (!d.isMutable()) + return QByteArray{constData(), size()}; + return std::move(*this); + } + + QByteArray &nullTerminate() + { + // Ensure \0-termination for fromRawData() byte arrays + if (!d.isMutable()) + *this = QByteArray{constData(), size()}; + return *this; + } + private: friend bool comparesEqual(const QByteArray &lhs, const QByteArrayView &rhs) noexcept { return QByteArrayView(lhs) == rhs; } diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 1d1485d20f6..e225852b0f3 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -7063,6 +7063,31 @@ const ushort *QString::utf16() const return reinterpret_cast(d.data()); } +/*! + \fn QString nullTerminate() const + \since 6.9 + + If this string data isn't null-terminated, this method will make a deep + copy of the data and make it null-terminated(). + + A QString is null-terminated by default, however in some cases (e.g. + when using fromRawData()), the string data doesn't necessarily end + with a \c {\0} character, which could be a problem when calling methods + that expect a null-terminated string. + + \sa nullTerminated(), fromRawData(), setRawData() +*/ + +/*! + \fn QString nullTerminated() const + \since 6.9 + + Returns a copy of this string that is always null-terminated. + See nullTerminate(). + + \sa nullTerminated(), fromRawData(), setRawData() +*/ + /*! Returns a string of size \a width that contains this string padded by the \a fill character. @@ -9375,7 +9400,8 @@ QString::iterator QString::erase(QString::const_iterator first, QString::const_i '\\0'-terminated string (although utf16() does, at the cost of copying the raw data). - \sa fromUtf16(), setRawData() + \sa fromUtf16(), setRawData(), data(), constData(), + nullTerminate(), nullTerminated() */ QString QString::fromRawData(const QChar *unicode, qsizetype size) { @@ -9394,7 +9420,7 @@ QString QString::fromRawData(const QChar *unicode, qsizetype size) This function can be used instead of fromRawData() to re-use existings QString objects to save memory re-allocations. - \sa fromRawData() + \sa fromRawData(), nullTerminate(), nullTerminated() */ QString &QString::setRawData(const QChar *unicode, qsizetype size) { diff --git a/src/corelib/text/qstring.h b/src/corelib/text/qstring.h index 0cf01ff2c3a..b36ac709a2c 100644 --- a/src/corelib/text/qstring.h +++ b/src/corelib/text/qstring.h @@ -699,6 +699,29 @@ public: [[nodiscard]] QString repeated(qsizetype times) const; const ushort *utf16() const; // ### Qt 7 char16_t + [[nodiscard]] QString nullTerminated() const & + { + // ensure '\0'-termination for ::fromRawData strings + if (!d->isMutable()) + return QString{constData(), size()}; + return *this; + } + + [[nodiscard]] QString nullTerminated() && + { + // ensure '\0'-termination for ::fromRawData strings + if (!d->isMutable()) + return QString{constData(), size()}; + return std::move(*this); + } + + QString &nullTerminate() + { + // ensure '\0'-termination for ::fromRawData strings + if (!d->isMutable()) + *this = QString{constData(), size()}; + return *this; + } #if !defined(Q_QDOC) [[nodiscard]] QByteArray toLatin1() const & diff --git a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp index 6ad3165a5e8..df7e14ef3c7 100644 --- a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp +++ b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp @@ -60,6 +60,7 @@ private slots: void appendFromRawData(); void appendExtended_data(); void appendExtended(); + void nullTerminated(); void appendEmptyNull(); void assign(); void assignShared(); @@ -983,6 +984,42 @@ void tst_QByteArray::appendExtended() QCOMPARE(array.size(), 11); } +void tst_QByteArray::nullTerminated() +{ + const char ptr[] = {'A', 'B', 'C'}; + + QTest::ThrowOnFailEnabler throwOnFail; + + auto check = [&ptr](const QByteArray &ba) { + QCOMPARE_NE(reinterpret_cast(ba.constData()), ptr); + QCOMPARE(ba.constData()[0], ptr[0]); + QCOMPARE(ba.constData()[1], ptr[1]); + QCOMPARE(ba.constData()[2], '\0'); + QCOMPARE(ba.size(), 2); + }; + + { + auto ba = QByteArray::fromRawData(ptr, 2); + QCOMPARE_EQ(reinterpret_cast(ba.constData()), ptr); + QCOMPARE(ba.constData()[0], ptr[0]); + QCOMPARE(ba.constData()[1], ptr[1]); + QCOMPARE(ba.size(), 2); + + check(ba.nullTerminated()); + check(QByteArray::fromRawData(ptr, 2).nullTerminated()); // rvalue + } + + { + auto ba = QByteArray::fromRawData(ptr, 2); + QCOMPARE_EQ(reinterpret_cast(ba.constData()), ptr); + QCOMPARE(ba.constData()[0], ptr[0]); + QCOMPARE(ba.constData()[1], ptr[1]); + QCOMPARE(ba.size(), 2); + + check(ba.nullTerminate()); + } +} + void tst_QByteArray::appendEmptyNull() { QByteArray a; diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index 3acc02c0221..6e657a29cd6 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -370,6 +370,7 @@ private slots: void check_QDataStream(); void fromRawData(); void setRawData(); + void nullTerminated(); void setUnicode(); void endsWith(); void startsWith(); @@ -5934,6 +5935,42 @@ void tst_QString::setRawData() QVERIFY(cstr.data_ptr() != csd); } +void tst_QString::nullTerminated() +{ + const QChar ptr[] = { u'ሴ', u'ʎ', u'\0' }; + + QTest::ThrowOnFailEnabler thrower; + + auto check = [ptr] (const QString &r) { + QVERIFY(r.constData() != ptr); + QCOMPARE(r.constData()[0], ptr[0]); + QCOMPARE(r.constData()[1], ptr[1]); + QCOMPARE(r.constData()[2], u'\0'); + QCOMPARE(r.size(), 2); + }; + + { + QString str = QString::fromRawData(ptr, 2); + QCOMPARE(str.constData(), ptr); + QCOMPARE(str.constData()[0], ptr[0]); + QCOMPARE(str.constData()[1], ptr[1]); + QCOMPARE(str.size(), 2); + + check(str.nullTerminated()); + check(QString::fromRawData(ptr, 2).nullTerminated()); // rvalue + } + + { + QString str = QString::fromRawData(ptr, 2); + QCOMPARE(str.constData(), ptr); + QCOMPARE(str.constData()[0], ptr[0]); + QCOMPARE(str.constData()[1], ptr[1]); + QCOMPARE(str.size(), 2); + + check(str.nullTerminate()); + } +} + void tst_QString::setUnicode() { const QChar ptr[] = { u'ሴ', QChar(0x0000) };