to the top
close form
Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Проверка кроссплатформенного фреймворка…

Проверка кроссплатформенного фреймворка Cocos2d-x

21 Авг 2014

Cocos2d — открытое программное обеспечение, фреймворк. Он может быть использован для построения игр, приложений и графических интерфейсов интерактивных кроссплатформенных приложений. Cocos2d содержит множество бранчей, известные из них Cocos2d-iPhone, Cocos2d-x, Cocos2d-html5 и Cocos2d-XNA.

В данной статье будут рассмотрены результаты проверки Cocos2d-x, фреймворка для C++, полученные с помощью PVS-Studio 5.18. Проект достаточно качественный, но всё же на некоторые места стоит обратить внимание. Исходный код взят с GitHub.

0275_cocos2d-x_ru/image1.png

От malloc к new, от C к С++

Работа с графическими объектами, как правило, связана с обработкой массивов и матриц, память под которые выделяют динамически. В данном проекте для выделения памяти используются как вызовы функции 'malloc', так и оператор 'new'. У этих способов есть существенные различия в использовании, которые необходимо учитывать при их замене в коде. Далее будут описаны места, не совсем корректно использующие 'malloc' и 'new'.

V630 The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors and destructors. ccmotionstreak.cpp 122

Vec2::Vec2() : x(0.0f), y(0.0f) { }
Vec2::Vec2(float xx, float yy) : x(xx), y(yy) { }

bool MotionStreak::initWithFade(...)
{
  ....
  _pointVertexes = (Vec2*)malloc(sizeof(Vec2) * _maxPoints);
  _vertices = (Vec2*)malloc(sizeof(Vec2) * _maxPoints * 2);
  _texCoords = (Tex2F*)malloc(sizeof(Tex2F) * _maxPoints * 2);
  ....
}

Обычно с выделенной памятью работают как с массивом объектов, имеющих конструктор или деструктор. При таком выделении памяти для класса не будет вызван конструктор. При освобождении памяти с помощью функции free также не будет вызван деструктор. Это крайне подозрительно. В результате переменные 'x' и 'y' будут не инициализированы. Конечно, можно вызвать конструктор для каждого объекта "вручную" или явно инициализировать поля, но более правильным будет использование оператора 'new':

_pointVertexes = new Vec2[_maxPoints];
_vertices = new Vec2[_maxPoints * 2];

Аналогичные места:

  • V630 The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors and destructors. ccmotionstreak.cpp 124
  • V630 The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors. ccmotionstreak.cpp 125

V572 It is odd that the object which was created using 'new' operator is immediately casted to another type. ccactiontiledgrid.cpp 322

struct Tile
{
    Vec2    position;
    Vec2    startPosition;
    Size    delta;
};

Tile* _tiles;

void ShuffleTiles::startWithTarget(Node *target)
{
  ....
  _tiles = (struct Tile *)new Tile[_tilesCount];  // <=
  Tile *tileArray = (Tile*) _tiles;               // <=
  ....
}

Здесь оператор 'new' уже возвращает типизированный указатель и приведение его к этому же типу является бессмысленным.

Похожее место:

  • V572 It is odd that the object which was created using 'new' operator is immediately casted to another type. luabasicconversions.cpp 1301

V668 There is no sense in testing the 'pRet' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ccfloat.h 48

static __Float* create(float v)
{
  __Float* pRet = new __Float(v); // <=
  if (pRet)                       // <=
  {
    pRet->autorelease();
  }
  return pRet;
}

Если оператор 'new' не смог выделить память, то согласно стандарту языка Си++, генерируется исключение std::bad_alloc(). Таким образом проверять указатель на равенство нулю не имеет смысла, в отличии от возвращаемого значения функции 'malloc'. В проекте есть ещё 475 таких проверок!

V547 Expression '0 == commonInfo->eventName' is always false. Pointer 'commonInfo->eventName' != NULL. ccluaengine.cpp 436

struct CommonScriptData
{
  // Now this struct is only used in LuaBinding.
  int handler;
  char eventName[64];                                    // <=
  ....
};

int LuaEngine::handleCommonEvent(void* data)
{
  ....
  CommonScriptData* commonInfo = static_cast<....*>(data);
  if (NULL == commonInfo->eventName ||                   // <=
      0 == commonInfo->handler)
    return 0;
  ....
}

