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

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

![0244_Trust_but_Verify_ru/image1.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image1.png)

## Scilab

Сегодня мы будем рассматривать подозрительные фрагменты кода в математическом пакете Scilab\. Анализа выполнен с помощью инструмента [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\.

Scilab \- пакет прикладных математических программ, предоставляющий мощное открытое окружение для инженерных \(технических\) и научных расчётов \[[Wikipedia](https://ru.wikipedia.org/wiki/Scilab)\]\.

Официальный сайт: [http://www\.scilab\.org/](https://www.scilab.org/)

В системе доступно множество инструментов:

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

Приготовьтесь\. Статья будет длинная\. Я ведь не виноват, что тут так много разных пакостей\. И хочется показать разные классы ошибок\.

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

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

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

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

![0244_Trust_but_Verify_ru/image2.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image2.png)



```cpp
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

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

```cpp
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' что\-то неладно\. Мне кажется, здесь какая\-то опечатка\. 

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

![0244_Trust_but_Verify_ru/image3.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image3.png)



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

```cpp
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\.

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

![0244_Trust_but_Verify_ru/image4.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image4.png)



```cpp
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" пять символов, а не четыре\. Не используйте такие магические числа\.

Я бы сделал специальный макрос для вычисления длины строковых литералов и использовал бы его так:

```cpp
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

![0244_Trust_but_Verify_ru/image5.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image5.png)



```cpp
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

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

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

![0244_Trust_but_Verify_ru/image6.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image6.png)



```cpp
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](https://man7.org/linux/man-pages/man2/open.2.html)\. Затем, код переписали и стали использовать функцию на [\_wfopen](https://msdn.microsoft.com/ru-ru/library/yeby3zcb.aspx)\. Её вызов спрятан в макрос 'wcfopen'\.

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

Ещё одно место, где прослеживается история\.

```cpp
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;"\. Затем ниже появилось:

```cpp
assert(sizeof(int) == 4);
maxint = 2147483647; /* 2**31-1, for 32-bit only! */
```

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

![0244_Trust_but_Verify_ru/image7.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image7.png)

```cpp
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\]"\. Здесь размер указателя делится на размер указателя\. Результат равен единице\.

Наверное, правильный код должен был быть таким:

```cpp
qsort(dictionary, sizeDictionary, sizeof dictionary[0], cmp);
```

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

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

```cpp
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

Эта строка ужасна\. Или прекрасна, если мы говорим об интересных ошибках\.

```cpp
while ( *last == ' ' ) { last = '\0' ; }
```

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

Мне кажется, этот код должен был заменить все пробелы на '\\0'\. Тогда код должен быть таким:

```cpp
while ( *last == ' ' ) { *last++ = '\0' ; }
```

Забавно, что есть ещё одно место в коде, где тоже хотят менять пробелы на нули\. И его тоже не удалось написать правильно\.

```cpp
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

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

![0244_Trust_but_Verify_ru/image8.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image8.png)



```cpp
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\(\)\.

Это была разминка\. Теперь рассмотрим более сложный случай\.

```cpp
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\(\):

```cpp
inline static int *alloc(
  void * pvApiCtx, const int position, const int rows,
  const int cols, int * ptr)
```

Видите, последний аргумент не является ссылкой\. Кстати, вообще не понятно, зачем он нужен\. Заглянем внутрь функции alloc\(\):

```cpp
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' вообще не используется\.

В любом случае, код выделения памяти неверен\. Код должен быть таким:

```cpp
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 часто находит опечатки в обработчиках ошибок\. Этот код редко выполняется, и ошибки подолгу остаются незамеченными\. Я думаю, из\-за таких ошибок мы часто не можем понять, что же с программой не так\. Выданное программой диагностическое сообщение не соответствует действительности\.

Пример неправильного формирования сообщения об ошибке:

```cpp
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

Теперь рассмотрим обработчик ошибки, который никогда не получит управление:

```cpp
#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\. Я думаю, здесь хотели написать так:

```cpp
if (iParentType != __GO_FIGURE__ &&
    iParentType != __GO_UIMENU__)
```

Ещё один, особенно сладкий пример\.

```cpp
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\)"\. Вместо сравнения, происходит присваивание\.

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

![0244_Trust_but_Verify_ru/image9.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image9.png)



Функция, которую можно явно сократить\. Видимо, она написана с помощью Copy\-Paste, и в скопированном коде что\-то забыли поправить\.

```cpp
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

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

Теперь другая ситуация\. Здесь совпадают условия:

```cpp
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

## Классика

Пожалуй, я выделил самый частый ляп в программах на языке Си/Си\+\+\. В начале указатель разыменовывается, а уже затем проверяется на равенство нулю\. Это не всегда приводит к ошибке\. Но безобразие, оно и есть безобразие\.

```cpp
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':

```cpp
int maxNumberOfPoints = sco->internal.maxNumberOfPoints;
int numberOfPoints = sco->internal.numberOfPoints;
```

А потом вдруг вспомнили, что этот указатель надо проверить:

```cpp
if (sco != NULL .....
```

Анализатор выдал ещё 61 одно предупреждение V595\. Перечислять их в статье не вижу смысла\. Привожу их отдельным списком: [scilab\-v595\.txt](https://pvs-studio.com/external-pictures/txt/scilab-v595.txt)\.

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

Из интересного можно отметить только вот это:

```cpp
#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\.

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

![0244_Trust_but_Verify_ru/image10.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image10.png)



```cpp
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\)"\. Соответственно, результат может отличаться на разных компиляторах, платформах и так далее\.

Другой, более интересный случай\. Здесь неопределённое поведение возникает из\-за опечатки\.

```cpp
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\+\+\.

По всей видимости, хотел написать так:

```cpp
for (i = 0; i < nr; i++)
```

## Ещё о строках

```cpp
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\. Правильный код:

```cpp
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



Следующий код по всей видимости недописан:

```cpp
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

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

![0244_Trust_but_Verify_ru/image11.png](https://import.viva64.com/docx/blog/0244_Trust_but_Verify_ru/image11.png)



```cpp
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

Пример кода, который работает, хотя и не верен:

```cpp
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'\. Правильный код должен был быть таким:

```cpp
fprintf(stderr, "%d: %s\n", j, vm_args.options[j].optionString);
```

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

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

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

Аллилуйя\!

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

```cpp
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](https://pvs-studio.ru/ru/pvs-studio/)\. 

**Примечание\.** Тем, кто решил проверить один раз проверить проект и не покупать анализатор, хочу напомнить, что это совершенно бессмысленное действие\. Вся суть статического анализа в регулярных проверках, а не в разовых запусках\. Вы допускаете опечатку и анализатор сразу её обнаруживает\. Сокращается время на тестирование, отладку и работу с ошибками, появляющимися в bug\-трекере\. См\. также статью: "[Лев Толстой и статический анализ кода](https://pvs-studio.ru/ru/blog/posts/0105/)"\.

## Примечание

Обязательно кто\-то спросит, какая версия Scilab проверялась\. К сожалению, не самая свежая\. Где\-то полтора месяца я проверил этот проект, выписал подозрительные фрагменты кода\. И\.\.\. И забыл про этот файл\. Было много работы, связанной со [сравнением анализаторов](https://pvs-studio.ru/ru/blog/posts/0241/)\. Сейчас я набрёл на этот файл, и долго вспоминал, что это такое было\. Я проверяю так много проектов, что у меня в голове уже всё перепуталось, и я даже не помню, смотрел я какой\-то проект или нет\.

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

## Заключение

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

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

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

1. Терминология\. [Статический анализ кода](https://pvs-studio.ru/ru/blog/terms/0046/)\.
1. Андрей Карпов, Евгений Рыжков, Павел Еремеев, Святослав Размыслов\. [Сравнение анализаторов кода: CppCat, Cppcheck, PVS\-Studio, Visual Studio](https://pvs-studio.ru/ru/blog/posts/0241/)\. \([методология сравнения](https://pvs-studio.ru/ru/blog/posts/a0086/)\)\.