QIODevice: Add overloads of QIODevice::readLineInto() that takes QSpan

The existing QIODevice::readLineInto takes a QByteArray *, so it's
dependent on a QByteArray and therefore needs at least one (initial)
allocation.
Add QIODevice::readLineInto() that takes a QSpan, allowing the user to
provide a buffer to write into and returns a QByteArrayView.

QBAV and QSpan are not null terminated. Make null termination optional
when reading using QIODevicePrivate::readLine(). Extend the function to
accept a ReadLineOption enum parameter with a default value of
"NullTerminated"  to preserve old calls. Do the same to
QRingBufferRef::readLine().
Add QRingBuffer::readLineWithoutTerminatingNull().

[ChangeLog][QtCore][QIODevice] Added overloads of
QIODevice::readLineInto() taking QSpan and returnig a QByteArrayView.

Task-number: QTBUG-126574
Change-Id: I278a42cef4c9aa64ed884027f79105b5b7d44813
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Rym Bouabid 2024-10-30 09:23:33 +01:00 committed by Marc Mutz
parent e23dc7c420
commit c39c3fe0cb
6 changed files with 194 additions and 15 deletions

View File

@ -86,6 +86,14 @@ static void checkWarnMessage(const QIODevice *device, const char *function, cons
} \
} while (0)
#define CHECK_LINEMAXLEN_1(function, returnType) \
do { \
if (maxSize < 1) { \
checkWarnMessage(this, #function, "Called with maxSize < 1"); \
return returnType; \
} \
} while (0)
#define CHECK_MAXBYTEARRAYSIZE(function) \
do { \
if (maxSize >= QByteArray::maxSize()) { \
@ -1335,13 +1343,17 @@ qint64 QIODevice::readLine(char *data, qint64 maxSize)
/*!
\internal
*/
qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize, ReadLineOption option)
{
Q_Q(QIODevice);
Q_ASSERT(maxSize >= 2);
const auto appendNullByte = option & ReadLineOption::NullTerminated;
// Leave room for a '\0'
--maxSize;
if (appendNullByte) {
Q_ASSERT(maxSize >= 2);
--maxSize; // Leave room for a '\0'
} else {
Q_ASSERT(maxSize >= 1);
}
const bool sequential = isSequential();
const bool keepDataInBuffer = sequential && transactionStarted;
@ -1358,8 +1370,8 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
q->readData(data, 0);
}
} else if (!buffer.isEmpty()) {
// QRingBuffer::readLine() terminates the line with '\0'
readSoFar = buffer.readLine(data, maxSize + 1);
// QRingBuffer::readLine() terminates the line with '\0' if requested
readSoFar = buffer.readLine(data, maxSize + (appendNullByte ? 1 : 0), option);
if (buffer.isEmpty())
q->readData(data, 0);
if (!sequential)
@ -1380,7 +1392,9 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
data[readSoFar - 1] = '\n';
}
}
data[readSoFar] = '\0';
if (appendNullByte)
data[readSoFar] = '\0';
return readSoFar;
}
}
@ -1401,7 +1415,8 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
}
#endif
if (readBytes < 0) {
data[readSoFar] = '\0';
if (appendNullByte)
data[readSoFar] = '\0';
return readSoFar ? readSoFar : -1;
}
readSoFar += readBytes;
@ -1411,12 +1426,14 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
// assume the device position is invalid and force a seek.
devicePos = qint64(-1);
}
data[readSoFar] = '\0';
if (appendNullByte)
data[readSoFar] = '\0';
if (openMode & QIODevice::Text) {
if (readSoFar > 1 && data[readSoFar - 1] == '\n' && data[readSoFar - 2] == '\r') {
data[readSoFar - 2] = '\n';
data[readSoFar - 1] = '\0';
if (appendNullByte)
data[readSoFar - 1] = '\0';
--readSoFar;
}
}
@ -1527,6 +1544,64 @@ bool QIODevice::readLineInto(QByteArray *line, qint64 maxSize)
return true;
}
/*!
\since 6.9
\fn QIODevice::readLineInto(QSpan<char> buffer);
\fn QIODevice::readLineInto(QSpan<uchar> buffer);
\fn QIODevice::readLineInto(QSpan<std::byte> buffer);
Reads a line from this device into \a buffer, and returns the subset of
\a buffer that contains the data read.
If \a buffer's size is smaller than the length of the line, only the
characters that fit within \a buffer are read and returned. In this case,
calling readLineInto() again will retrieve the remainder of the line.
To determine whether the entire line was read, first check if the device is
atEnd(), in case the last line didn't end with a newline. If not atEnd(),
verify whether the returned view ends with '\n'. Otherwise, another call to
readLineInto() is required.
The resulting line can have trailing end-of-line characters ("\n" or "\r\n"),
so calling QByteArrayView::trimmed() may be necessary.
In case an error occurred, this function returns a null QByteArrayView.
Otherwise it is a sub-span of \a buffer. If no data was currently
available for reading or the device is atEnd(), this function returns an
empty QByteArrayView.
Note that the return value is not null terminated. If you want
null-termination, you can pass \c{buffer.chopped(1)} and then insert '\\0'
at \c{buffer[result.size()]}.
\sa readLine()
*/
QByteArrayView QIODevice::readLineInto(QSpan<std::byte> buffer)
{
Q_D(QIODevice);
qint64 maxSize = buffer.size();
#if defined QIODEVICE_DEBUG
printf("%p QIODevice::readLineInto(%lld), d->pos = %lld, d->buffer.size() = %lld\n",
this, qlonglong(maxSize), qlonglong(d->pos), qlonglong(d->buffer.size()));
#endif
CHECK_READABLE(readLineInto, {});
if (atEnd())
return buffer.first(0);
CHECK_LINEMAXLEN_1(readLineInto, {});
CHECK_MAXBYTEARRAYSIZE(readLineInto);
const qint64 readBytes = d->readLine(reinterpret_cast<char*>(buffer.data()), buffer.size(),
QIODevicePrivate::ReadLineOption::NotNullTerminated);
if (readBytes < 0)
return {};
return buffer.first(readBytes);
}
/*!
Reads up to \a maxSize characters into \a data and returns the
number of characters read.

View File

@ -12,6 +12,7 @@
#include <QtCore/qobjectdefs.h>
#include <QtCore/qscopedpointer.h>
#endif
#include <QtCore/qspan.h>
#include <QtCore/qstring.h>
#ifdef open
@ -79,6 +80,13 @@ public:
qint64 readLine(char *data, qint64 maxlen);
QByteArray readLine(qint64 maxlen = 0);
bool readLineInto(QByteArray *result, qint64 maxlen = 0);
QByteArrayView readLineInto(QSpan<char> buffer)
{ return readLineInto(as_writable_bytes(buffer)); }
QByteArrayView readLineInto(QSpan<uchar> buffer)
{ return readLineInto(as_writable_bytes(buffer)); }
QByteArrayView readLineInto(QSpan<std::byte> buffer);
virtual bool canReadLine() const;
void startTransaction();

View File

@ -45,6 +45,12 @@ public:
QIODevicePrivate();
virtual ~QIODevicePrivate();
enum class ReadLineOption {
NotNullTerminated,
NullTerminated,
};
Q_DECLARE_FLAGS(ReadLineOptions, ReadLineOption)
// The size of this class is a subject of the library hook data.
// When adding a new member, do not make gaps and be aware
// about the padding. Accordingly, adjust offsets in
@ -85,7 +91,14 @@ public:
inline void append(const char *data, qint64 size) { Q_ASSERT(m_buf); m_buf->append(data, size); }
inline void append(const QByteArray &qba) { Q_ASSERT(m_buf); m_buf->append(qba); }
inline qint64 skip(qint64 length) { return (m_buf ? m_buf->skip(length) : Q_INT64_C(0)); }
inline qint64 readLine(char *data, qint64 maxLength) { return (m_buf ? m_buf->readLine(data, maxLength) : Q_INT64_C(-1)); }
qint64 readLine(char *data, qint64 maxLength,
ReadLineOptions option = ReadLineOption::NullTerminated)
{
const auto appendNullByte = option & ReadLineOption::NullTerminated;
return !m_buf ? Q_INT64_C(-1) :
appendNullByte ? m_buf->readLine(data, maxLength) :
m_buf->readLineWithoutTerminatingNull(data, maxLength);
}
inline bool canReadLine() const { return m_buf && m_buf->canReadLine(); }
};
@ -145,7 +158,9 @@ public:
void setWriteChannelCount(int count);
qint64 read(char *data, qint64 maxSize, bool peeking = false);
qint64 readLine(char *data, qint64 maxSize);
qint64 readLine(char *data, qint64 maxSize,
ReadLineOption option = ReadLineOption::NullTerminated);
virtual qint64 peek(char *data, qint64 maxSize);
virtual QByteArray peek(qint64 maxSize);
qint64 skipByReading(qint64 maxSize);
@ -164,6 +179,8 @@ public:
#endif
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QIODevicePrivate::ReadLineOptions)
QT_END_NAMESPACE
#endif // QIODEVICE_P_H

View File

@ -340,14 +340,22 @@ void QRingBuffer::append(QByteArray &&qba)
bufferSize += qbaSize;
}
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
qint64 QRingBuffer::readLineWithoutTerminatingNull(char *data, qint64 maxLength)
{
Q_ASSERT(data != nullptr && maxLength > 1);
Q_ASSERT(data != nullptr && maxLength > 0);
--maxLength;
qint64 i = indexOf('\n', maxLength);
i = read(data, i >= 0 ? (i + 1) : maxLength);
return i;
}
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
{
Q_ASSERT(maxLength > 1);
qint64 i = readLineWithoutTerminatingNull(data, maxLength - 1);
// Terminate it.
data[i] = '\0';
return i;

View File

@ -214,6 +214,7 @@ public:
return bytesToSkip;
}
Q_CORE_EXPORT qint64 readLineWithoutTerminatingNull(char *data, qint64 maxLength);
Q_CORE_EXPORT qint64 readLine(char *data, qint64 maxLength);
inline bool canReadLine() const {

View File

@ -32,6 +32,7 @@ private slots:
void readLineInto_Checks();
void readLineInto();
void readLineInto_qspan();
void readAllKeepPosition();
void writeInTextMode();
@ -628,6 +629,75 @@ void tst_QIODevice::readLineInto()
QCOMPARE(buffer.pos(), pos_before);
}
void tst_QIODevice::readLineInto_qspan()
{
QByteArray data ("1st Line\r\nL2\r\nRead the rest");
QBuffer buffer(&data);
{
QVERIFY(buffer.open(QIODevice::ReadOnly));
QVERIFY(buffer.canReadLine());
buffer.seek(0);
QSpan<char> span; // zero-sized span
QTest::ignoreMessage(QtWarningMsg,
"QIODevice::readLineInto (QBuffer): Called with maxSize < 1");
QCOMPARE(buffer.readLineInto(span), "");
char buffer1[1024];
QCOMPARE(buffer.readLineInto(buffer1), "1st Line\r\n");
uchar buffer2[4]; // length of the buffer is equal to the size of the line
QCOMPARE(buffer.readLineInto(buffer2), "L2\r\n");
std::byte buffer3[5]; // length of the buffer is less than the size of the line
QCOMPARE(buffer.readLineInto(buffer3), "Read ");
QCOMPARE(buffer.readLineInto(buffer1), "the rest"); // read the rest
QCOMPARE(buffer.readLineInto(span), ""); // No warning even thought maxSize < 1 because we
// are at the end
}
{
QVERIFY(buffer.open(QIODevice::ReadOnly | QIODevice::Text)); // "\r\n" is translated to '\n'
QVERIFY(buffer.canReadLine());
buffer.seek(0);
QSpan<char> span; // zero-sized span
QTest::ignoreMessage(QtWarningMsg,
"QIODevice::readLineInto (QBuffer): Called with maxSize < 1");
QCOMPARE(buffer.readLineInto(span), "");
char buffer1[1024];
QCOMPARE(buffer.readLineInto(buffer1), "1st Line\n");
uchar buffer2[3]; // length of the buffer is equal to the size of the line
QCOMPARE(buffer.readLineInto(buffer2), "L2\n");
std::byte buffer3[5]; // length of the buffer is less than the size of the line
QCOMPARE(buffer.readLineInto(buffer3), "Read ");
QCOMPARE(buffer.readLineInto(buffer1), "the rest"); // read the rest
QCOMPARE(buffer.readLineInto(span), ""); // No warning even thought maxSize < 1 because we
// are at the end
}
{
// This test checks the behavior when !keepDataInBuffer and !buffer.isEmpty().
// 'buffer' was always empty in the previous tests and calling ungetChar() changes that.
QByteArray data2 ("Q");
QBuffer buffer2(&data2);
QVERIFY(buffer2.open(QIODevice::ReadOnly));
buffer2.seek(0);
buffer2.read(1);
buffer2.ungetChar('t'); // Make the buffer size equal to 1
char buf[1];
QCOMPARE(buffer2.readLineInto(buf), "t");
QCOMPARE(buffer2.readLineInto(buf), ""); // no more data to read
}
}
class SequentialReadBuffer : public QIODevice
{
public: