From f0d1f50e0294e5a55a0e450993e0810bd4dbf63d Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Mon, 14 Jun 2021 13:09:58 +0200 Subject: [PATCH] QRegularExpression: fix matching over null/empty QString(View) An empty QString(View) is allowed to have nullptr as its data pointer (of course, only if its size is 0). This wasn't properly checked in QRegularExpression, which passed such nullptr to PCRE, and that resulted in PCRE raising an error (PCRE_ERROR_NULL). Detect this case and pass a dummy pointer to keep PCRE happy. Fixing and testing this in turn exposed a problem with QStringView support in QRegularExpression when used over a null QString: the code is supposed to use the QStringView(QString) constructor and NOT qToStringViewIgnoringNull. That's because QRE distinguishes null and empty subjects; when using qToStringViewIgnoringNull over a null QString, one gets a non-null QStringView (!). Again, this in turn exposed a problem with a QRegularExpression autotest that assumed that a null match could only mean "no match" (instead, it can happen at position 0 of a null QString(View)). Change-Id: Ifb3cf14dec42ce76fcdbcb07ea1d80784d52ef65 Pick-to: 6.1 6.2 Reviewed-by: Thiago Macieira Reviewed-by: Edward Welbourne --- src/corelib/text/qregularexpression.cpp | 16 ++++++++++-- .../tst_qregularexpression.cpp | 25 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/corelib/text/qregularexpression.cpp b/src/corelib/text/qregularexpression.cpp index a311c0878fc..a4bb19d0b29 100644 --- a/src/corelib/text/qregularexpression.cpp +++ b/src/corelib/text/qregularexpression.cpp @@ -1174,7 +1174,19 @@ void QRegularExpressionPrivate::doMatch(QRegularExpressionMatchPrivate *priv, pcre2_jit_stack_assign_16(matchContext, &qtPcreCallback, nullptr); pcre2_match_data_16 *matchData = pcre2_match_data_create_from_pattern_16(compiledPattern, nullptr); - const char16_t * const subjectUtf16 = priv->subject.utf16(); + // PCRE does not accept a null pointer as subject string, even if + // its length is zero. We however allow it in input: a QStringView + // subject may have data == nullptr. In this case, to keep PCRE + // happy, pass a pointer to a dummy character. + constexpr char16_t dummySubject = 0; + const char16_t * const subjectUtf16 = [&]() + { + const auto subjectUtf16 = priv->subject.utf16(); + if (subjectUtf16) + return subjectUtf16; + Q_ASSERT(subjectLength == 0); + return &dummySubject; + }(); int result; @@ -1610,7 +1622,7 @@ QRegularExpressionMatch QRegularExpression::match(const QString &subject, d.data()->compilePattern(); auto priv = new QRegularExpressionMatchPrivate(*this, subject, - qToStringViewIgnoringNull(subject), + QStringView(subject), matchType, matchOptions); d->doMatch(priv, offset); diff --git a/tests/auto/corelib/text/qregularexpression/tst_qregularexpression.cpp b/tests/auto/corelib/text/qregularexpression/tst_qregularexpression.cpp index f2fe382521f..48d4de0aa4d 100644 --- a/tests/auto/corelib/text/qregularexpression/tst_qregularexpression.cpp +++ b/tests/auto/corelib/text/qregularexpression/tst_qregularexpression.cpp @@ -252,8 +252,11 @@ void consistencyCheck(const QRegularExpressionMatch &match) QVERIFY((endPos - startPos) == length); QVERIFY(captured == capturedView); } else { - QVERIFY(startPos == -1); - QVERIFY(endPos == -1); + // A null capture can either mean no capture at all, + // or capture of length 0 over a null subject. + QVERIFY(startPos == endPos); + QVERIFY(((startPos == -1) && (endPos == -1)) // no capture + || ((startPos == 0) && (endPos == 0))); // null subject QVERIFY((endPos - startPos) == length); QVERIFY(capturedView.isNull()); } @@ -860,6 +863,24 @@ void tst_QRegularExpression::normalMatch_data() << QRegularExpression::MatchOptions(QRegularExpression::NoMatchOption) << m; + m.clear(); + m.isValid = true; m.hasMatch = true; + m.captured << QString(); + QTest::newRow("empty-in-null-string") << QRegularExpression("") + << QString() + << qsizetype(0) + << QRegularExpression::MatchOptions(QRegularExpression::NoMatchOption) + << m; + + m.clear(); + m.isValid = true; m.hasMatch = true; + m.captured << QString(""); + QTest::newRow("empty-in-empty-string") << QRegularExpression("") + << QString("") + << qsizetype(0) + << QRegularExpression::MatchOptions(QRegularExpression::NoMatchOption) + << m; + // non existing names for capturing groups m.clear(); m.isValid = true; m.hasMatch = true;