Ensure left/right/mid behave in a compatible way

QString and QStringRef did bounds checking for left/right/mid, whereas
QStringView was asserting on out of bounds.

Relax the behavior for QStringView and do bounds checking on pos/n
as well. This removes a source of potentially hidden errors when porting
from QStringRef (or QString) to QStringView.

Unfortunately, one difference remains, where QByteArray::left/right()
behaves differently (and somewhat more sane) than QString and
QStringRef. We're keeping the difference here, as it has been around
for many years.

Mark left/right/mid as obsolete and to be replaced with the new
first/last/slice methods.

Change-Id: I18c203799ba78c928a4610a6038089f27696c22e
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Lars Knoll 2020-06-02 15:51:15 +02:00
parent fd856532d7
commit d2833a3ce5
9 changed files with 235 additions and 125 deletions

View File

@ -2914,13 +2914,17 @@ bool QByteArray::isLower() const
Returns a byte array that contains the first \a len bytes of this byte Returns a byte array that contains the first \a len bytes of this byte
array. array.
\obsolete Use first() instead in new code.
The entire byte array is returned if \a len is greater than The entire byte array is returned if \a len is greater than
size(). size().
Returns an empty QByteArray if \a len is smaller than 0.
Example: Example:
\snippet code/src_corelib_text_qbytearray.cpp 27 \snippet code/src_corelib_text_qbytearray.cpp 27
\sa startsWith(), right(), mid(), chopped(), chop(), truncate() \sa first(), last(), startsWith(), chopped(), chop(), truncate()
*/ */
QByteArray QByteArray::left(int len) const QByteArray QByteArray::left(int len) const
@ -2935,15 +2939,18 @@ QByteArray QByteArray::left(int len) const
/*! /*!
Returns a byte array that contains the last \a len bytes of this byte array. Returns a byte array that contains the last \a len bytes of this byte array.
\obsolete Use last() instead in new code.
The entire byte array is returned if \a len is greater than The entire byte array is returned if \a len is greater than
size(). size().
Returns an empty QByteArray if \a len is smaller than 0.
Example: Example:
\snippet code/src_corelib_text_qbytearray.cpp 28 \snippet code/src_corelib_text_qbytearray.cpp 28
\sa endsWith(), left(), mid(), chopped(), chop(), truncate() \sa endsWith(), last(), first(), slice(), chopped(), chop(), truncate()
*/ */
QByteArray QByteArray::right(int len) const QByteArray QByteArray::right(int len) const
{ {
if (len >= size()) if (len >= size())
@ -2957,6 +2964,8 @@ QByteArray QByteArray::right(int len) const
Returns a byte array containing \a len bytes from this byte array, Returns a byte array containing \a len bytes from this byte array,
starting at position \a pos. starting at position \a pos.
\obsolete Use slice() instead in new code.
If \a len is -1 (the default), or \a pos + \a len >= size(), If \a len is -1 (the default), or \a pos + \a len >= size(),
returns a byte array containing all bytes starting at position \a returns a byte array containing all bytes starting at position \a
pos until the end of the byte array. pos until the end of the byte array.
@ -2964,13 +2973,15 @@ QByteArray QByteArray::right(int len) const
Example: Example:
\snippet code/src_corelib_text_qbytearray.cpp 29 \snippet code/src_corelib_text_qbytearray.cpp 29
\sa left(), right(), chopped(), chop(), truncate() \sa first(), last(), slice(), chopped(), chop(), truncate()
*/ */
QByteArray QByteArray::mid(int pos, int len) const QByteArray QByteArray::mid(int pos, int len) const
{ {
qsizetype p = pos;
qsizetype l = len;
using namespace QtPrivate; using namespace QtPrivate;
switch (QContainerImplHelper::mid(size(), &pos, &len)) { switch (QContainerImplHelper::mid(size(), &p, &l)) {
case QContainerImplHelper::Null: case QContainerImplHelper::Null:
return QByteArray(); return QByteArray();
case QContainerImplHelper::Empty: case QContainerImplHelper::Empty:
@ -2982,7 +2993,7 @@ QByteArray QByteArray::mid(int pos, int len) const
case QContainerImplHelper::Full: case QContainerImplHelper::Full:
return *this; return *this;
case QContainerImplHelper::Subset: case QContainerImplHelper::Subset:
return QByteArray(d.data() + pos, len); return QByteArray(d.data() + p, l);
} }
Q_UNREACHABLE(); Q_UNREACHABLE();
return QByteArray(); return QByteArray();

View File

@ -4530,12 +4530,14 @@ QString QString::section(const QRegularExpression &re, int start, int end, Secti
Returns a substring that contains the \a n leftmost characters Returns a substring that contains the \a n leftmost characters
of the string. of the string.
\obsolete Use first() instead in new code.
The entire string is returned if \a n is greater than or equal The entire string is returned if \a n is greater than or equal
to size(), or less than zero. to size(), or less than zero.
\snippet qstring/main.cpp 31 \snippet qstring/main.cpp 31
\sa right(), mid(), startsWith(), chopped(), chop(), truncate() \sa first(), last(), startsWith(), chopped(), chop(), truncate()
*/ */
QString QString::left(int n) const QString QString::left(int n) const
{ {
@ -4548,12 +4550,14 @@ QString QString::left(int n) const
Returns a substring that contains the \a n rightmost characters Returns a substring that contains the \a n rightmost characters
of the string. of the string.
\obsolete Use last() instead in new code.
The entire string is returned if \a n is greater than or equal The entire string is returned if \a n is greater than or equal
to size(), or less than zero. to size(), or less than zero.
\snippet qstring/main.cpp 48 \snippet qstring/main.cpp 48
\sa left(), mid(), endsWith(), chopped(), chop(), truncate() \sa endsWith(), last(), first(), slice(), chopped(), chop(), truncate()
*/ */
QString QString::right(int n) const QString QString::right(int n) const
{ {
@ -4566,6 +4570,8 @@ QString QString::right(int n) const
Returns a string that contains \a n characters of this string, Returns a string that contains \a n characters of this string,
starting at the specified \a position index. starting at the specified \a position index.
\obsolete Use slice() instead in new code.
Returns a null string if the \a position index exceeds the Returns a null string if the \a position index exceeds the
length of the string. If there are less than \a n characters length of the string. If there are less than \a n characters
available in the string starting at the given \a position, or if available in the string starting at the given \a position, or if
@ -4576,13 +4582,15 @@ QString QString::right(int n) const
\snippet qstring/main.cpp 34 \snippet qstring/main.cpp 34
\sa left(), right(), chopped(), chop(), truncate() \sa first(), last(), slice(), chopped(), chop(), truncate()
*/ */
QString QString::mid(int position, int n) const QString QString::mid(int position, int n) const
{ {
qsizetype p = position;
qsizetype l = n;
using namespace QtPrivate; using namespace QtPrivate;
switch (QContainerImplHelper::mid(size(), &position, &n)) { switch (QContainerImplHelper::mid(size(), &p, &l)) {
case QContainerImplHelper::Null: case QContainerImplHelper::Null:
return QString(); return QString();
case QContainerImplHelper::Empty: case QContainerImplHelper::Empty:
@ -4594,7 +4602,7 @@ QString QString::mid(int position, int n) const
case QContainerImplHelper::Full: case QContainerImplHelper::Full:
return *this; return *this;
case QContainerImplHelper::Subset: case QContainerImplHelper::Subset:
return QString(constData() + position, n); return QString(constData() + p, l);
} }
Q_UNREACHABLE(); Q_UNREACHABLE();
return QString(); return QString();
@ -9375,28 +9383,17 @@ QString &QString::setRawData(const QChar *unicode, int size)
\sa crbegin(), rend(), cend() \sa crbegin(), rend(), cend()
*/ */
/*! \fn QLatin1String QLatin1String::mid(int start) const
\since 5.8
Returns the substring starting at position \a start in this object,
and extending to the end of the string.
\note This function performs no error checking.
The behavior is undefined when \a start < 0 or \a start > size().
\sa left(), right(), chopped(), chop(), truncate()
*/
/*! \fn QLatin1String QLatin1String::mid(int start, int length) const /*! \fn QLatin1String QLatin1String::mid(int start, int length) const
\since 5.8 \since 5.8
\overload
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
\a start in this object. \a start in this object.
\note This function performs no error checking. Returns a null string if the \a start index exceeds the
The behavior is undefined when \a start < 0, \a length < 0, length of the string. If there are less than \a length characters
or \a start + \a length > size(). available in the string starting at \a start, or if
\a length is negative (default), the function returns all characters
that are available from \a start.
\sa left(), right(), chopped(), chop(), truncate() \sa left(), right(), chopped(), chop(), truncate()
*/ */
@ -9407,8 +9404,8 @@ QString &QString::setRawData(const QChar *unicode, int size)
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
0 in this object. 0 in this object.
\note This function performs no error checking. The entire string is returned if \a length is greater than or equal
The behavior is undefined when \a length < 0 or \a length > size(). to size(), or less than zero.
\sa mid(), right(), chopped(), chop(), truncate() \sa mid(), right(), chopped(), chop(), truncate()
*/ */
@ -9419,8 +9416,8 @@ QString &QString::setRawData(const QChar *unicode, int size)
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
size() - \a length in this object. size() - \a length in this object.
\note This function performs no error checking. The entire string is returned if \a length is greater than or equal
The behavior is undefined when \a length < 0 or \a length > size(). to size(), or less than zero.
\sa mid(), left(), chopped(), chop(), truncate() \sa mid(), left(), chopped(), chop(), truncate()
*/ */
@ -10777,8 +10774,10 @@ QStringRef QString::rightRef(int n) const
*/ */
QStringRef QStringRef::mid(int pos, int n) const QStringRef QStringRef::mid(int pos, int n) const
{ {
qsizetype p = pos;
qsizetype l = n;
using namespace QtPrivate; using namespace QtPrivate;
switch (QContainerImplHelper::mid(m_size, &pos, &n)) { switch (QContainerImplHelper::mid(m_size, &p, &l)) {
case QContainerImplHelper::Null: case QContainerImplHelper::Null:
return QStringRef(); return QStringRef();
case QContainerImplHelper::Empty: case QContainerImplHelper::Empty:
@ -10786,7 +10785,7 @@ QStringRef QStringRef::mid(int pos, int n) const
case QContainerImplHelper::Full: case QContainerImplHelper::Full:
return *this; return *this;
case QContainerImplHelper::Subset: case QContainerImplHelper::Subset:
return QStringRef(m_string, pos + m_position, n); return QStringRef(m_string, p + m_position, l);
} }
Q_UNREACHABLE(); Q_UNREACHABLE();
return QStringRef(); return QStringRef();

View File

@ -179,14 +179,26 @@ public:
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
Q_DECL_CONSTEXPR QLatin1String mid(int pos) const constexpr QLatin1String mid(int pos, int n = -1) const
{ return Q_ASSERT(pos >= 0), Q_ASSERT(pos <= size()), QLatin1String(m_data + pos, m_size - pos); } {
Q_DECL_CONSTEXPR QLatin1String mid(int pos, int n) const qsizetype p = pos;
{ return Q_ASSERT(pos >= 0), Q_ASSERT(n >= 0), Q_ASSERT(pos + n <= size()), QLatin1String(m_data + pos, n); } qsizetype l = n;
Q_DECL_CONSTEXPR QLatin1String left(int n) const using namespace QtPrivate;
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QLatin1String(m_data, n); } auto result = QContainerImplHelper::mid(size(), &p, &l);
Q_DECL_CONSTEXPR QLatin1String right(int n) const return result == QContainerImplHelper::Null ? QLatin1String() : QLatin1String(m_data + p, l);
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QLatin1String(m_data + m_size - n, n); } }
constexpr QLatin1String left(int n) const
{
if (size_t(n) >= size_t(size()))
n = size();
return QLatin1String(m_data, n);
}
constexpr QLatin1String right(int n) const
{
if (size_t(n) >= size_t(size()))
n = size();
return QLatin1String(m_data + m_size - n, n);
}
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QLatin1String chopped(int n) const Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QLatin1String chopped(int n) const
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QLatin1String(m_data, m_size - n); } { return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QLatin1String(m_data, m_size - n); }

View File

@ -610,50 +610,49 @@ QT_BEGIN_NAMESPACE
\sa back(), front(), first() \sa back(), front(), first()
*/ */
/*!
\fn QStringView QStringView::mid(qsizetype start) const
Returns the substring starting at position \a start in this object,
and extending to the end of the string.
\note The behavior is undefined when \a start < 0 or \a start > size().
\sa left(), right(), chopped(), chop(), truncate()
*/
/*! /*!
\fn QStringView QStringView::mid(qsizetype start, qsizetype length) const \fn QStringView QStringView::mid(qsizetype start, qsizetype length) const
\overload
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
\a start in this object. \a start in this object.
\note The behavior is undefined when \a start < 0, \a length < 0, \obsolete Use slice() instead in new code.
or \a start + \a length > size().
\sa left(), right(), chopped(), chop(), truncate() Returns an empty string view if \a start exceeds the
length of the string. If there are less than \a length characters
available in the string starting at \a start, or if
\a length is negative (default), the function returns all characters that
are available from \a start.
\sa first(), last(), slice(), chopped(), chop(), truncate()
*/ */
/*! /*!
\fn QStringView QStringView::left(qsizetype length) const \fn QStringView QStringView::left(qsizetype length) const
\obsolete Use first() instead in new code.
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
0 in this object. 0 in this object.
\note The behavior is undefined when \a length < 0 or \a length > size(). The entire string is returned if \a length is greater than or equal
to size(), or less than zero.
\sa mid(), right(), chopped(), chop(), truncate() \sa first(), last(), slice(), startsWith(), chopped(), chop(), truncate()
*/ */
/*! /*!
\fn QStringView QStringView::right(qsizetype length) const \fn QStringView QStringView::right(qsizetype length) const
\obsolete Use last() instead in new code.
Returns the substring of length \a length starting at position Returns the substring of length \a length starting at position
size() - \a length in this object. size() - \a length in this object.
\note The behavior is undefined when \a length < 0 or \a length > size(). The entire string is returned if \a length is greater than or equal
to size(), or less than zero.
\sa mid(), left(), chopped(), chop(), truncate() \sa first(), last(), slice(), endsWith(), chopped(), chop(), truncate()
*/ */
/*! /*!

View File

@ -257,14 +257,24 @@ public:
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QChar at(qsizetype n) const { return (*this)[n]; } Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QChar at(qsizetype n) const { return (*this)[n]; }
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView mid(qsizetype pos) const Q_REQUIRED_RESULT constexpr QStringView mid(qsizetype pos, qsizetype n = -1) const
{ return Q_ASSERT(pos >= 0), Q_ASSERT(pos <= size()), QStringView(m_data + pos, m_size - pos); } {
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView mid(qsizetype pos, qsizetype n) const using namespace QtPrivate;
{ return Q_ASSERT(pos >= 0), Q_ASSERT(n >= 0), Q_ASSERT(pos + n <= size()), QStringView(m_data + pos, n); } auto result = QContainerImplHelper::mid(size(), &pos, &n);
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView left(qsizetype n) const return result == QContainerImplHelper::Null ? QStringView() : QStringView(m_data + pos, n);
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data, n); } }
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView right(qsizetype n) const Q_REQUIRED_RESULT constexpr QStringView left(qsizetype n) const
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data + m_size - n, n); } {
if (size_t(n) >= size_t(size()))
n = size();
return QStringView(m_data, n);
}
Q_REQUIRED_RESULT constexpr QStringView right(qsizetype n) const
{
if (size_t(n) >= size_t(size()))
n = size();
return QStringView(m_data + m_size - n, n);
}
Q_REQUIRED_RESULT constexpr QStringView first(qsizetype n) const Q_REQUIRED_RESULT constexpr QStringView first(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QStringView(m_data, int(n)); } { Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QStringView(m_data, int(n)); }

View File

@ -281,33 +281,4 @@ void QArrayData::deallocate(QArrayData *data, size_t objectSize,
::free(data); ::free(data);
} }
namespace QtPrivate {
/*!
\internal
*/
QContainerImplHelper::CutResult QContainerImplHelper::mid(int originalLength, int *_position, int *_length)
{
int &position = *_position;
int &length = *_length;
if (position > originalLength)
return Null;
if (position < 0) {
if (length < 0 || length + position >= originalLength)
return Full;
if (length + position <= 0)
return Null;
length += position;
position = 0;
} else if (uint(length) > uint(originalLength - position)) {
length = originalLength - position;
}
if (position == 0 && length == originalLength)
return Full;
return length > 0 ? Subset : Empty;
}
}
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -306,7 +306,37 @@ namespace QtPrivate {
struct Q_CORE_EXPORT QContainerImplHelper struct Q_CORE_EXPORT QContainerImplHelper
{ {
enum CutResult { Null, Empty, Full, Subset }; enum CutResult { Null, Empty, Full, Subset };
static CutResult mid(int originalLength, int *position, int *length); static constexpr CutResult mid(qsizetype originalLength, qsizetype *_position, qsizetype *_length)
{
qsizetype &position = *_position;
qsizetype &length = *_length;
if (position > originalLength) {
position = 0;
length = 0;
return Null;
}
if (position < 0) {
if (length < 0 || length + position >= originalLength) {
position = 0;
length = originalLength;
return Full;
}
if (length + position <= 0) {
position = length = 0;
return Null;
}
length += position;
position = 0;
} else if (size_t(length) > size_t(originalLength - position)) {
length = originalLength - position;
}
if (position == 0 && length == originalLength)
return Full;
return length > 0 ? Subset : Empty;
}
}; };
} }

View File

@ -710,8 +710,10 @@ int QVector<T>::lastIndexOf(const T &t, int from) const noexcept
template <typename T> template <typename T>
inline QVector<T> QVector<T>::mid(int pos, int len) const inline QVector<T> QVector<T>::mid(int pos, int len) const
{ {
qsizetype p = pos;
qsizetype l = len;
using namespace QtPrivate; using namespace QtPrivate;
switch (QContainerImplHelper::mid(d.size, &pos, &len)) { switch (QContainerImplHelper::mid(d.size, &p, &l)) {
case QContainerImplHelper::Null: case QContainerImplHelper::Null:
case QContainerImplHelper::Empty: case QContainerImplHelper::Empty:
return QVector(); return QVector();
@ -722,8 +724,8 @@ inline QVector<T> QVector<T>::mid(int pos, int len) const
} }
// Allocate memory // Allocate memory
DataPointer copied(Data::allocate(len)); DataPointer copied(Data::allocate(l));
copied->copyAppend(constBegin() + pos, constBegin() + pos + len); copied->copyAppend(constBegin() + p, constBegin() + p + l);
return copied; return copied;
} }

View File

@ -644,9 +644,6 @@ private:
void chop_data(); void chop_data();
template <typename String> void chop_impl(); template <typename String> void chop_impl();
void truncate_data() { left_data(); }
template <typename String> void truncate_impl();
private Q_SLOTS: private Q_SLOTS:
void mid_QString_data() { mid_data(); } void mid_QString_data() { mid_data(); }
@ -660,16 +657,16 @@ private Q_SLOTS:
void mid_QByteArray_data() { mid_data(); } void mid_QByteArray_data() { mid_data(); }
void mid_QByteArray() { mid_impl<QByteArray>(); } void mid_QByteArray() { mid_impl<QByteArray>(); }
void left_truncate_QString_data() { left_data(); } void left_QString_data() { left_data(); }
void left_truncate_QString() { left_impl<QString>(); } void left_QString() { left_impl<QString>(); }
void left_truncate_QStringRef_data() { left_data(); } void left_QStringRef_data() { left_data(); }
void left_truncate_QStringRef() { left_impl<QStringRef>(); } void left_QStringRef() { left_impl<QStringRef>(); }
void left_truncate_QStringView_data() { left_data(); } void left_QStringView_data() { left_data(); }
void left_truncate_QStringView() { left_impl<QStringView>(); } void left_QStringView() { left_impl<QStringView>(); }
void left_truncate_QLatin1String_data() { left_data(); } void left_QLatin1String_data() { left_data(); }
void left_truncate_QLatin1String() { left_impl<QLatin1String>(); } void left_QLatin1String() { left_impl<QLatin1String>(); }
void left_truncate_QByteArray_data() { left_data(); } void left_QByteArray_data();
void left_truncate_QByteArray() { left_impl<QByteArray>(); } void left_QByteArray() { left_impl<QByteArray>(); }
void right_QString_data() { right_data(); } void right_QString_data() { right_data(); }
void right_QString() { right_impl<QString>(); } void right_QString() { right_impl<QString>(); }
@ -679,7 +676,7 @@ private Q_SLOTS:
void right_QStringView() { right_impl<QStringView>(); } void right_QStringView() { right_impl<QStringView>(); }
void right_QLatin1String_data() { right_data(); } void right_QLatin1String_data() { right_data(); }
void right_QLatin1String() { right_impl<QLatin1String>(); } void right_QLatin1String() { right_impl<QLatin1String>(); }
void right_QByteArray_data() { right_data(); } void right_QByteArray_data();
void right_QByteArray() { right_impl<QByteArray>(); } void right_QByteArray() { right_impl<QByteArray>(); }
void slice_QString_data() { slice_data(); } void slice_QString_data() { slice_data(); }
@ -1533,6 +1530,37 @@ void tst_QStringApiSymmetry::tok_impl() const
void tst_QStringApiSymmetry::mid_data() void tst_QStringApiSymmetry::mid_data()
{ {
slice_data(); slice_data();
// mid() has a wider contract compared to slize(), so test those cases here:
#define ROW(base, p, n, r1, r2) \
QTest::addRow("%s %d %d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2)
ROW(a, -1, 0, a, null);
ROW(a, -1, 2, a, a);
ROW(a, -1, 3, a, a);
ROW(a, 0, -1, a, a);
ROW(a, 0, 2, a, a);
ROW(a, -1, -1, a, a);
ROW(a, 1, -1, empty, empty);
ROW(a, 1, 1, empty, empty);
ROW(a, 2, -1, null, null);
ROW(a, 2, 1, null, null);
ROW(abc, -1, -1, abc, abc);
ROW(abc, -1, 0, abc, null);
ROW(abc, -1, 2, abc, a);
ROW(abc, -1, 3, abc, ab);
ROW(abc, -1, 5, abc, abc);
ROW(abc, 0, -1, abc, abc);
ROW(abc, 0, 5, abc, abc);
ROW(abc, -1, 1, abc, null);
ROW(abc, -1, 2, abc, a);
ROW(abc, -1, 4, abc, abc);
ROW(abc, 1, -1, bc, bc);
ROW(abc, 1, 1, bc, b);
ROW(abc, 3, -1, empty, empty);
ROW(abc, 3, 1, empty, empty);
#undef ROW
} }
template <typename String> template <typename String>
@ -1578,6 +1606,35 @@ void tst_QStringApiSymmetry::mid_impl()
void tst_QStringApiSymmetry::left_data() void tst_QStringApiSymmetry::left_data()
{ {
first_data(); first_data();
// specific data testing out of bounds cases
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, -1, a);
ROW(a, 2, a);
ROW(ab, -100, ab);
ROW(ab, 100, ab);
#undef ROW
}
// This is different from the rest for historical reasons. As we're replacing
// left() with first() as the recommended API, there's no point fixing this anymore
void tst_QStringApiSymmetry::left_QByteArray_data()
{
first_data();
// specific data testing out of bounds cases
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, -1, empty);
ROW(a, 2, a);
ROW(ab, -100, empty);
ROW(ab, 100, ab);
#undef ROW
} }
template <typename String> template <typename String>
@ -1602,14 +1659,6 @@ void tst_QStringApiSymmetry::left_impl()
{ {
const auto left = detached(s).left(n); const auto left = detached(s).left(n);
QCOMPARE(left, result);
QCOMPARE(left.isNull(), result.isNull());
QCOMPARE(left.isEmpty(), result.isEmpty());
}
{
auto left = s;
left.truncate(n);
QCOMPARE(left, result); QCOMPARE(left, result);
QCOMPARE(left.isNull(), result.isNull()); QCOMPARE(left.isNull(), result.isNull());
QCOMPARE(left.isEmpty(), result.isEmpty()); QCOMPARE(left.isEmpty(), result.isEmpty());
@ -1619,6 +1668,35 @@ void tst_QStringApiSymmetry::left_impl()
void tst_QStringApiSymmetry::right_data() void tst_QStringApiSymmetry::right_data()
{ {
last_data(); last_data();
// specific data testing out of bounds cases
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, -1, a);
ROW(a, 2, a);
ROW(ab, -100, ab);
ROW(ab, 100, ab);
#undef ROW
}
// This is different from the rest for historical reasons. As we're replacing
// left() with first() as the recommended API, there's no point fixing this anymore
void tst_QStringApiSymmetry::right_QByteArray_data()
{
last_data();
// specific data testing out of bounds cases
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, -1, empty);
ROW(a, 2, a);
ROW(ab, -100, empty);
ROW(ab, 100, ab);
#undef ROW
} }
template <typename String> template <typename String>
@ -1661,8 +1739,6 @@ void tst_QStringApiSymmetry::slice_data()
// QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << 0 << QStringRef() << QStringRef(); // QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << 0 << QStringRef() << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << 0 << QStringRef(&empty) << QStringRef(&empty); QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << 0 << QStringRef(&empty) << QStringRef(&empty);
// Some classes' mid() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, p, n, r1, r2) \ #define ROW(base, p, n, r1, r2) \
QTest::addRow("%s%d%d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2) QTest::addRow("%s%d%d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2)