QUuid: add fromString(QStringView/QLatin1String)

As for the formatting code, de-duplicate the parsing code by only
parsing char*s, converting QChars to Latin-1 first in a small buffer.

The QUuid(const char*) ctor performed no length checking, relying
instead on the checks performed within _q_uuidFromHex(), which
includes an implicit check for premature end (because NUL is not
a valid token for the parser).

The (QString) and (QByteArray) ctors did perform length checking.
To the extent possible, this is removed, since it is handled by
_q_uuidFromHex(). Failure cases need not be optimized. Only the
QLatin1String overload needs to do some checking, because views in
general are not NUL-terminated. The QStringView overload can just
append a NUL when it converts to Latin-1.

The only check I added to _q_uuidFromHex() is that for src ==
nullptr. It would otherwise be duplicated in several callers.

While touching the internal functions, port to passing and returning
by value.

Saves 1.6KiB in text size on optimized GCC 6.1 Linux AMD64 builds,
even though we added new API.

Port some users to the new functions. Expand fromString() test.

[ChangeLog][QtCore][QUuid] Added fromString(QStringView/QLatin1String).

Change-Id: I519339419129550c86e0ea80514865cd6a768f5d
Reviewed-by: Anton Kudryavtsev <antkudr@mail.ru>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
bb10
Marc Mutz 2017-04-10 10:17:34 +02:00
parent af211b3993
commit 7d4bf142d7
6 changed files with 154 additions and 76 deletions

View File

@ -223,7 +223,7 @@ QUuid VcprojGenerator::getProjectUUID(const QString &filename)
bool validUUID = true;
// Read GUID from variable-space
QUuid uuid = project->first("GUID").toQString();
auto uuid = QUuid::fromString(project->first("GUID").toQStringView());
// If none, create one based on the MD5 of absolute project path
if(uuid.isNull() || !filename.isEmpty()) {

View File

@ -67,8 +67,8 @@ void _q_toHex(char *&dst, Integral value)
}
}
template <class Char, class Integral>
bool _q_fromHex(const Char *&src, Integral &value)
template <class Integral>
bool _q_fromHex(const char *&src, Integral &value)
{
value = 0;
@ -102,30 +102,46 @@ static char *_q_uuidToHex(const QUuid &uuid, char *dst)
return dst;
}
template <class Char>
bool _q_uuidFromHex(const Char *&src, uint &d1, ushort &d2, ushort &d3, uchar (&d4)[8])
/*!
\internal
Parses the string representation of a UUID (with optional surrounding "{}")
by reading at most MaxStringUuidLength (38) characters from \a src, which
may be \c nullptr. Stops at the first invalid character (which includes a
premature NUL).
Returns the successfully parsed QUuid, or a null QUuid in case of failure.
*/
Q_NEVER_INLINE
static QUuid _q_uuidFromHex(const char *src)
{
if (*src == Char('{'))
src++;
if (!_q_fromHex(src, d1)
|| *src++ != Char('-')
|| !_q_fromHex(src, d2)
|| *src++ != Char('-')
|| !_q_fromHex(src, d3)
|| *src++ != Char('-')
|| !_q_fromHex(src, d4[0])
|| !_q_fromHex(src, d4[1])
|| *src++ != Char('-')
|| !_q_fromHex(src, d4[2])
|| !_q_fromHex(src, d4[3])
|| !_q_fromHex(src, d4[4])
|| !_q_fromHex(src, d4[5])
|| !_q_fromHex(src, d4[6])
|| !_q_fromHex(src, d4[7])) {
return false;
uint d1;
ushort d2, d3;
uchar d4[8];
if (src) {
if (*src == '{')
src++;
if (Q_LIKELY( _q_fromHex(src, d1)
&& *src++ == '-'
&& _q_fromHex(src, d2)
&& *src++ == '-'
&& _q_fromHex(src, d3)
&& *src++ == '-'
&& _q_fromHex(src, d4[0])
&& _q_fromHex(src, d4[1])
&& *src++ == '-'
&& _q_fromHex(src, d4[2])
&& _q_fromHex(src, d4[3])
&& _q_fromHex(src, d4[4])
&& _q_fromHex(src, d4[5])
&& _q_fromHex(src, d4[6])
&& _q_fromHex(src, d4[7]))) {
return QUuid(d1, d2, d3, d4[0], d4[1], d4[2], d4[3], d4[4], d4[5], d4[6], d4[7]);
}
}
return true;
return QUuid();
}
#ifndef QT_BOOTSTRAPPED
@ -336,7 +352,7 @@ static QUuid createFromName(const QUuid &ns, const QByteArray &baseData, QCrypto
/*!
Creates a QUuid object from the string \a text, which must be
formatted as five hex fields separated by '-', e.g.,
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where 'x' is a hex
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
digit. The curly braces shown here are optional, but it is normal to
include them. If the conversion fails, a null UUID is created. See
toString() for an explanation of how the five hex fields map to the
@ -345,45 +361,76 @@ static QUuid createFromName(const QUuid &ns, const QByteArray &baseData, QCrypto
\sa toString(), QUuid()
*/
QUuid::QUuid(const QString &text)
: QUuid(fromString(text))
{
if (text.length() < 36) {
*this = QUuid();
return;
}
}
const ushort *data = reinterpret_cast<const ushort *>(text.unicode());
/*!
\since 5.10
if (*data == '{' && text.length() < 37) {
*this = QUuid();
return;
}
Creates a QUuid object from the string \a text, which must be
formatted as five hex fields separated by '-', e.g.,
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
digit. The curly braces shown here are optional, but it is normal to
include them. If the conversion fails, a null UUID is returned. See
toString() for an explanation of how the five hex fields map to the
public data members in QUuid.
if (!_q_uuidFromHex(data, data1, data2, data3, data4)) {
*this = QUuid();
return;
\sa toString(), QUuid()
*/
QUuid QUuid::fromString(QStringView text) Q_DECL_NOTHROW
{
if (text.size() > MaxStringUuidLength)
text = text.left(MaxStringUuidLength); // text.truncate(MaxStringUuidLength);
char latin1[MaxStringUuidLength + 1];
char *dst = latin1;
for (QChar ch : text)
*dst++ = ch.toLatin1();
*dst++ = '\0'; // don't read garbage as potentially valid data
return _q_uuidFromHex(latin1);
}
/*!
\since 5.10
\overload
Creates a QUuid object from the string \a text, which must be
formatted as five hex fields separated by '-', e.g.,
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
digit. The curly braces shown here are optional, but it is normal to
include them. If the conversion fails, a null UUID is returned. See
toString() for an explanation of how the five hex fields map to the
public data members in QUuid.
\sa toString(), QUuid()
*/
QUuid QUuid::fromString(QLatin1String text) Q_DECL_NOTHROW
{
if (Q_UNLIKELY(text.size() < MaxStringUuidLength - 2
|| (text.front() == QLatin1Char('{') && text.size() < MaxStringUuidLength - 1))) {
// Too short. Don't call _q_uuidFromHex(); QL1Ss need not be NUL-terminated,
// and we don't want to read trailing garbage as potentially valid data.
text = QLatin1String();
}
return _q_uuidFromHex(text.data());
}
/*!
\internal
*/
QUuid::QUuid(const char *text)
: QUuid(_q_uuidFromHex(text))
{
if (!text) {
*this = QUuid();
return;
}
if (!_q_uuidFromHex(text, data1, data2, data3, data4)) {
*this = QUuid();
return;
}
}
/*!
Creates a QUuid object from the QByteArray \a text, which must be
formatted as five hex fields separated by '-', e.g.,
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where 'x' is a hex
"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
digit. The curly braces shown here are optional, but it is normal to
include them. If the conversion fails, a null UUID is created. See
toByteArray() for an explanation of how the five hex fields map to the
@ -394,23 +441,8 @@ QUuid::QUuid(const char *text)
\sa toByteArray(), QUuid()
*/
QUuid::QUuid(const QByteArray &text)
: QUuid(fromString(QLatin1String(text.data(), text.size())))
{
if (text.length() < 36) {
*this = QUuid();
return;
}
const char *data = text.constData();
if (*data == '{' && text.length() < 37) {
*this = QUuid();
return;
}
if (!_q_uuidFromHex(data, data1, data2, data3, data4)) {
*this = QUuid();
return;
}
}
/*!

View File

@ -116,6 +116,8 @@ public:
#endif
QUuid(const QString &);
static QUuid fromString(QStringView string) Q_DECL_NOTHROW;
static QUuid fromString(QLatin1String string) Q_DECL_NOTHROW;
QUuid(const char *);
QString toString() const;
QUuid(const QByteArray &);

View File

@ -909,7 +909,7 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url)
// (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx)
// specified as "clsid:<GUID>" (without '{', '}').
IShellItem *result = Q_NULLPTR;
const QUuid uuid(url.path());
const auto uuid = QUuid::fromString(url.path());
if (uuid.isNull()) {
qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path();
return Q_NULLPTR;

View File

@ -41,6 +41,7 @@ private slots:
void fromChar();
void toString();
void fromString_data();
void fromString();
void toByteArray();
void fromByteArray();
@ -127,15 +128,58 @@ void tst_QUuid::toString()
QCOMPARE(uuidB.toString(), QString("{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}"));
}
void tst_QUuid::fromString_data()
{
QTest::addColumn<QUuid>("expected");
QTest::addColumn<QString>("input");
QUuid invalid = {};
#define ROW(which, string) \
QTest::addRow("%-38s -> %s", string, #which) << which << string
ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5}");
ROW(uuidA, "fc69b59e-cc34-4436-a43c-ee95d128b8c5}");
ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5" );
ROW(uuidA, "fc69b59e-cc34-4436-a43c-ee95d128b8c5" );
ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c56"); // too long (not an error!)
ROW(invalid, "{fc69b59e-cc34-4436-a43c-ee95d128b8c" ); // premature end (within length limits)
ROW(invalid, " fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // leading space
ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5 "); // trailing space (not an error!)
ROW(invalid, "{gc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // non-hex digit in 1st group
ROW(invalid, "{fc69b59e-cp34-4436-a43c-ee95d128b8c5}"); // non-hex digit in 2nd group
ROW(invalid, "{fc69b59e-cc34-44r6-a43c-ee95d128b8c5}"); // non-hex digit in 3rd group
ROW(invalid, "{fc69b59e-cc34-4436-a4yc-ee95d128b8c5}"); // non-hex digit in 4th group
ROW(invalid, "{fc69b59e-cc34-4436-a43c-ee95d128j8c5}"); // non-hex digit in last group
ROW(invalid, "(fc69b59e-cc34-4436-a43c-ee95d128b8c5}"); // wrong initial character
ROW(invalid, "{fc69b59e+cc34-4436-a43c-ee95d128b8c5}"); // wrong 1st separator
ROW(invalid, "{fc69b59e-cc34*4436-a43c-ee95d128b8c5}"); // wrong 2nd separator
ROW(invalid, "{fc69b59e-cc34-44366a43c-ee95d128b8c5}"); // wrong 3rd separator
ROW(invalid, "{fc69b59e-cc34-4436-a43c\303\244ee95d128b8c5}"); // wrong 4th separator (&auml;)
ROW(uuidA, "{fc69b59e-cc34-4436-a43c-ee95d128b8c5)"); // wrong final character (not an error!)
ROW(uuidB, "{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}");
#undef ROW
}
void tst_QUuid::fromString()
{
QCOMPARE(uuidA, QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c5}")));
QCOMPARE(uuidA, QUuid(QString("fc69b59e-cc34-4436-a43c-ee95d128b8c5}")));
QCOMPARE(uuidA, QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c5")));
QCOMPARE(uuidA, QUuid(QString("fc69b59e-cc34-4436-a43c-ee95d128b8c5")));
QCOMPARE(QUuid(), QUuid(QString("{fc69b59e-cc34-4436-a43c-ee95d128b8c")));
QFETCH(const QUuid, expected);
QFETCH(const QString, input);
QCOMPARE(uuidB, QUuid(QString("{1ab6e93a-b1cb-4a87-ba47-ec7e99039a7b}")));
const auto inputL1 = input.toLatin1();
const auto inputU8 = input.toUtf8();
QCOMPARE(expected, QUuid(input));
QCOMPARE(expected, QUuid(inputU8));
QCOMPARE(expected, QUuid(inputL1));
QCOMPARE(expected, QUuid::fromString(input));
// for QLatin1String, construct one whose data() is not NUL-terminated:
const auto longerInputL1 = inputL1 + '5'; // the '5' makes the premature end check incorrectly succeed
const auto inputL1S = QLatin1String(longerInputL1.data(), inputL1.size());
QCOMPARE(expected, QUuid::fromString(inputL1S));
}
void tst_QUuid::toByteArray()

View File

@ -36,7 +36,7 @@ void tst_QUuid_darwinTypes()
{
// QUuid <-> CFUUID
{
QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const CFUUIDRef cfuuid = qtUuid.toCFUUID();
QCOMPARE(QUuid::fromCFUUID(cfuuid), qtUuid);
CFStringRef cfstring = CFUUIDCreateString(0, cfuuid);
@ -45,10 +45,10 @@ void tst_QUuid_darwinTypes()
CFRelease(cfuuid);
}
{
QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const CFUUIDRef cfuuid = qtUuid.toCFUUID();
QUuid qtUuidCopy(qtUuid);
qtUuid = QUuid(QByteArrayLiteral("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify
qtUuid = QUuid::fromString(QLatin1String("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify
QCOMPARE(QUuid::fromCFUUID(cfuuid), qtUuidCopy);
CFStringRef cfstring = CFUUIDCreateString(0, cfuuid);
QCOMPARE(QString::fromCFString(cfstring), qtUuidCopy.toString().mid(1, 36).toUpper());
@ -59,7 +59,7 @@ void tst_QUuid_darwinTypes()
{
QMacAutoReleasePool pool;
QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const NSUUID *nsuuid = qtUuid.toNSUUID();
QCOMPARE(QUuid::fromNSUUID(nsuuid), qtUuid);
QCOMPARE(QString::fromNSString([nsuuid UUIDString]), qtUuid.toString().mid(1, 36).toUpper());
@ -67,10 +67,10 @@ void tst_QUuid_darwinTypes()
{
QMacAutoReleasePool pool;
QUuid qtUuid(QByteArrayLiteral("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
auto qtUuid = QUuid::fromString(QLatin1String("0f7169cc-5711-4af9-99d9-fecb2329fdef"));
const NSUUID *nsuuid = qtUuid.toNSUUID();
QUuid qtUuidCopy(qtUuid);
qtUuid = QUuid(QByteArrayLiteral("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify
qtUuid = QUuid::fromString(QLatin1String("93eec131-13c5-4d13-aaea-e456b4c57efa")); // modify
QCOMPARE(QUuid::fromNSUUID(nsuuid), qtUuidCopy);
QCOMPARE(QString::fromNSString([nsuuid UUIDString]), qtUuidCopy.toString().mid(1, 36).toUpper());
}