Время от времени мы перепроверяем проекты, которые уже проверяли и упоминали в наших статьях в прошлом. Qt - один из них. В последний раз мы проверяли его с помощью PVS-Studio в 2014 году. Начиная с 2014 года, проект регулярно проверяли с помощью Coverity, что делает его интереснее. Посмотрим, найдет ли PVS-Studio на этот раз какие-нибудь крутые баги.

Qt

Предыдущие статьи:

На этот раз мы проверили Qt Base (Core, Gui, Widgets, Network,…) и Qt5 super module. Что касается Qt Creator, мы планируем написать о нем отдельную статью позже. Проверка проводилась с помощью статического анализатора PVS-Studio; Вы можете скачать демо-версию с нашего сайта.

Лично я считаю, что качество кода Qt улучшилось. За годы, прошедшие с последней проверки, мы добавили в PVS-Studio много новых диагностик. Несмотря на это, беглый просмотр предупреждений анализатора показал, что для проекта такого размера было относительно мало ошибок. Как я уже сказал, это мое собственное впечатление о коде; Я не проводил специальных исследований плотности ошибок ни раньше, ни сейчас.

Похоже, высокое качество кода является результатом регулярных проверок статическим анализатором Coverity. С 2014 года разработчики используют его для проверки своего проекта (qt-project), а с 2016 года - Qt Creator (qt-creator). На мой взгляд, если вы разрабатываете проект с открытым исходным кодом, Coverity Scan - хороший выбор среди бесплатных инструментов и может значительно улучшить качество и надежность ваших проектов.

В любом случае, я бы явно не написал эту статью, если бы не нашел в отчете PVS-Studio ничего достойного :). А поскольку он здесь, значит, я нашел несколько ошибок. Посмотрим, что у нас здесь есть. Всего я записал 96 дефектов.

Плохой копипаст и опечатки

Начнем с классического: ошибки из-за невнимательности. Такие ошибки часто недооценивают, и если вы еще не читали эти две статьи, я рекомендую сделать это:

Ошибки этого типа характерны для всех языков. Например, во второй статье выше показано множество примеров ошибок в функциях сравнения, написанных на C, C ++ и C #. Сейчас, когда мы работаем над добавлением поддержки Java в PVS-Studio, мы наблюдаем те же шаблоны ошибок. Вот, например, ошибка, которую мы недавно обнаружили в библиотеке Hibernate:

public boolean equals(Object other) {
  if (other instanceof Id) {
    Id that = (Id) other;
    return purchaseSequence.equals(this.purchaseSequence) &&
           that.purchaseNumber == this.purchaseNumber;
  }
  else {
    return false;
  }
}

Если вы присмотритесь, то заметите, что поле PurchaseSequence сравнивается с самим собой. Это правильная версия:

return that.purchaseSequence.equals(this.purchaseSequence) &&
       that.purchaseNumber == this.purchaseNumber;

Это все та же старая история, и теперь PVS-Studio придется чистить авгиевы конюшни и внутри Java-проектов. Кстати, всех желающих приглашаем принять участие в бета-тестировании PVS-Studio для Java, которое скоро выйдет. Напишите нам, если хотите принять участие (выберите тему Я хочу проанализировать Java).

Возвращаясь к ошибкам в Qt.

Дефект 1

static inline int windowDpiAwareness(HWND hwnd)
{
  return QWindowsContext::user32dll.getWindowDpiAwarenessContext &&
         QWindowsContext::user32dll.getWindowDpiAwarenessContext
    ? QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext(
        QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd))
    : -1;
}

Диагностическое сообщение PVS-Studio: V501 CWE-571 Слева и справа от оператора «&&» находятся идентичные подвыражения ‘QWindowsContext :: user32dll.getWindowDpiAwarenessContext’. qwindowscontext.cpp 150

В этом случае не требуется никаких специальных комментариев, кроме сообщения. Думаю, это выражение должно было выглядеть так:

return QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext &&
       QWindowsContext::user32dll.getWindowDpiAwarenessContext
  ? QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext(
      QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd))
  : -1;

Дефекты 2, 3

void QReadWriteLockPrivate::release()
{
  Q_ASSERT(!recursive);
  Q_ASSERT(!waitingReaders && !waitingReaders &&
           !readerCount && !writerCount);
  freelist->release(id);
}

Диагностическое сообщение PVS-Studio: V501 CWE-571 Слева и справа от оператора «&&» находятся идентичные подвыражения:! WaitReaders &&! WaitReaders qreadwritelock.cpp 632

Ошибка находится внутри условия в макросе Q_ASSERT, что делает ее незначительным дефектом, но все же ошибкой. Переменная waitReaders проверяется дважды, что означает, что какая-то другая переменная вообще не проверялась.

Та же ошибка обнаружена в строке 625 файла qreadwritelock.cpp. Приветствую копипаст! :)

Дефект 4

QString QGraphicsSceneBspTree::debug(int index) const
{
  ....
  if (node->type == Node::Horizontal) {
    tmp += debug(firstChildIndex(index));
    tmp += debug(firstChildIndex(index) + 1);
  } else {
    tmp += debug(firstChildIndex(index));
    tmp += debug(firstChildIndex(index) + 1);
  }
  ....
}

Диагностическое сообщение PVS-Studio: V523 CWE-691 Оператор then эквивалентен оператору else. qgraphicsscene_bsp.cpp 179

Похоже, что программист скопировал этот блок кода, но забыл его изменить.

Дефект 5

enum FillRule {
  OddEvenFill,
  WindingFill
};
QDataStream &operator>>(QDataStream &s, QPainterPath &p)
{
  ....
  int fillRule;
  s >> fillRule;
  Q_ASSERT(fillRule == Qt::OddEvenFill || Qt::WindingFill);
  ....
}

Диагностическое сообщение PVS-Studio: V768 CWE-571 Константа перечисления «WindingFill» используется как переменная логического типа. qpainterpath.cpp 2479

Это так здорово! Q_ASSERT ничего не проверяет, так как условие всегда истинно. И это правда, потому что значение именованной константы Qt :: WindingFill равно 1.

Дефект 6

bool QVariant::canConvert(int targetTypeId) const
{
  ....
  if (currentType == QMetaType::SChar || currentType == QMetaType::Char)
    currentType = QMetaType::UInt;
  if (targetTypeId == QMetaType::SChar || currentType == QMetaType::Char)
    targetTypeId = QMetaType::UInt;
  ....
}

Попробуйте найти ошибку самостоятельно, прежде чем переходить к чтению предупреждения. Чтобы убедиться, что вы не посмотрите на это сразу, вот красивая картинка :).

Диагностическое сообщение PVS-Studio: V560 CWE-570 Часть условного выражения всегда ложна: currentType == QMetaType :: Char. qvariant.cpp 3529

Условие currentType == QMetaType :: Char проверяется в первом операторе if. Если это правда, переменной currentType присваивается значение QMetaType :: UInt. Это означает, что после этого он не может стать равным QMetaType :: Char. Вот почему анализатор сообщает нам, что подвыражение currentType == QMetaType :: Char во втором операторе if всегда ложно.

Второй элемент if должен выглядеть так:

if (targetTypeId == QMetaType::SChar || targetTypeId == QMetaType::Char)
  targetTypeId = QMetaType::UInt;

Примечание по диагностике V560

В отчете было много предупреждений V560, но я перестал читать, когда нашел интересный случай для включения в статью - см. Дефект 6 выше.

Большинство предупреждений V560 нельзя назвать ложными, но они все равно бесполезны. Другими словами, их неинтересно обсуждать. Вот один пример, чтобы объяснить, что я имею в виду.

QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, ....)
{
  QString url;
  if (!doc)
    return url;
  if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent()))
      return findUrlForImage(parent, cacheKey, isPixmap);
 
  if (doc && doc->docHandle()) {       // <=
  ....
}