Условие (NULL == commonInfo->eventName) всегда будет ложным, так как массив 'eventName' объявлен локально. Если выделить память под массив фиксированного размера не удастся, то проблема будет выявлена ранее - при выделении памяти под структуру.

Ещё проверки:

  • V547 Expression '0 != commonInfo->eventSourceClassName' is always true. Pointer 'commonInfo->eventSourceClassName' != NULL. ccluaengine.cpp 442
  • V600 Consider inspecting the condition. The 'commonInfo->eventName' pointer is always not equal to NULL. ccluaengine.cpp 436
  • V600 Consider inspecting the condition. The 'commonInfo->eventSourceClassName' pointer is always not equal to NULL. ccluaengine.cpp 442

Страшный сон структурного программирования

V696 The 'continue' operator will terminate 'do { ... } while (FALSE)' loop because the condition is always false. Check lines: 125, 153. cccomaudio.cpp 125

bool ComAudio::serialize(void* r)
{
  bool ret = false;
  do
  {
    ....
    if (file != nullptr)
    {
      if (strcmp(file, "") == 0)
      {
         continue;                   // <=
      }
      ....
    }
  }while(0);
  return ret;
}

Анализатор обнаружил код, который может ввести в заблуждение программиста. Оператор continue в цикле "do { ... } while(0)" остановит цикл, а не возобновит его. Таким образом, после вызова оператора 'continue' будет проверено условие (0), и цикл завершится так как условие ложно. Если именно так задумано и ошибки нет, то код всё равно лучше изменить. Можно воспользоваться оператором 'break'.

Аналогичные циклы:

  • V696 The 'continue' operator will terminate 'do { ... } while (FALSE)' loop because the condition is always false. Check lines: 188, 341. cccomrender.cpp 188
  • V696 The 'continue' operator will terminate 'do { ... } while (FALSE)' loop because the condition is always false. Check lines: 276, 341. cccomrender.cpp 276
  • V696 The 'continue' operator will terminate 'do { ... } while (FALSE)' loop because the condition is always false. Check lines: 281, 341. cccomrender.cpp 281
  • V696 The 'continue' operator will terminate 'do { ... } while (FALSE)' loop because the condition is always false. Check lines: 323, 341. cccomrender.cpp 323

Форматированный вывод

V576 Incorrect format. Consider checking the fourth actual argument of the 'fprintf' function. The pointer to string of char type symbols is expected. ccconsole.cpp 341

#ifdef UNICODE
#define gai_strerror   gai_strerrorW            // <=
#else
#define gai_strerror   gai_strerrorA
#endif  /* UNICODE */

bool Console::listenOnTCP(int port)
{
  ....
  fprintf(stderr,"net_listen error for %s: %s", // <=
    serv, gai_strerror(n));                     // <=
  ....
}

Функция gai_strerror может быть определена как gai_strerrorW и gai_strerrorA, в зависимости от определения директивы UNICODE. В Visual Studio 2012, где проверялся проект, была определена юникодная функция, которая возвращает широкую строку, для печати которой необходимо использовать спецификатор '%S' (большая S), иначе будет напечатан только первый символ строки или бессмысленный текст.

Одинаковый результат условия

V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: ATLAS_REPEAT. atlas.cpp 219

spAtlas* spAtlas_readAtlas (....)
{
  ....
  page->uWrap = *str.begin == 'x' ? ATLAS_REPEAT :
    (*str.begin == 'y' ? ATLAS_CLAMPTOEDGE : ATLAS_REPEAT);
  page->vWrap = *str.begin == 'x' ? ATLAS_CLAMPTOEDGE :
    (*str.begin == 'y' ? ATLAS_REPEAT : ATLAS_REPEAT);     // <=
  ....
}

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

Разыменование указателя

V595 The 'values' pointer was utilized before it was verified against nullptr. Check lines: 188, 189. ccbundlereader.h 188

template<>
inline bool BundleReader::readArray<std::string>(
  unsigned int *length, std::vector<std::string> *values)
{
  ....
  values->clear();             // <=
  if (*length > 0 && values)   // <=
  {
    for (int i = 0; i < (int)*length; ++i)
    {
      values->push_back(readString());
    }
  }
  return true;
}

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

  • V595 The '_openGLView' pointer was utilized before it was verified against nullptr. Check lines: 410, 417. ccdirector.cpp 410
  • V595 The 'node' pointer was utilized before it was verified against nullptr. Check lines: 365, 374. cctween.cpp 365
  • V595 The 'rootEle' pointer was utilized before it was verified against nullptr. Check lines: 378, 379. ccfileutils.cpp 378
  • V595 The 'tolua_ret' pointer was utilized before it was verified against nullptr. Check lines: 429, 433. lua_cocos2dx_manual.cpp 429
  • V595 The 'tolua_ret' pointer was utilized before it was verified against nullptr. Check lines: 1858, 1861. lua_cocos2dx_manual.cpp 1858
  • V595 The 'tolua_ret' pointer was utilized before it was verified against nullptr. Check lines: 4779, 4781. lua_cocos2dx_manual.cpp 4779
  • V595 The '_fontAtlas' pointer was utilized before it was verified against nullptr. Check lines: 384, 396. cclabel.cpp 384
  • V595 The '_glprogramstate' pointer was utilized before it was verified against nullptr. Check lines: 216, 218. shadertest2.cpp 216
  • V595 The '_sprite' pointer was utilized before it was verified against nullptr. Check lines: 530, 533. sprite3dtest.cpp 530

Неслучайный тест

V636 The 'rand() / 0x7fff' expression was implicitly casted from 'int' type to 'float' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. cpp-tests physicstest.cpp 307

static inline float frand(void) 
{
  return rand()/RAND_MAX; 
}

В исходных файлах для тестирования была обнаружена такая функция. Скорее всего планируется получать вещественные числа в диапазоне 0.0f до 1.0f, но результат функции rand() является целым числом, следовательно вещественная часть после деления отбрасывается. Функция возвращает только 0.0 или 1.0. Более того, так как функция rand() возвращает значение от 0 до RAND_MAX, вероятность получать результат 1.0 ничтожно мала.

Скорее тесты, использующие функцию frand() на самом деле ничего не тестируют. Хороший пример, как статический анализ дополняет тестирование с помощью юнит-тестов.

Заключение

Как я уже сказал в самом начале, Cocos2d-x содержит довольно мало подозрительных мест. Проект сравнительно молодой и инновационный, не содержит унаследованного от старых времён кода. Наверняка разработчики используют различные способы контроля качества и стараются следовать современным стандартам, и методологиям программирования.

Популярные статьи по теме
Под капотом SAST: как инструменты анализа кода ищут дефекты безопасности

Дата: 26 Янв 2023

Автор: Сергей Васильев

Сегодня речь о том, как SAST-решения ищут дефекты безопасности. Расскажу, как разные подходы к поиску потенциальных уязвимостей дополняют друг друга, зачем нужен каждый из них и как теория ложится на…
Ложные представления программистов о неопределённом поведении

Дата: 17 Янв 2023

Автор: Гость

Неопределённое поведение (UB) – непростая концепция в языках программирования и компиляторах. Я слышал много заблуждений в том, что гарантирует компилятор при наличии UB. Это печально, но неудивитель…
Топ-10 ошибок в C++ проектах за 2022 год

Дата: 29 Дек 2022

Автор: Владислав Столяров

Дело идёт к Новому году, а значит, самое время традиционно вспомнить десять самых интересных срабатываний, которые нашёл PVS-Studio в 2022 году.
PVS-Studio и RPCS3: лучшие предупреждения в один клик

Дата: 12 Дек 2022

Автор: Александр Куренев

Best Warnings — режим анализатора, оставляющий в окне вывода 10 лучших предупреждений. Мы предлагаем вам ознакомиться с обновлённым режимом Best Warnings на примере проверки проекта RPCS3.
Holy C++

Дата: 23 Ноя 2022

Автор: Гость

В этой статье постараюсь затронуть все вещи, которые можно без зазрения совести выкинуть из С++, не потеряв ничего (кроме боли), уменьшить стандарт, нагрузку на создателей компиляторов, студентов, из…

Комментарии (0)

Следующие комментарии next comments
close comment form
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо