Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

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

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

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

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

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


Если вы так и не получили ответ, пожалуйста, проверьте папку
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 содержит довольно мало подозрительных мест. Проект сравнительно молодой и инновационный, не содержит унаследованного от старых времён кода. Наверняка разработчики используют различные способы контроля качества и стараются следовать современным стандартам, и методологиям программирования.

Популярные статьи по теме
Как различить C и C++ разработчиков по их коду

Дата: 12 Май 2022

Автор: Гость

Так уж случилось, что я пишу код для разных IoT-железок, связанных с электричеством, типа зарядных станций автомобилей. Поскольку аппаратных ресурсов, как правило, вполне достаточно, то основным фоку…
Отладочный вывод на микроконтроллерах: как Concepts и Ranges отправили мой printf на покой

Дата: 06 Май 2022

Автор: Гость

Здравствуйте! Меня зовут Александр, и я работаю программистом микроконтроллеров.
Нереальный baselining или доработки PVS-Studio для Unreal Engine проектов

Дата: 26 Апр 2022

Автор: Валерий Комаров

Статический анализатор PVS-Studio постоянно развивается: улучшаются различные механизмы, происходит интеграция с игровыми движками, IDE, CI/CD и другими системами и сервисами. Благодаря этому несколь…
Разбор некоторых вредных советов для С++ программиста

Дата: 21 Апр 2022

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

Юмор юмором, но осторожность не повредит. Вдруг кому-то не до конца понятно, почему какой-то из советов является вредным. Здесь можно найти соответствующие пояснения.
Четыре причины проверять, что вернула функция malloc

Дата: 20 Апр 2022

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

Некоторые разработчики пренебрежительно относятся к проверкам: удалось ли выделить память с помощью функции malloc или нет. Их логика проста – памяти всегда должно хватить. А если не хватит, всё равн…

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

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