Диагностическое сообщение PVS-Studio: V560 CWE-571 Часть условного выражения всегда истинна: ​​doc. qtextdocument.cpp 2992

Анализатор совершенно прав, когда говорит, что указатель doc всегда не равен nullptr во второй проверке. Но это не ошибка; программист просто перестраховывался. Код можно упростить следующим образом:

if (doc->docHandle()) {

Дефект 7

Это последний случай, который можно квалифицировать как опечатку. Ошибка возникает из-за того, что имена констант не совпадают, они отличаются только регистром первой буквы.

class QWindowsCursor : public QPlatformCursor
{
public:
  enum CursorState {
    CursorShowing,
    CursorHidden,
    CursorSuppressed
  };
  ....
}
QWindowsCursor::CursorState QWindowsCursor::cursorState()
{
  enum { cursorShowing = 0x1, cursorSuppressed = 0x2 };
  CURSORINFO cursorInfo;
  cursorInfo.cbSize = sizeof(CURSORINFO);
  if (GetCursorInfo(&cursorInfo)) {
    if (cursorInfo.flags & CursorShowing)
  ....
}

Диагностическое сообщение PVS-Studio: V616 CWE-480 В побитовой операции используется именованная константа CursorShowing со значением 0. qwindowscursor.cpp 669

Я уже подробно рассказывал об этом недочете некоторое время назад в небольшом отдельном посте: В очередной раз анализатор PVS-Studio оказался внимательнее человека.

Проблемы с безопасностью

По правде говоря, все обсуждаемые здесь ошибки можно назвать проблемами безопасности; все они попадают в классификацию Common Weakness Enumeration (см. тег CWE ID в предупреждениях анализатора). Ошибки, зарегистрированные в CWE, потенциально опасны с точки зрения безопасности. Более подробно по этой теме читайте на странице PVS-Studio SAST.

Однако некоторые ошибки требуют вынесения в отдельную группу. Давайте посмотрим на них.

Дефекты 8, 9

bool QLocalServerPrivate::addListener()
{
  ....
  SetSecurityDescriptorOwner(pSD.data(), pTokenUser->User.Sid, FALSE);
  SetSecurityDescriptorGroup(pSD.data(), pTokenGroup->PrimaryGroup, FALSE);
  ....
}

Диагностические сообщения PVS-Studio:

  • V530 CWE-252 Необходимо использовать возвращаемое значение функции SetSecurityDescriptorOwner. qlocalserver_win.cpp 167
  • V530 CWE-252 Необходимо использовать возвращаемое значение функции SetSecurityDescriptorGroup. qlocalserver_win.cpp 168

Существуют функции, которые имеют дело с контролем доступа, и среди них функции SetSecurityDescriptorOwner и SetSecurityDescriptorGroup.

Вы должны быть очень осторожны при использовании этих функций. Всегда проверяйте статус, который они возвращают. Что, если позвонить им не удастся? Не делайте предположений, просто напишите код, чтобы справиться с этим случаем.

Недостающие проверки не всегда легко использовать, чтобы превратить подобные дефекты в уязвимости. Но вы все равно не хотите рисковать. Напишите более безопасный код.

Дефект 10

bool QLocalServerPrivate::addListener()
{
  ....
  InitializeAcl(acl, aclSize, ACL_REVISION_DS);
  ....
}

Диагностическое сообщение PVS-Studio: V530 CWE-252 Необходимо использовать возвращаемое значение функции InitializeAcl. qlocalserver_win.cpp 144

Этот случай аналогичен предыдущему.

Дефекты 11, 12

static inline void sha1ProcessChunk(....)
{
  ....
  quint8 chunkBuffer[64];
  ....
#ifdef SHA1_WIPE_VARIABLES
  ....
  memset(chunkBuffer, 0, 64);
#endif
}

Диагностическое сообщение PVS-Studio: V597 CWE-14 Компилятор может удалить вызов функции memset, который используется для очистки буфера chunkBuffer. Для удаления личных данных следует использовать функцию RtlSecureZeroMemory (). sha1.cpp 189

Компилятор удалит вызов функции memset. Я уже обсуждал эту ситуацию во многих постах и ​​не хочу повторяться. См. Эту статью: Безопасная очистка личных данных.

Другая уязвимость обнаружена в том же файле sha1.cpp, строка 247.

Нулевые указатели

Пришло время поговорить об указателях. Ошибок из этой группы довольно много.

Дефект 13

QByteArray &QByteArray::append(const char *str, int len)
{
  if (len < 0)
    len = qstrlen(str);
  if (str && len) {
    ....
}

Диагностическое сообщение PVS-Studio: V595 CWE-476 Указатель 'str' использовался до того, как он был проверен против nullptr. Проверьте строки: 2118, 2119. qbytearray.cpp 2118

Это типичная ситуация: сначала используется указатель, а затем проверяется его соответствие nullptr. Этот паттерн ошибок очень распространен, и мы видим его постоянно почти в каждом проекте.

Дефекты 14, 15

static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }
bool QMetaEnum::isFlag() const
{
  const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1;
  return mobj && mobj->d.data[handle + offset] & EnumIsFlag;
}

Диагностическое сообщение PVS-Studio: V595 CWE-476 Указатель mobj использовался до того, как он был проверен на nullptr. Проверьте строки: 2671, 2672. qmetaobject.cpp 2671

На всякий случай цитирую тело функции priv. По какой-то причине читатели иногда придумывают сценарии, в которых, по их мнению, этот код будет работать. Интересно, откуда взялось это недоверие и желание рассматривать баг как хитрую фичу :). Например, кто-то может написать комментарий, предполагающий, что priv является макросом этого шаблона:

#define priv(A) foo(sizeof(A))

В этом случае, мол, все будет хорошо.

Чтобы избежать подобных споров, при цитировании фрагментов кода я стараюсь предоставить всю необходимую информацию, чтобы доказать, что код действительно ошибочен.

Итак, указатель modj разыменовывается, а затем проверяется.

Затем в игру вступает «Великий и мощный» копипаст, создавая клон этой ошибки в функции isScoped:

bool QMetaEnum::isScoped() const
{
  const int offset = priv(mobj->d.data)->revision >= 8 ? 2 : 1;
  return mobj && mobj->d.data[handle + offset] & EnumIsScoped;
}

Диагностическое сообщение PVS-Studio: V595 CWE-476 Указатель mobj использовался до того, как он был проверен на nullptr. Проверьте строки: 2683, 2684. qmetaobject.cpp 2683

Дефекты 16–21

Последний пример из этой группы.

void QTextCursor::insertFragment(const QTextDocumentFragment &fragment)
{
  if (!d || !d->priv || fragment.isEmpty())
    return;
  d->priv->beginEditBlock();
  d->remove();
  fragment.d->insert(*this);
  d->priv->endEditBlock();
  if (fragment.d && fragment.d->doc)
    d->priv->mergeCachedResources(fragment.d->doc->docHandle());
}

Диагностическое сообщение PVS-Studio: V595 CWE-476 Указатель «fragment.d» использовался до того, как он был проверен против nullptr. Проверить строки: 2238, 2241. qtextcursor.cpp 2238

Здесь ничего нового. Обратите внимание на порядок операций, выполняемых с указателем, хранящимся в переменной fragment.d.

Другие ошибки этого типа:

  • V595 CWE-476 Указатель «окна» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 1846, 1848. qapplication.cpp 1846
  • V595 CWE-476 Указатель «окна» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 1858, 1860. qapplication.cpp 1858
  • V595 CWE-476 Указатель «ответа» использовался до того, как он был проверен на соответствие nullptr. Проверьте строки: 492, 502. qhttpnetworkconnectionchannel.cpp 492
  • V595 CWE-476 Указатель «newHandle» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 877, 883. qsplitter.cpp 877
  • V595 CWE-476 Указатель «виджета» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 2320, 2322. qwindowsvistastyle.cpp 2320
  • Этот список на самом деле длиннее, но я быстро устал просматривать предупреждения V595, и у меня было достаточно фрагментов примеров для статьи.

Дефекты 22–33

Некоторые проверки проверяют указатель, возвращаемый оператором new. Это особенно забавно, учитывая, что во многих случаях результат, возвращаемый функцией malloc, вообще не проверяется (см. Следующую группу дефектов).

bool QTranslatorPrivate::do_load(const QString &realname,
                                 const QString &directory)
{
  ....
  d->unmapPointer = new char[d->unmapLength];
  if (d->unmapPointer) {
    file.seek(0);
    qint64 readResult = file.read(d->unmapPointer, d->unmapLength);
    if (readResult == qint64(unmapLength))
      ok = true;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V668 CWE-571 Нет смысла тестировать указатель «d-› unmapPointer »на нуль, так как память была выделена с помощью оператора« new ». Исключение будет сгенерировано в случае ошибки выделения памяти. qtranslator.cpp 596

Проверка указателя не имеет смысла, потому что сбой выделения памяти вызовет исключение std :: bad_alloc. Если разработчики хотели, чтобы оператор new возвращал nullptr, когда память не может быть выделена, им следовало написать это следующим образом:

d->unmapPointer = new (std::nothrow) char[d->unmapLength];

Анализатор знает об этой реализации оператора new и не выдает по ней предупреждений.

Прочие недоработки: см. Файл qt-V668.txt.

Дефекты 34–70

Как я и обещал, вот группа ошибок, связанных с отсутствием проверок значений, возвращаемых функциями malloc, calloc, strdup, и т. д. Они более серьезны, чем вы думаете. См. Статью Почему важно проверять, что возвращает функция malloc ».

SourceFiles::SourceFiles()
{
  nodes = (SourceFileNode**)malloc(sizeof(SourceFileNode*)*(num_nodes=3037));
  for(int n = 0; n < num_nodes; n++)
    nodes[n] = nullptr;
}

Диагностическое сообщение PVS-Studio: V522 CWE-690 Возможно разыменование потенциального нулевого указателя «узлов». Проверьте строки: 138, 136. makefiledeps.cpp 138

Указатель используется без предварительной проверки.

Все эти дефекты похожи, поэтому я не буду вдаваться в подробности. Остальные предупреждения этого типа перечислены в qt-V522-V575.txt.

Логические ошибки в условиях

Дефект 71

QString QEdidParser::parseEdidString(const quint8 *data)
{
  QByteArray buffer(reinterpret_cast<const char *>(data), 13);
  // Erase carriage return and line feed
  buffer = buffer.replace('\r', '\0').replace('\n', '\0');
  // Replace non-printable characters with dash
  for (int i = 0; i < buffer.count(); ++i) {
    if (buffer[i] < '\040' && buffer[i] > '\176')
      buffer[i] = '-';
  }
  return QString::fromLatin1(buffer.trimmed());
}

Диагностическое сообщение PVS-Studio: V547 CWE-570 Выражение ‘buffer [i]‹ ‘\ 040’ && buffer [i] ›‘ \ 176 ’’ всегда ложно. qedidparser.cpp 169

Функция предназначена для «замены непечатаемых символов тире», чего не делает. Давайте рассмотрим следующее условие:

if (buffer[i] < '\040' && buffer[i] > '\176')

Это не имеет никакого смысла. Символ не может быть меньше «\ 040» и больше «\ 176» одновременно. Вместо этого следует использовать оператор ‘||’:

if (buffer[i] < '\040' || buffer[i] > '\176')

Дефект 72

Еще одна похожая ошибка, которая коснется пользователей Windows.

#if defined(Q_OS_WIN)
static QString driveSpec(const QString &path)
{
  if (path.size() < 2)
    return QString();
  char c = path.at(0).toLatin1();
  if (c < 'a' && c > 'z' && c < 'A' && c > 'Z')
    return QString();
  if (path.at(1).toLatin1() != ':')
    return QString();
  return path.mid(0, 2);
}
#endif

Этот код вызывает сразу два предупреждения:

  • V590 CWE-571 Рассмотрите возможность проверки выражения «c‹ ‘a’ && c ›‘ z ’&& c‹ ‘A’ && c ›‘ Z ’’. Выражение является чрезмерным или содержит опечатку. qdir.cpp 77
  • V560 CWE-570 Часть условного выражения всегда ложна: c ›‘ z ’. qdir.cpp 77

Логическая ошибка обнаруживается в следующем состоянии:

if (c < 'a' && c > 'z' && c < 'A' && c > 'Z')

Насколько я понимаю, намерением разработчика было найти символ, не принадлежащий к латинскому алфавиту. В таком случае условие должно выглядеть так:

if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))

Дефект 73

enum SelectionMode {
  NoSelection,
  SingleSelection,
  MultiSelection,
  ExtendedSelection,
  ContiguousSelection
};
void QAccessibleTableCell::unselectCell()
{
  QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
  if (!m_index.isValid() || (selectionMode & QAbstractItemView::NoSelection))
    return;
  ....
}

Диагностическое сообщение PVS-Studio: V616 CWE-480 В побитовой операции используется именованная константа QAbstractItemView :: NoSelection со значением 0. itemviews.cpp 976

Поскольку значение именованной константы QAbstractItemView :: NoSelection равно нулю, подвыражение (selectionMode & QAbstractItemView :: NoSelection) бессмысленно: оно всегда будет оцениваться как 0.

Думаю, авторы намеревались написать следующее:

if (!m_index.isValid() || (selectionMode == QAbstractItemView::NoSelection))

Дефект 74

Приведенный ниже фрагмент - это то, что я не могу полностью понять. Он неисправен, но я все еще не уверен, как должна выглядеть его правильная версия. Комментарий тоже не помогает.

// Re-engineered from the inline function _com_error::ErrorMessage().
// We cannot use it directly since it uses swprintf_s(), which is not
// present in the MSVCRT.DLL found on Windows XP (QTBUG-35617).
static inline QString errorMessageFromComError(const _com_error &comError)
{
  TCHAR *message = nullptr;
  FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    NULL, DWORD(comError.Error()), MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
    message, 0, NULL);
  if (message) {
    const QString result = QString::fromWCharArray(message).trimmed();
    LocalFree(static_cast<HLOCAL>(message));
    return result;
  }
  if (const WORD wCode = comError.WCode())
    return QString::asprintf("IDispatch error #%u", uint(wCode));
  return QString::asprintf("Unknown error 0x0%x", uint(comError.Error()));
}

