>
>
>
Анализ проекта TrinityCore с помощью PV…

Андрей Карпов
Статей: 674

Анализ проекта TrinityCore с помощью PVS-Studio

TrinityCore - бесплатный проект, распространяемый под лицензией GPL. Целью проекта является создание альтернативного программного обеспечения для эмулирования сервера многопользовательской игры World of Warcraft от Blizzard Entertainment. Основная цель проекта – обучающая. Проект ни в коем случаи не направлен на извлечение прибыли от использования. Исходный код, который написан на C и C++, является открытым, это значит, что он распространяется свободно, при этом на пользователя не накладывается никаких обязательств.

Мы проверили проект, используя анализатор PVS-Studio версии 4.54. Ошибок найдено немного и в основном они относятся к внешним библиотекам. Чтобы подробно проанализировать проект можно воспользоваться триальной версией PVS-Studio. Её будет достаточно, чтобы просмотреть все подозрительные участки кода. Здесь перечислим только некоторые фрагменты кода, на которых остановился взгляд:

1. Опечатка. Два раза используется 'other.y'.

inline Vector3int32& operator+=(const Vector3int32& other)
{
  x += other.x;
  y += other.y;
  z += other.y;
  return *this;
}

PVS-Studio: V537 Consider reviewing the correctness of 'y' item's usage. g3dlib vector3int32.h 77

2. Ошибка в макросе NEXT_CMP_VALUE.

static struct wordvalue doubles[] = {
 { "ch", (uchar*) "\014\031\057\057" },
 { "Ch", (uchar*) "\014\031\060\060" },
 { "CH", (uchar*) "\014\031\061\061" },
 { "c",  (uchar*) "\005\012\021\021" },
 { "C",  (uchar*) "\005\012\022\022" },
 };

#define NEXT_CMP_VALUE(src, p, store, pass, value, len) \
while (1)                                      \
{                                              \
  ......                                       \
  for (i = 0; i < (int) sizeof(doubles); i++)  \
  {                                            \
    const char * pattern = doubles[i].word;    \
    ...                                        \
    }                                          \
  }                                            \
  ......                                       \
}

PVS-Studio: V557 Array overrun is possible. The value of 'i' index could reach 39. libmysql ctype-czech.c 260

Корректный код должен выглядеть так:

for (i = 0; i < (int) sizeof(doubles) / sizeof(doubles[0]); i++)

3. Копирование только части строки.

class ACE_Name_Request
{
  ...
  char *type_;
};

void
ACE_Name_Request::type (const char *c)
{
  ACE_TRACE ("ACE_Name_Request::type");
  ACE_OS::strsncpy (this->type_,
                    c,
                    sizeof this->type_);
}

PVS-Studio: V579 The strsncpy function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. ace name_request_reply.cpp 251

Вместо длины строки вычисляется размер указателя.

4. Недоочищенный буфер.

ACE_INLINE int
ACE_Thread::disablecancel(struct cancel_state *old_state)
{
  ...
  ACE_OS::memset (old_state,
                  0,
                  sizeof (old_state));
  ...
}

PVS-Studio: V579 The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. ace thread.inl 172

5. Ошибка при cравнении матриц.

bool Matrix4::operator==(const Matrix4& other) const {
  if (memcmp(this, &other, sizeof(Matrix4) == 0)) {
    return true;
  } 
  ...
}

PVS-Studio: V575 The 'memcmp' function processes '0' elements. Inspect the third argument. g3dlib matrix4.cpp 385

6. Всегда истинное условие.

enum enum_mysql_timestamp_type
str_to_datetime(....)
{
  ...
  else if (str[0] != 'a' || str[0] != 'A')
  ...
}

PVS-Studio: V547 Expression 'str[0] != 'a' || str[0] != 'A'' is always true. Probably the '&&' operator should be used here. libmysql my_time.c 340

7. Не там поставленная скобка.

static my_bool socket_poll_read(my_socket sd, uint timeout)
{
  int res;
  ...
  if ((res = select((int) fd,
         &readfds, NULL, &errorfds, &tm) <= 0))
  {
    DBUG_RETURN(res < 0 ? 0 : 1);
  }
  ...
}

PVS-Studio: V593 Consider reviewing the expression of the 'A = B <= C' kind. The expression is calculated as following: 'A = (B <= C)'. libmysql viosocket.c 550

Корректный код должен выглядеть так:

if ((res= select((int) fd,
            &readfds, NULL, &errorfds, &tm)) <= 0)

8. Неправильные проверки указатель на значение 0.

Достаточно много обнаруживается проверок, что указатель равен 0, уже после того, как указатель использовали. Первый пример:

bool OnCheck(Player* player, Unit* /*target*/)
{
  bool checkArea =
    player->GetAreaId() == AREA_ARGENT_TOURNAMENT_FIELDS ||
    player->GetAreaId() == AREA_RING_OF_ASPIRANTS ||
    player->GetAreaId() == AREA_RING_OF_ARGENT_VALIANTS ||
    player->GetAreaId() == AREA_RING_OF_ALLIANCE_VALIANTS ||
    player->GetAreaId() == AREA_RING_OF_HORDE_VALIANTS ||
    player->GetAreaId() == AREA_RING_OF_CHAMPIONS;

  return player && checkArea &&
         player->duel && player->duel->isMounted;
}

PVS-Studio: V595 The 'player' pointer was utilized before it was verified against nullptr. Check lines: 310, 312. scripts achievement_scripts.cpp 310

Еще один пример:

CreatureAI* GetAI(Creature* creature) const
{
  ...
  Item* item =
    player->StoreNewItem(dest, ITEM_TEAR_OF_GODDESS, true);
  if (item && player)
    player->SendNewItem(item, 1, true, false, true);
  ...
}

PVS-Studio: V595 The 'player' pointer was utilized before it was verified against nullptr. Check lines: 224, 225. scripts hyjal.cpp 224

Вывод

С трудом верится, что этот проект разрабатывается энтузиастами, обучающимися программированию. Здесь перечислены не все замеченные недостатки. Но всё равно найденных ошибок слишком мало. Возможно, кто-то из разработчиков уже использовал PVS-Studio для проверки TrinityCore. Косвенным подтверждением является то, что больше всего ошибок находится с помощью диагностического правила V595. А эта диагностика появилась совсем недавно.