qEnvironmentVariableIntValue: fix the case of a non-numeric value

The documentation says that it's equivalent to
    qgetenv(varName).toInt()

But the implementation wasn't. QByteArray::toInt() verifies that the
entire string was consumed, so QByteArray("1a").toInt() == 0, but
qstrtoll alone doesn't. That is, qstrtoll("1a", ...) == 1.

The implementation also detected the base, a behavior I kept. Instead, I
updated the documentation.

Change-Id: I0031aa609e714ae983c3fffd14676ea6061a9268
Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
This commit is contained in:
Thiago Macieira 2016-08-03 16:04:22 -07:00 committed by Liang Qi
parent e3bc01b0e3
commit c214c000cc
2 changed files with 39 additions and 8 deletions

View File

@ -3259,20 +3259,26 @@ bool qEnvironmentVariableIsEmpty(const char *varName) Q_DECL_NOEXCEPT
Equivalent to Equivalent to
\code \code
qgetenv(varName).toInt() qgetenv(varName).toInt(ok, 0)
\endcode \endcode
except that it's much faster, and can't throw exceptions. except that it's much faster, and can't throw exceptions.
\note there's a limit on the length of the value, which is sufficient for
all valid values of int, not counting leading zeroes or spaces. Values that
are too long will either be truncated or this function will set \a ok to \c
false.
\sa qgetenv(), qEnvironmentVariableIsSet() \sa qgetenv(), qEnvironmentVariableIsSet()
*/ */
int qEnvironmentVariableIntValue(const char *varName, bool *ok) Q_DECL_NOEXCEPT int qEnvironmentVariableIntValue(const char *varName, bool *ok) Q_DECL_NOEXCEPT
{ {
QMutexLocker locker(&environmentMutex);
#if defined(_MSC_VER) && _MSC_VER >= 1400
// we provide a buffer that can hold any int value:
static const int NumBinaryDigitsPerOctalDigit = 3; static const int NumBinaryDigitsPerOctalDigit = 3;
static const int MaxDigitsForOctalInt = static const int MaxDigitsForOctalInt =
(std::numeric_limits<uint>::digits + NumBinaryDigitsPerOctalDigit - 1) / NumBinaryDigitsPerOctalDigit; (std::numeric_limits<uint>::digits + NumBinaryDigitsPerOctalDigit - 1) / NumBinaryDigitsPerOctalDigit;
QMutexLocker locker(&environmentMutex);
#if defined(_MSC_VER) && _MSC_VER >= 1400
// we provide a buffer that can hold any int value:
char buffer[MaxDigitsForOctalInt + 2]; // +1 for NUL +1 for optional '-' char buffer[MaxDigitsForOctalInt + 2]; // +1 for NUL +1 for optional '-'
size_t dummy; size_t dummy;
if (getenv_s(&dummy, buffer, sizeof buffer, varName) != 0) { if (getenv_s(&dummy, buffer, sizeof buffer, varName) != 0) {
@ -3282,15 +3288,16 @@ int qEnvironmentVariableIntValue(const char *varName, bool *ok) Q_DECL_NOEXCEPT
} }
#else #else
const char * const buffer = ::getenv(varName); const char * const buffer = ::getenv(varName);
if (!buffer || !*buffer) { if (!buffer || strlen(buffer) > MaxDigitsForOctalInt + 2) {
if (ok) if (ok)
*ok = false; *ok = false;
return 0; return 0;
} }
#endif #endif
bool ok_ = true; bool ok_ = true;
const qlonglong value = qstrtoll(buffer, Q_NULLPTR, 0, &ok_); const char *endptr;
if (int(value) != value) { // this is the check in QByteArray::toInt(), keep it in sync const qlonglong value = qstrtoll(buffer, &endptr, 0, &ok_);
if (int(value) != value || *endptr != '\0') { // this is the check in QByteArray::toInt(), keep it in sync
if (ok) if (ok)
*ok = false; *ok = false;
return 0; return 0;

View File

@ -97,17 +97,33 @@ void tst_QGetPutEnv::intValue_data()
QTest::addColumn<int>("expected"); QTest::addColumn<int>("expected");
QTest::addColumn<bool>("ok"); QTest::addColumn<bool>("ok");
// most non-success cases already tested in getSetCheck() // some repetition from what is tested in getSetCheck()
QTest::newRow("empty") << QByteArray() << 0 << false;
QTest::newRow("spaces-heading") << QByteArray(" 1") << 1 << true;
QTest::newRow("spaces-trailing") << QByteArray("1 ") << 0 << false;
#define ROW(x, i, b) \ #define ROW(x, i, b) \
QTest::newRow(#x) << QByteArray(#x) << (i) << (b) QTest::newRow(#x) << QByteArray(#x) << (i) << (b)
ROW(auto, 0, false); ROW(auto, 0, false);
ROW(1auto, 0, false);
ROW(0, 0, true); ROW(0, 0, true);
ROW(+0, 0, true);
ROW(1, 1, true); ROW(1, 1, true);
ROW(+1, 1, true);
ROW(09, 0, false);
ROW(010, 8, true); ROW(010, 8, true);
ROW(0x10, 16, true); ROW(0x10, 16, true);
ROW(0x, 0, false);
ROW(0xg, 0, false);
ROW(0x1g, 0, false);
ROW(000000000000000000000000000000000000000000000000001, 0, false);
ROW(+000000000000000000000000000000000000000000000000001, 0, false);
ROW(000000000000000000000000000000000000000000000000001g, 0, false);
ROW(-0, 0, true);
ROW(-1, -1, true); ROW(-1, -1, true);
ROW(-010, -8, true); ROW(-010, -8, true);
ROW(-000000000000000000000000000000000000000000000000001, 0, false);
ROW(2147483648, 0, false);
// ROW(0xffffffff, -1, true); // could be expected, but not how QByteArray::toInt() works // ROW(0xffffffff, -1, true); // could be expected, but not how QByteArray::toInt() works
ROW(0xffffffff, 0, false); ROW(0xffffffff, 0, false);
const int bases[] = {10, 8, 16}; const int bases[] = {10, 8, 16};
@ -125,6 +141,7 @@ void tst_QGetPutEnv::intValue_data()
void tst_QGetPutEnv::intValue() void tst_QGetPutEnv::intValue()
{ {
const int maxlen = (sizeof(int) * CHAR_BIT + 2) / 3;
const char varName[] = "should_not_exist"; const char varName[] = "should_not_exist";
QFETCH(QByteArray, value); QFETCH(QByteArray, value);
@ -133,6 +150,13 @@ void tst_QGetPutEnv::intValue()
bool actualOk = !ok; bool actualOk = !ok;
// Self-test: confirm that it was like the docs said it should be
if (value.length() < maxlen) {
QCOMPARE(value.toInt(&actualOk, 0), expected);
QCOMPARE(actualOk, ok);
}
actualOk = !ok;
QVERIFY(qputenv(varName, value)); QVERIFY(qputenv(varName, value));
QCOMPARE(qEnvironmentVariableIntValue(varName), expected); QCOMPARE(qEnvironmentVariableIntValue(varName), expected);
QCOMPARE(qEnvironmentVariableIntValue(varName, &actualOk), expected); QCOMPARE(qEnvironmentVariableIntValue(varName, &actualOk), expected);