Диагностическое сообщение PVS-Studio: V547 CWE-570 Выражение «message» всегда ложно. qwindowscontext.cpp 802

Программист, вероятно, предполагал, что функция FormatMessage изменит значение указателя message, но это было неверно. Функция FormatMessage не может этого сделать, потому что указатель передается по значению. Вот прототип функции:

DWORD
__stdcall
FormatMessageW(
  DWORD dwFlags,
  LPCVOID lpSource,
  DWORD dwMessageId,
  DWORD dwLanguageId,
  LPWSTR lpBuffer,
  DWORD nSize,
  va_list *Arguments
);

Возможные утечки памяти

Дефекты 75–92

struct SourceDependChildren {
  SourceFile **children;
  int num_nodes, used_nodes;
  SourceDependChildren() : children(nullptr), num_nodes(0), used_nodes(0) { }
  ~SourceDependChildren() { if (children) free(children); children = nullptr; }
  void addChild(SourceFile *s) {
    if(num_nodes <= used_nodes) {
      num_nodes += 200;
      children = (SourceFile**)realloc(children,
                                       sizeof(SourceFile*)*(num_nodes));
    }
    children[used_nodes++] = s;
  }
};

Диагностическое сообщение PVS-Studio: V701 CWE-401 Возможная утечка realloc (): когда realloc () не может выделить память, исходный указатель «children» теряется. Рассмотрите возможность присвоения realloc () временному указателю. makefiledeps.cpp 103

