diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp index fc31eca5de7..2d5246352af 100644 --- a/src/corelib/io/qsettings.cpp +++ b/src/corelib/io/qsettings.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -53,7 +53,7 @@ #include "qtemporaryfile.h" #include "qstandardpaths.h" #include -#include +#include "private/qstringconverter_p.h" #ifndef QT_NO_GEOM_VARIANT #include "qsize.h" @@ -230,13 +230,32 @@ QSettingsPrivate::~QSettingsPrivate() { } -QString QSettingsPrivate::actualKey(const QString &key) const +QString QSettingsPrivate::actualKey(QAnyStringView key) const { auto n = normalizedKey(key); Q_ASSERT_X(!n.isEmpty(), "QSettings", "empty key"); return groupPrefix + n; } +namespace { + // ### this needs some public API (QStringConverter?) + QChar *write(QChar *out, QUtf8StringView v) + { + return QUtf8::convertToUnicode(out, QByteArrayView(v)); + } + QChar *write(QChar *out, QLatin1String v) + { + for (char ch : v) + *out++ = QLatin1Char(ch); + return out; + } + QChar *write(QChar *out, QStringView v) + { + memcpy(out, v.data(), v.size() * sizeof(QChar)); + return out + v.size(); + } +} + /* Returns a string that never starts nor ends with a slash (or an empty string). Examples: @@ -244,32 +263,43 @@ QString QSettingsPrivate::actualKey(const QString &key) const "foo" becomes "foo" "/foo//bar///" becomes "foo/bar" "///" becomes "" - - This function is optimized to avoid a QString deep copy in the - common case where the key is already normalized. */ -QString QSettingsPrivate::normalizedKey(const QString &key) +QString QSettingsPrivate::normalizedKey(QAnyStringView key) { - QString result = key; + QString result(key.size(), Qt::Uninitialized); + auto out = const_cast(result.constData()); // don't detach - int i = 0; - while (i < result.size()) { - while (result.at(i) == QLatin1Char('/')) { - result.remove(i, 1); - if (i == result.size()) - goto after_loop; - } - while (result.at(i) != QLatin1Char('/')) { - ++i; - if (i == result.size()) - return result; - } - ++i; // leave the slash alone - } + const bool maybeEndsInSlash = key.visit([&out](auto key) { + using View = decltype(key); -after_loop: - if (!result.isEmpty()) - result.truncate(i - 1); // remove the trailing slash + auto it = key.begin(); + const auto end = key.end(); + + while (it != end) { + while (*it == u'/') { + ++it; + if (it == end) + return true; + } + auto mark = it; + while (*it != u'/') { + ++it; + if (it == end) + break; + } + out = write(out, View{mark, it}); + if (it == end) + return false; + Q_ASSERT(*it == u'/'); + *out++ = u'/'; + ++it; + } + return true; + }); + + if (maybeEndsInSlash && out != result.constData()) + --out; // remove the trailing slash + result.truncate(out - result.constData()); return result; } @@ -3290,7 +3320,7 @@ QVariant QSettings::value(const QString &key, const QVariant &defaultValue) cons return d->value(key, &defaultValue); } -QVariant QSettingsPrivate::value(const QString &key, const QVariant *defaultValue) const +QVariant QSettingsPrivate::value(QAnyStringView key, const QVariant *defaultValue) const { if (key.isEmpty()) { qWarning("QSettings::value: Empty key passed"); diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h index 1573a39d2a0..ac824291468 100644 --- a/src/corelib/io/qsettings_p.h +++ b/src/corelib/io/qsettings_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -209,14 +209,14 @@ public: virtual bool isWritable() const = 0; virtual QString fileName() const = 0; - QVariant value(const QString &key, const QVariant *defaultValue) const; - QString actualKey(const QString &key) const; + QVariant value(QAnyStringView key, const QVariant *defaultValue) const; + QString actualKey(QAnyStringView key) const; void beginGroupOrArray(const QSettingsGroup &group); void setStatus(QSettings::Status status) const; void requestUpdate(); void update(); - static QString normalizedKey(const QString &key); + static QString normalizedKey(QAnyStringView key); static QSettingsPrivate *create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application); static QSettingsPrivate *create(const QString &fileName, QSettings::Format format); diff --git a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp index c4ede66381c..e8bef28c02d 100644 --- a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp +++ b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -2160,20 +2160,17 @@ void tst_QSettings::testNormalizedKey() inKey.detach(); - QString result = QSettingsPrivate::normalizedKey(inKey); - QCOMPARE(result, outKey); - - /* - If the key is already normalized, we verify that outKey is - just a shallow copy of the input string. This is an important - optimization that shouldn't be removed accidentally. - */ - if (inKey == outKey) { - QVERIFY(!result.isDetached()); - } else { - if (!result.isEmpty()) { - QVERIFY(result.isDetached()); - } + { + auto result = QSettingsPrivate::normalizedKey(inKey); + QCOMPARE(result, outKey); + } + { + auto result = QSettingsPrivate::normalizedKey(QUtf8StringView{inKey.toUtf8()}); + QCOMPARE(result, outKey); + } + { + auto result = QSettingsPrivate::normalizedKey(QLatin1String{inKey.toLatin1()}); + QCOMPARE(result, outKey); } } #endif