habrahabr

Математикам доверяй, но проверяй

  • понедельник, 31 марта 2014 г. в 03:10:17
http://habrahabr.ru/company/pvs-studio/blog/217557/

PVS-Studio. You shall not pass!
Я временами бываю озадачен, рассматривая ошибки в очередном программном проекте. Многие из этих ошибок живут в проектах годами. Смотришь на сотню ляпов в коде и удивляешься, как программа вообще работает. И ведь как-то работает. Ей даже пользуются. Причем, я говорю не о коде, рисующем покемона в игре. А, например, о математических библиотеках. Да, вы верно догадались. В этой статье пойдет речь о проверке кода математической библиотеки Scilab.

Scilab


Сегодня мы будем рассматривать подозрительные фрагменты кода в математическом пакете Scilab. Анализа выполнен с помощью инструмента PVS-Studio.

Scilab — пакет прикладных математических программ, предоставляющий мощное открытое окружение для инженерных (технических) и научных расчётов [Wikipedia].

Официальный сайт: http://www.scilab.org/

В системе доступно множество инструментов:
  • 2D и 3D графики, анимация;
  • линейная алгебра, разреженные матрицы (sparse matrices);
  • полиномиальные и рациональные функции;
  • интерполяция, аппроксимация;
  • симуляция: решение ОДУ и ДУ;
  • Scicos: гибрид системы моделирования динамических систем и симуляции;
  • дифференциальные и не дифференциальные оптимизации;
  • обработка сигналов;
  • параллельная работа;
  • статистика;
  • работа с компьютерной алгеброй;
  • интерфейс к Fortran, Tcl/Tk, C, C++, Java, LabVIEW.
Приготовьтесь. Статья будет длинная. Я ведь не виноват, что тут так много разных пакостей. И хочется показать разные классы ошибок.

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

Да, статический анализ находит только некоторые виды ошибок. Но раз их легко обнаружить, зачем себе отказывать в таком удовольствии. Лучше поправить ещё 10% ошибок, чем вовсе ничего не поправить.

Итак, давайте посмотрим, что рассказал мне анализатор PVS-Studio о проекте Scilab.

Буфер, которого нет


Буфер, которого нет
int sci_champ_G(....)
{
  ....
  char * strf = NULL ;
  ....
  if ( isDefStrf( strf ) )
  {
    char strfl[4];
    strcpy(strfl,DEFSTRFN);
    strf = strfl;
    if ( !isDefRect( rect ) )
    {
      strf[1]='5';
    }
  }

  (*func)(stk(l1), stk(l2), stk(l3), stk(l4),
    &m3, &n3, strf, rect, arfact, 4L);
  ....  
}

Сообщение PVS-Studio: V507 Pointer to local array 'strfl' is stored outside the scope of this array. Such a pointer will become invalid. sci_champ.c 103

Ссылка на временный массив 'strfl' сохраняется в переменной 'strf'. При выходе из блока «if () {… }» этот массив перестаёт существовать. Однако, в программе работают с указателем 'strf'.

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

Аналогичные проблемы:
  • Array 'strfl'. sci_fec.c 111
  • Array 'strfl'. sci_grayplot.c 94
  • Array 'strfl'. sci_matplot.c 84

Что-то не то посчитали


int C2F(pmatj)
  (char *fname, int *lw, int *j, unsigned long fname_len)
{
  ....
  ix1 = il2 + 4;
  m2 = Max(m, 1);
  ix1 = il + 9 + m * n;
  ....
}

Предупреждение PVS-Studio: V519 The 'ix1' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2387, 2389. stack1.c 2389

С переменной 'ix1' что-то неладно. Мне кажется, здесь какая-то опечатка.

В начале проверка, затем инициализация


В начале проверка, затем инициализация

Интересный фрагмент кода. Нужно получить некоторые значения, а затем проверить их. Но получилось наоборот.
int sci_Playsound (char *fname,unsigned long fname_len)
{
  ....
  int m1 = 0, n1 = 0;
  ....
  if ( (m1 != n1) && (n1 != 1) ) 
  {
    Scierror(999,_("%s: Wrong size for input argument #%d: ")
                 _("A string expected.\n"),fname,1);
    return 0;
  }
  sciErr = getMatrixOfWideString(pvApiCtx, piAddressVarOne,
             &m1,&n1,&lenStVarOne, NULL);
  ....
}

Предупреждения PVS-Studio: V560 A part of conditional expression is always false: (m1 != n1). sci_playsound.c 66; V560 A part of conditional expression is always true: (n1 != 1). sci_playsound.c 66

Переменные m1 и n1 должны получить значения при вызове функции getMatrixOfWideString(). Потом эти переменные должны быть проверены. Вот только получилось так, что проверка осуществляется до вызова функции getMatrixOfWideString().

В момент проверки переменные m1 и n1 равны 0. Условие «if ( (m1 != n1) && (n1 != 1) )» не выполняется. В результате, поверка никак не влияет на работу программы.

Итого. Не осуществляется проверка переменных m1 и n1.

Магические числа


Воображаемый друг
void CreCommon(f,var)
     FILE *f;
     VARPTR var;
{
  ....
  if ( strncmp(var->fexternal, "cintf", 4)==0 )
  ....
}

Предупреждение PVS-Studio: V666 Consider inspecting third argument of the function 'strncmp'. It is possible that the value does not correspond with the length of a string which was passed with the second argument. crerhs.c 119

Используется магическое число 4. И это число неправильное. В строке «cintf» пять символов, а не четыре. Не используйте такие магические числа.

Я бы сделал специальный макрос для вычисления длины строковых литералов и использовал бы его так:
if ( strncmp(var->fexternal, "cintf", litlen("cintf"))==0 )

Как изготовить макрос 'litlen', обсуждать не будем. Есть масса способов на любой вкус. Главное, избавиться от числа.

Другие неправильные размеры строк:
  • crerhs.c 121
  • crerhs.c 123
  • crerhs.c 125
  • crerhs.c 127

1, 2, 3, 4, 4, 6


Массив, дырка.
int C2F(run)(void)
{
  ....
  static int *Lpt = C2F(iop).lpt - 1;
  ....
  Lpt[1] = Lin[1 + k];
  Lpt[2] = Lin[2 + k];
  Lpt[3] = Lin[3 + k];
  Lpt[4] = Lin[4 + k];
  Lct[4] = Lin[6 + k ];
  Lpt[6] = k;
  ....
}

Предупреждение PVS-Studio: V525 The code containing the collection of similar blocks. Check items '1', '2', '3', '4', '4' in lines 1005, 1006, 1007, 1008, 1009. run.c 1005

Опечатка в последовательности чисел. В результате один элемент массива останется неинициализированным. Можно массу интересных математических результатов получить.

Эволюция кода


Эволюция
int write_xml_states(
  int nvar, const char * xmlfile, char **ids, double *x)
{
  ....
  FILE *fd = NULL;
  ....
  wcfopen(fd, (char*)xmlfile, "wb");
  if (fd < 0)
  {
    sciprint(_("Error: cannot write to  '%s'  \n"), xmlfile);
    ....
}

Предупреждение PVS-Studio: V503 This is a nonsensical comparison: pointer < 0. scicos.c 5826

Я почти уверен, что когда-то в этом коде для открытия файла использовалась функция open. Затем, код переписали и стали использовать функцию на _wfopen. Её вызов спрятан в макрос 'wcfopen'.

А вот проверку, что файл успешно открыт, поправить забыли. Функция open() возвращает в случае ошибки значение -1. Проверять же, что указатель меньше нуля, не имеет никакого практического смысла.

Ещё одно место, где прослеживается история.
void taucs_ccs_genmmd(taucs_ccs_matrix* m,
  int** perm, int** invperm)
{
  int  n, maxint, delta, nofsub;
  ....
  maxint = 32000;
  assert(sizeof(int) == 4);
  maxint = 2147483647; /* 2**31-1, for 32-bit only! */
  ....
}

Предупреждение PVS-Studio: V519 The 'maxint' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 154, 157. taucs_scilab.c 157

Ошибки здесь нет, но код забавен.

Видно, что давным-давно, было написано «maxint = 32000;». Затем ниже появилось:
assert(sizeof(int) == 4);
maxint = 2147483647; /* 2**31-1, for 32-bit only! */

Сортируем один элемент


Сортируем один элемент
char *getCommonPart(char **dictionary, int sizeDictionary)
{
  ....
  char *currentstr = dictionary[0];
  qsort(dictionary, sizeof dictionary / sizeof dictionary[0],
        sizeof dictionary[0], cmp);
  ....
}

Предупреждение PVS-Studio: V514 Dividing sizeof a pointer 'sizeof dictionary' by another value. There is a probability of logical error presence. getcommonpart.c 76

Второй аргумент функции qsort() — это количество элементов в массиве. Из-за ошибки, количество элементов всегда равно одному.

Рассмотрим выражение «sizeof dictionary / sizeof dictionary[0]». Здесь размер указателя делится на размер указателя. Результат равен единице.

Наверное, правильный код должен был быть таким:
qsort(dictionary, sizeDictionary, sizeof dictionary[0], cmp);

Аналогичная ошибка здесь: getfilesdictionary.c 105

Упрямые строки


void GetenvB(char *name, char *env, int len)
{
  int ierr = 0, one = 1;
  C2F(getenvc)(&ierr,name,env,&len,&one);
  if (ierr == 0) 
  {
    char *last = &env[len-1];
    while ( *last == ' ' ) { last = '\0' ; } 
    last--;
  }
  ....
}  

V527 It is odd that the '\0' value is assigned to 'char' type pointer. Probably meant: *last = '\0'. getenvb.c 24

Эта строка ужасна. Или прекрасна, если мы говорим об интересных ошибках.
while ( *last == ' ' ) { last = '\0' ; }

Если первый символ в строке пробел, то указатель станет равен нулю. Далее возникнет обращение по нулевому указателю.

Мне кажется, этот код должен был заменить все пробелы на '\0'. Тогда код должен быть таким:
while ( *last == ' ' ) { *last++ = '\0' ; }

Забавно, что есть ещё одно место в коде, где тоже хотят менять пробелы на нули. И его тоже не удалось написать правильно.
static int msg_101(int *n, int *ierr)
{
  ....
  for (i=0;i<(int)strlen(line);i++)
  {
    if (line[i]==' ') line[i]='\0';
    break;
  }
  ....
}

Предупреждение PVS-Studio: V612 An unconditional 'break' within a loop. msgs.c 1293

Всё бы хорошо, если бы не оператор 'break'. Будет заменён только один пробел. Впрочем, если убрать 'break' это не поможет. Функция strlen() вернёт ноль, и цикл всё равно остановится.

Аналогичные «одноразовые» циклы:
  • V612 An unconditional 'break' within a loop. msgs.c 1313
  • V612 An unconditional 'break' within a loop. api_common.cpp 1407

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


Разыменовывание нулевого указателя
char **splitLineCSV(....)
{
  ....
  if (retstr[curr_str] == NULL)
  {
    *toks = 0;
    FREE(substitutedstring);
    substitutedstring = NULL;
    freeArrayOfString(retstr, strlen(substitutedstring));
    return NULL;
  }
  ....
}

Предупреждение PVS-Studio: V575 The null pointer is passed into 'strlen' function. Inspect the first argument. splitline.c 107

Странный код. В начале явно обнулили указатель 'substitutedstring'. Затем, отдали его на растерзание в функцию strlen().

Скорее всего, вызов функции freeArrayOfString() должен быть расположен выше, чем вызов FREE().

Это была разминка. Теперь рассмотрим более сложный случай.
inline static void create(void * pvApiCtx, const int position,
  const int rows, const int cols, long long * ptr)
{
  int * dataPtr = 0;
  alloc(pvApiCtx, position, rows, cols, dataPtr);
  for (int i = 0; i < rows * cols; i++)
  {
    dataPtr[i] = static_cast<int>(ptr[i]);
  }
}

V522 Dereferencing of the null pointer 'dataPtr' might take place. scilababstractmemoryallocator.hxx 222

В этой функции хотят выделить память, используя функцию alloc(). Может показаться, что функция возвращает значение по ссылке. Последним аргументом является указатель 'dataPtr'. Кажется, что в него и будет записан указатель на выделенный буфер памяти.

Но это не так. Указатель останется равен нулю. Давайте посмотрим, как объявлена функция alloc():
inline static int *alloc(
  void * pvApiCtx, const int position, const int rows,
  const int cols, int * ptr)

Видите, последний аргумент не является ссылкой. Кстати, вообще не понятно, зачем он нужен. Заглянем внутрь функции alloc():
inline static int *alloc(
  void * pvApiCtx, const int position, const int rows,
  const int cols, int * ptr)
{
  int * _ptr = 0;
  SciErr err = allocMatrixOfInteger32(
    pvApiCtx, position, rows, cols, &_ptr);
  checkError(err);
  return _ptr;
}

Последний аргумент 'ptr' вообще не используется.

В любом случае, код выделения памяти неверен. Код должен быть таким:
inline static void create(void * pvApiCtx, const int position,
  const int rows, const int cols, long long * ptr)
{
  int *dataPtr = alloc(pvApiCtx, position, rows, cols, 0);
  for (int i = 0; i < rows * cols; i++)
  {
    dataPtr[i] = static_cast<int>(ptr[i]);
  }
}

Аналогичные ситуации:
  • scilababstractmemoryallocator.hxx 237
  • scilababstractmemoryallocator.hxx 401

Неправильные сообщения об ошибках


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

Пример неправильного формирования сообщения об ошибке:
static SciErr fillCommonSparseMatrixInList(....)
{
  ....
  addErrorMessage(&sciErr, API_ERROR_FILL_SPARSE_IN_LIST,
   _("%s: Unable to create list item #%d in Scilab memory"),
   _iComplex ? "createComplexSparseMatrixInList" :
               "createComplexSparseMatrixInList",
   _iItemPos + 1);
  ....
}

Сообщение PVS-Studio: V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: «createComplexSparseMatrixInList». api_list.cpp 2398

В независимости от значения переменной '_iComplex', всегда будет распечатано «createComplexSparseMatrixInList».

Аналогично:
  • api_list.cpp 2411
  • api_list.cpp 2418
  • api_list.cpp 2464
  • api_list.cpp 2471
Теперь рассмотрим обработчик ошибки, который никогда не получит управление:
#define __GO_FIGURE__ 9
#define __GO_UIMENU__ 21
int sci_uimenu(char *fname, unsigned long fname_len)
{
  ....
  if (iParentType == __GO_FIGURE__ &&
      iParentType == __GO_UIMENU__)
  {
    Scierror(999, _("%s: Wrong type for input argument #%d: ")
             _("A '%s' or '%s' handle expected.\n"), 
             fname, 1, "Figure", "Uimenu");
    return FALSE;
  }
  ....
}

Предупреждение PVS-Studio: V547 Expression 'iParentType == 9 && iParentType == 21' is always false. Probably the '||' operator should be used here. sci_uimenu.c 99

Условие (iParentType == __GO_FIGURE__ && iParentType == __GO_UIMENU__) никогда не выполняется. Переменная не может быть одновременно равна числу 9 и числу 21. Я думаю, здесь хотели написать так:
if (iParentType != __GO_FIGURE__ &&
    iParentType != __GO_UIMENU__)

Ещё один, особенно сладкий пример.
int set_view_property(....)
{
  BOOL status = FALSE;
  ....
  status = setGraphicObjectProperty(
    pobjUID, __GO_VIEW__, &viewType, jni_int, 1);

  if (status = TRUE)
  {
    return SET_PROPERTY_SUCCEED;
  }
  else
  {
    Scierror(999, _("'%s' property does not exist ")
      _("for this handle.\n"), "view");
    return  SET_PROPERTY_ERROR ;
  }
  ....
}

Предупреждение PVS-Studio: V559 Suspicious assignment inside the condition expression of 'if' operator: status = 1. set_view_property.c 61

Ошибка здесь: «if (status = TRUE)». Вместо сравнения, происходит присваивание.

Отсутствие выбора


Отсутствие выбора

Функция, которую можно явно сократить. Видимо, она написана с помощью Copy-Paste, и в скопированном коде что-то забыли поправить.
static int uf_union  (int* uf, int s, int t) {
  if (uf_find(uf,s) < uf_find(uf,t)) 
  {
    uf[uf_find(uf,s)] = uf_find(uf,t); 
    return (uf_find(uf,t)); 
  }
  else
  {
    uf[uf_find(uf,s)] = uf_find(uf,t); 
    return (uf_find(uf,t)); 
  }
}

Предупреждение PVS-Studio: V523 The 'then' statement is equivalent to the 'else' statement. taucs_scilab.c 700

Независимо от условия выполняются идентичные действия.

Теперь другая ситуация. Здесь совпадают условия:
int sci_xset( char *fname, unsigned long fname_len )
{
  ....
  else if ( strcmp(cstk(l1), "mark size") == 0)
  ....
  else if ( strcmp(cstk(l1), "mark") == 0)  
  ....
  else if ( strcmp(cstk(l1), "mark") == 0)
  ....
  else if ( strcmp(cstk(l1), "colormap") == 0)
  ....
}

Предупреждение PVS-Studio: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 175, 398. sci_xset.c 175

Есть ещё несколько неправильных условий:
  • sci_xset.c 159
  • h5_readdatafromfile_v1.c 1148
  • h5_readdatafromfile.c 1010

Классика


Пожалуй, я выделил самый частый ляп в программах на языке Си/Си++. В начале указатель разыменовывается, а уже затем проверяется на равенство нулю. Это не всегда приводит к ошибке. Но безобразие, оно и есть безобразие.
static void appendData(....)
{
  ....
  sco_data *sco = (sco_data *) * (block->work);
  int maxNumberOfPoints = sco->internal.maxNumberOfPoints;
  int numberOfPoints = sco->internal.numberOfPoints;
  
  if (sco != NULL && numberOfPoints >= maxNumberOfPoints)
  ....
}  

Предупреждение PVS-Studio: V595 The 'sco' pointer was utilized before it was verified against nullptr. Check lines: 305, 311. canimxy3d.c 305

В начале, осуществляли доступ к членам, используя указатель 'sco':
int maxNumberOfPoints = sco->internal.maxNumberOfPoints;
int numberOfPoints = sco->internal.numberOfPoints;

А потом вдруг вспомнили, что этот указатель надо проверить:
if (sco != NULL .....

Анализатор выдал ещё 61 одно предупреждение V595. Перечислять их в статье не вижу смысла. Привожу их отдельным списком: scilab-v595.txt.

Ещё одна распространённая ситуация — использование неправильных спецификаторов формата (format specifiers) при работе с функцией sprintf() и аналогичных ей. Почти всё, что нашлось, не интересно. Печатаем беззнаковые значения, как знаковые. Поэтому привожу все эти предупреждения тоже списком: scilab-v576.txt.

Из интересного можно отметить только вот это:
#define FORMAT_SESSION "%s%s%s"
char *getCommentDateSession(BOOL longFormat)
{
  ....
  sprintf(line, FORMAT_SESSION, SESSION_PRAGMA_BEGIN,
          STRING_BEGIN_SESSION, time_str, SESSION_PRAGMA_END);
  ....
}

Предупреждение PVS-Studio: V576 Incorrect format. A different number of actual arguments is expected while calling 'sprintf' function. Expected: 5. Present: 6. getcommentdatesession.c 68

Не будет распечатана строка SESSION_PRAGMA_END.

Осторожно, неопределённое поведение


Осторожно, неопределённое поведение. Это С++!
short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len)
{
  ....
  while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') {
  ....
}

Предупреждение PVS-Studio: V567 Undefined behavior. The 's' variable is modified while being used twice between sequence points. ezxml.c 385

Неизвестно, будет вычислено в начале выражение "++s' или выражение «strspn(s, EZXML_WS)». Соответственно, результат может отличаться на разных компиляторах, платформах и так далее.

Другой, более интересный случай. Здесь неопределённое поведение возникает из-за опечатки.
static char **replaceStrings(....)
{
  ....
  int i = 0;
  ....
  for (i = 0; i < nr; i = i++)
  ....
}

V567 Undefined behavior. The 'i' variable is modified while being used twice between sequence points. csvread.c 620

Беда здесь: i = i++.

По всей видимости, хотел написать так:
for (i = 0; i < nr; i++)

Ещё о строках


char *PLD_strtok(....)
{
  ....
  if ((st->start)&&(st->start != '\0'))
  ....
}

Предупреждение PVS-Studio: V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *st->start != '\0'. pldstr.c 303

Хотели проверить, что строка не пустая. Но на самом деле указатель два раза сравнивается с NULL. Правильный код:
if ((st->start)&&(st->start[0] != '\0'))

Аналогичный ляп:

V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: ** category == '\0'. sci_xcospalload.cpp 57

Следующий код по всей видимости недописан:
int sci_displaytree(char *fname, unsigned long fname_len)
{
  ....
  string szCurLevel = "";
  ....
  //Add node level
  if (szCurLevel != "")
  {
    szCurLevel + ".";
  }
  ....
}

Предупреждение PVS-Studio: V655 The strings was concatenated but are not utilized. Consider inspecting the 'szCurLevel + "."' expression. sci_displaytree.cpp 80

Код, который работает благодаря везению


Везение
static int sci_toprint_two_rhs(void* _pvCtx,
                               const char *fname)
{
  ....
  sprintf(lines, "%s%s\n", lines, pStVarOne[i]);
  ....
}

Предупреждение PVS-Studio: V541 It is dangerous to print the string 'lines' into itself. sci_toprint.cpp 314

Функция sprintf() сохраняет результат своей работы в буфер 'lines'. При этом, этот же буфер является одной из входных строк. Так делать не хорошо. Код вполне может работать. Но это опасно. При смене компилятора можно получить неожиданный и неприятный результат.

Аналогичная ситуация: sci_coserror.c 94

Пример кода, который работает, хотя и не верен:
typedef struct JavaVMOption {
    char *optionString;
    void *extraInfo;
} JavaVMOption;

JavaVMOption *options;

BOOL startJVM(char *SCI_PATH)
{
  ....
  fprintf(stderr, "%d: %s\n", j, vm_args.options[j]);
  ....
}

Предупреждение PVS-Studio: V510 The 'fprintf' function is not expected to receive class-type variable as fourth actual argument. jvm.c 247

Здесь хотели распечатать строку, на которую ссылается указатель 'optionString'. Правильный код должен был быть таким:
fprintf(stderr, "%d: %s\n", j, vm_args.options[j].optionString);

Но на самом деле, в качестве аргумента функция fprintf() примет объект типа JavaVMOption. Код работает благодаря чудесному стечению обстоятельств.

Во-первых, член 'optionString' расположен в начале структуры. Поэтому именно его возьмет функция fprintf() и обработает его как указатель на строку.

Во-вторых, после этого функция ничего не распечатывает. Следовательно, не будет распечатан мусор (содержимое переменной 'extraInfo', которая тоже попадёт в стек).

Аллилуйя!

Неработающий цикл


static void reinitdoit(double *told)
{
  int keve = 0, kiwa = 0;
  ....
  kiwa = 0;
  ....
  for (i = 0; i < kiwa; i++)
  ....
}

V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. scicos.c 4432

Здесь что-то не так. Переменная 'kiwa' всегда равна нулю. Цикл не выполняется. Возможно, код недописан.

Что не вошло в статью


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

Примечание. Тем, кто решил проверить один раз проверить проект с помощью CppCat и не покупать, хочу напомнить, что это совершенно бессмысленное действие. Вся суть статического анализа в регулярных проверках, а не в разовых запусках. Вы допускаете опечатку и анализатор сразу её обнаруживает. Сокращается время на тестирование, отладку и работу с ошибками, появляющимися в bug-трекере. См. также статью: "Лев Толстой и статический анализ кода".

Примечание


Обязательно кто-то спросит, какая версия Scilab проверялась. К сожалению, не самая свежая. Где-то полтора месяца я проверил этот проект, выписал подозрительные фрагменты кода. И… И забыл про этот файл. Было много работы, связанной со сравнением анализаторов. Сейчас я набрёл на этот файл, и долго вспоминал, что это такое было. Я проверяю так много проектов, что у меня в голове уже всё перепуталось, и я даже не помню, смотрел я какой-то проект или нет.

Впрочем, ничего страшного. Сейчас я напишу эту статью. Её увидят авторы Scilab и сами проверят проект. Цель моих статей показать возможности методологии статического анализа, а не найти ошибки в самой последней версии проекта.

Заключение


Используйте статический анализ на регулярной основе. Вы сократите время на устранение глупых ошибок и сможете потратить больше времени на что-то полезное.

Мы почти бесплатно раздаём анализатор CppCat. Покупка — $250, продление — $200, скидки. Для маленьких команд и индивидуальных разработчиков это отличный выбор.

Для средних и больших проектов, где требуются ночные проверки, доработка анализатора, интеграция с MSBuild, поддержка Visual Studio 2005/2008 и так далее, мы предлагаем инструмент PVS-Studio.

Дополнительные ссылки


  1. Терминология. Статический анализ кода.
  2. Андрей Карпов. Альтернатива PVS-Studio за $250.
  3. Андрей Карпов, Евгений Рыжков, Павел Еремеев, Святослав Размыслов. Сравнение анализаторов кода: CppCat, Cppcheck, PVS-Studio, Visual Studio. (методология сравнения).


Эта статья на английском


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Mathematicians: Trust, but Verify.

Прочитали статью и есть вопрос?
Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio и CppCat, версия 2014. Пожалуйста, ознакомьтесь со списком.