Изменение размера буфера сделано опасным способом. Если функции realloc не удается выделить блок памяти, она вернет значение NULL, которое будет немедленно присвоено переменной children, оставив нет возможности освободить ранее выделенный буфер. Программа заканчивается утечкой памяти.

Другие похожие дефекты: qt-701.txt.

Разное

Дефект 93

template<class GradientBase, typename BlendType>
static inline const BlendType * QT_FASTCALL
qt_fetch_linear_gradient_template(....)
{
  ....
  if (t+inc*length < qreal(INT_MAX >> (FIXPT_BITS + 1)) &&
      t+inc*length > qreal(INT_MIN >> (FIXPT_BITS + 1))) {
  ....
}

Диагностическое сообщение PVS-Studio: V610 CWE-758 Неопределенное поведение. Проверьте оператора смены ‘››’. Левый операнд «(- 2147483647–1)» отрицательный. qdrawhelper.cpp 4015

Невозможно сдвинуть отрицательное значение INT_MIN. Это неопределенное поведение, и вы не можете полагаться на результат такой операции. Старшие биты могут содержать как 0, так и 1.

Дефект 94

void QObjectPrivate::addConnection(int signal, Connection *c)
{
  ....
  if (signal >= connectionLists->count())
    connectionLists->resize(signal + 1);
  ConnectionList &connectionList = (*connectionLists)[signal];
  ....
  if (signal < 0) {
  ....
}

Диагностическое сообщение PVS-Studio: V781 CWE-129 Значение переменной «signal» проверяется после ее использования. Возможно, есть ошибка в логике программы. Проверить строки: 397, 413. qobject.cpp 397

Проверка (signal ‹0) предполагает, что значение аргумента signal может быть отрицательным. Но этот аргумент раньше использовался для индексации в массиве. Это означает, что исполнение дойдет до проверки слишком поздно; к тому времени программа будет затронута.

Дефект 95

bool QXmlStreamWriterPrivate::finishStartElement(bool contents)
{
  ....
  if (inEmptyElement) {
    write("/>");
    QXmlStreamWriterPrivate::Tag &tag = tagStack_pop();
    lastNamespaceDeclaration = tag.namespaceDeclarationsSize;
    lastWasStartElement = false;
  } else {
    write(">");
  }
  inStartElement = inEmptyElement = false;
  lastNamespaceDeclaration = namespaceDeclarations.size();
  return hadSomethingWritten;
}

Диагностическое сообщение PVS-Studio: V519 CWE-563 Переменной lastNamespaceDeclaration два раза подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 3188, 3194. qxmlstream.cpp 3194

Вот самая важная часть:

if (inEmptyElement) {
  lastNamespaceDeclaration = tag.namespaceDeclarationsSize;
}
lastNamespaceDeclaration = namespaceDeclarations.size();

Дефект 96

void QRollEffect::scroll()
{
  ....
  if (currentHeight != totalHeight) {
    currentHeight = totalHeight * (elapsed/duration)
        + (2 * totalHeight * (elapsed%duration) + duration)
        / (2 * duration);
    // equiv. to int((totalHeight*elapsed) / duration + 0.5)
    done = (currentHeight >= totalHeight);
  }
  done = (currentHeight >= totalHeight) &&
         (currentWidth >= totalWidth);
  ....
}

V519 CWE-563 Переменной «done» дважды подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 509, 511. qeffects.cpp 511

Так же, как и в предыдущем случае. Обратите внимание на переменную done.

Вывод

Даже беглый просмотр выявил около 100 дефектов. Я рад, как работает PVS-Studio.

Конечно, такие периодические проверки не имеют ничего общего с повышением качества и надежности кода. Они достаточно хороши, чтобы продемонстрировать, на что способен анализатор. Инструменты статического анализа необходимо использовать регулярно: только тогда они помогут снизить стоимость исправления ошибок и защитить ваши программы от многих потенциальных уязвимостей.

Спасибо за чтение. Следите за обновлениями наших каналов: