...

воскресенье, 30 марта 2014 г.

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

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

Я почти уверен, что когда-то в этом коде для открытия файла использовалась функция 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.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий