summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/text/qstring.cpp84
-rw-r--r--tests/auto/corelib/text/qstring/tst_qstring.cpp29
2 files changed, 109 insertions, 4 deletions
diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp
index 291e2c37ab..ca2ebe2b9e 100644
--- a/src/corelib/text/qstring.cpp
+++ b/src/corelib/text/qstring.cpp
@@ -1485,6 +1485,40 @@ inline char qToLower(char ch)
return ch;
}
+// ### Qt 7: do not allow anything but ASCII digits
+// in arg()'s replacements.
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+static bool supportUnicodeDigitValuesInArg()
+{
+ static const bool result = []() {
+ static const char supportUnicodeDigitValuesEnvVar[]
+ = "QT_USE_UNICODE_DIGIT_VALUES_IN_STRING_ARG";
+
+ if (qEnvironmentVariableIsSet(supportUnicodeDigitValuesEnvVar))
+ return qEnvironmentVariableIntValue(supportUnicodeDigitValuesEnvVar) != 0;
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) // keep it in sync with the test
+ return true;
+#else
+ return false;
+#endif
+ }();
+
+ return result;
+}
+#endif
+
+static int qArgDigitValue(QChar ch) noexcept
+{
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ if (supportUnicodeDigitValuesInArg())
+ return ch.digitValue();
+#endif
+ if (ch >= u'0' && ch <= u'9')
+ return int(ch.unicode() - u'0');
+ return -1;
+}
+
/*!
\macro QT_RESTRICTED_CAST_FROM_ASCII
@@ -7725,6 +7759,34 @@ QString QString::normalized(QString::NormalizationForm mode, QChar::UnicodeVersi
return copy;
}
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+static void checkArgEscape(QStringView s)
+{
+ // If we're in here, it means that qArgDigitValue has accepted the
+ // digit. We can skip the check in case we already know it will
+ // succeed.
+ if (!supportUnicodeDigitValuesInArg())
+ return;
+
+ const auto isNonAsciiDigit = [](QChar c) {
+ return c.unicode() < u'0' || c.unicode() > u'9';
+ };
+
+ if (std::any_of(s.begin(), s.end(), isNonAsciiDigit)) {
+ const auto accumulateDigit = [](int partial, QChar digit) {
+ return partial * 10 + digit.digitValue();
+ };
+ const int parsedNumber = std::accumulate(s.begin(), s.end(), 0, accumulateDigit);
+
+ qWarning("QString::arg(): the replacement \"%%%ls\" contains non-ASCII digits;\n"
+ " it is currently being interpreted as the %d-th substitution.\n"
+ " This is deprecated; support for non-ASCII digits will be dropped\n"
+ " in a future version of Qt.",
+ qUtf16Printable(s.toString()),
+ parsedNumber);
+ }
+}
+#endif
struct ArgEscapeData
{
@@ -7765,20 +7827,34 @@ static ArgEscapeData findArgEscapes(QStringView s)
break;
}
- int escape = c->digitValue();
+ int escape = qArgDigitValue(*c);
if (escape == -1)
continue;
+ // ### Qt 7: do not allow anything but ASCII digits
+ // in arg()'s replacements.
+#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0)
+ const QChar *escapeBegin = c;
+ const QChar *escapeEnd = escapeBegin + 1;
+#endif
+
++c;
if (c != uc_end) {
- int next_escape = c->digitValue();
+ const int next_escape = qArgDigitValue(*c);
if (next_escape != -1) {
escape = (10 * escape) + next_escape;
++c;
+#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0)
+ ++escapeEnd;
+#endif
}
}
+#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0)
+ checkArgEscape(QStringView(escapeBegin, escapeEnd));
+#endif
+
if (escape > d.min_escape)
continue;
@@ -7829,9 +7905,9 @@ static QString replaceArgEscapes(QStringView s, const ArgEscapeData &d, qsizetyp
if (localize)
++c;
- int escape = c->digitValue();
+ int escape = qArgDigitValue(*c);
if (escape != -1 && c + 1 != uc_end) {
- int digit = c[1].digitValue();
+ const int digit = qArgDigitValue(c[1]);
if (digit != -1) {
++c;
escape = 10 * escape + digit;
diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp
index f6e7563774..07122f6235 100644
--- a/tests/auto/corelib/text/qstring/tst_qstring.cpp
+++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp
@@ -5153,6 +5153,35 @@ void tst_QString::arg()
.arg( firstName ).arg( lastName );
QCOMPARE( fullName, QLatin1String("My name is Bond, James Bond") );
+ // ### Qt 7: clean this up, leave just the #else branch
+#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
+ static const QRegularExpression nonAsciiArgWarning("QString::arg\\(\\): the replacement \".*\" contains non-ASCII digits");
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%¹").arg("foo"), QString("foo") );
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%¹%1").arg("foo"), QString("foofoo") );
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%1²").arg("E=mc"), QString("E=mc") );
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%1²%2").arg("a").arg("b"), QString("ba") );
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%¹%1²%2").arg("a").arg("b"), QString("a%1²b") );
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QTest::ignoreMessage(QtWarningMsg, nonAsciiArgWarning);
+ QCOMPARE( QString("%2²%1").arg("a").arg("b"), QString("ba") );
+#else
+ QTest::ignoreMessage(QtWarningMsg, "QString::arg: Argument missing: %¹, foo");
+ QCOMPARE( QString("%¹").arg("foo"), QString("%¹") );
+ QCOMPARE( QString("%¹%1").arg("foo"), QString("%¹foo") );
+ QCOMPARE( QString("%1²").arg("E=mc"), QString("E=mc²") );
+ QCOMPARE( QString("%1²%2").arg("a").arg("b"), QString("a²b") );
+ QCOMPARE( QString("%¹%1²%2").arg("a").arg("b"), QString("%¹a²b") );
+ QCOMPARE( QString("%2²%1").arg("a").arg("b"), QString("b²a") );
+#endif
+
// number overloads
QCOMPARE( s4.arg(0), QLatin1String("[0]") );
QCOMPARE( s4.arg(-1), QLatin1String("[-1]") );