﻿# Проверяем Wine с помощью PVS\-Studio и Clang Static Analyzer

В статье хочу рассказать о проверке проекта Wine такими статическими анализаторами C/C\+\+ кода, как PVS\-Studio и Clang Static Analyzer\.

![0272_Wine_PVS-Studio_Clang_ru/image1.png](https://import.viva64.com/docx/blog/0272_Wine_PVS-Studio_Clang_ru/image1.png)

## Wine

[Wine](https://www.winehq.org/) \(Wine Is Not Emulator \-\- Wine \- не эмулятор\) \- это набор программ, позволяющий пользователям Linux, Mac, FreeBSD, и Solaris запускать Windows\-приложения без необходимости установки на компьютер самой Microsoft Windows\. Wine является активно развивающимся кросс\-платформенным свободным ПО, распространяемым под лицензией GNU Lesser General Public License\.

Исходный код проекта можно получить командой git clone git://source\.winehq\.org/git/wine\.git

## Об анализаторах

* [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) \- статический анализатор, выявляющий ошибки в исходном коде приложений на языке C/C\+\+/C\+\+11\. Для проверки проекта использовалась релиз\-версия PVS\-Studio 5\.18\.
* [Clang Static Analyzer](http://clang-analyzer.llvm.org/) \- статический анализатор, который находит ошибки в C, C\+\+ и Objective\-C программах\. Для проверки проекта использовалась релиз\-версия Clang 3\.4\.2 для openSUSE 13\.1\.

## Результаты проверки в PVS\-Studio

### Сдвиг отрицательного числа

[V610](https://pvs-studio.ru/ru/docs/warnings/v610/) Undefined behavior\. Check the shift operator '<<\. The left operand '\(LONGLONG\) \- 1' is negative\. propvar\.c 127

```cpp
...
if (*res >= ((LONGLONG)1 << (dest_bits-1)) ||
  *res < ((LONGLONG)-1 << (dest_bits-1)))
  return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
...
```

Тип LONGLONG объявлен как 'typedef signed \_\_int64 LONGLONG;' , т\.е\. является знаковым типом\. Сдвиги отрицательных чисел по новому стандарту приводят к неопределённому или неуточненному поведению\. Почему такой код может работать и как его лучше исправить, можно прочитать в статье: [Не зная брода, не лезь в воду\. Часть третья\.](https://pvs-studio.ru/ru/blog/posts/cpp/0142/)

### Опечатки

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions '\!lpScaleWindowExtEx\-\>xNum' to the left and to the right of the '\|\|' operator\. enhmetafile\.c 1418

```cpp
...
if (!lpScaleWindowExtEx->xNum || !lpScaleWindowExtEx->xDenom ||
    !lpScaleWindowExtEx->xNum || !lpScaleWindowExtEx->yDenom)
  break;
...
```

В условии два раза проверяется lpScaleWindowExtEx\-\>xNum, скорее всего в одном месте должно быть lpScaleWindowExtEx\-\>yNum\. В объявлении используемой структуры такое поле есть:

```cpp
typedef struct {
    EMR  emr;
    LONG xNum;   // <=
    LONG xDenom;
    LONG yNum;   // <=
    LONG yDenom;
} EMRSCALEVIEWPORTEXTEX, *PEMRSCALEVIEWPORTEXTEX,
  EMRSCALEWINDOWEXTEX,   *PEMRSCALEWINDOWEXTEX;
```

V501 There are identical sub\-expressions '\!\(types\[i \+ 1\] & PathPointTypeBezier\)' to the left and to the right of the '\|\|' operator\. graphics\.c 1751

```cpp
....
for(i = 1; i < count; i++){
  ....
  if((i + 2 >= count) ||
    !(types[i + 1] & PathPointTypeBezier) ||
    !(types[i + 1] & PathPointTypeBezier)){
    ....
  }
  i += 2;
}
....
```

Это место обнаружено аналогичной диагностикой [V501](https://pvs-studio.ru/ru/docs/warnings/v501/), но причина одинаковых условий тут не так очевидна\. Скорее всего, в одном условии должно быть types\[i \+ 2\], потому что выше проверили возможность обратиться к элементу массива с индексом на 2 больше, чем 'i'\.

[V593](https://pvs-studio.ru/ru/docs/warnings/v593/) Consider reviewing the expression of the 'A \= B \!\= C' kind\. The expression is calculated as following: 'A \= \(B \!\= C\)'\. request\.c 3354

```cpp
if ((hr = SafeArrayAccessData( sa, (void **)&ptr )) != S_OK)
  return hr;
if ((hr = SafeArrayGetUBound( sa, 1, &size ) != S_OK)) // <=
{
    SafeArrayUnaccessData( sa );
    return hr;
}
```

Приоритет оператора \!\= выше, чем оператора присваивания \=\. Причём по условию выше хорошо видно, что здесь тоже необходимо обернуть присваивание ещё в одни скобки, иначе hr получает значение 0 или 1\.

Ещё похожее место:

V501 There are identical sub\-expressions to the left and to the right of the '\|' operator: VT\_ARRAY \| VT\_ARRAY vartest\.c 2161

### Каскадирование условных операторов

[V517](https://pvs-studio.ru/ru/docs/warnings/v517/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. Check lines: 1754, 1765\. msi\.c 1754

```cpp
if (!strcmpW( szProperty, INSTALLPROPERTY_LOCALPACKAGEW )) // <=
{
  ...
}
else if (!strcmpW( szProperty, INSTALLPROPERTY_INSTALLDATEW ))
{
  ...
}
else
if (!strcmpW( szProperty, INSTALLPROPERTY_LOCALPACKAGEW )) // <=
{
  ...
}
else if (!strcmpW( szProperty, INSTALLPROPERTY_UNINSTALLABLEW ) ||
         !strcmpW( szProperty, INSTALLPROPERTY_PATCHSTATEW ) ||
         !strcmpW( szProperty, INSTALLPROPERTY_DISPLAYNAMEW ) ||
         !strcmpW( szProperty, INSTALLPROPERTY_MOREINFOURLW ))
{
  ...
}
else
{
  ...
}
```

Если в каскаде условных операторов проверяются одинаковые условия, то последние не получают управление\. Возможно здесь опечатка в переданной константе INSTALLPROPERTY\_\*\.

### Эквивалентные ветви оператора if

[V523](https://pvs-studio.ru/ru/docs/warnings/v523/) The 'then' statement is equivalent to the 'else' statement\. filedlg\.c 3302

```cpp
if(pDIStruct->itemID == liInfos->uSelectedItem)
{
  ilItemImage = (HIMAGELIST) SHGetFileInfoW (
    (LPCWSTR) tmpFolder->pidlItem, 0, &sfi, sizeof (sfi),
    shgfi_flags );
}
else
{
  ilItemImage = (HIMAGELIST) SHGetFileInfoW (
    (LPCWSTR) tmpFolder->pidlItem, 0, &sfi, sizeof (sfi),
    shgfi_flags );
}
```

Подобный код либо избыточен, либо имеет место опечатка\.

V523 The 'then' statement is equivalent to the 'else' statement\. genres\.c 1130

```cpp
...
if(win32)
{
  put_word(res, 0);  /* Reserved */
  /* FIXME: The ResType in the NEWHEADER structure should
   * contain 14 according to the MS win32 doc. This is
   * not the case with the BRC compiler and I really doubt
   * the latter. Putting one here is compliant to win16 spec,
   * but who knows the true value?
   */
  put_word(res, 1);  /* ResType */
  put_word(res, icog->nicon);
  for(ico = icog->iconlist; ico; ico = ico->next)
  {
    ...
  }
}
else /* win16 */
{
  put_word(res, 0);  /* Reserved */
  put_word(res, 1);  /* ResType */
  put_word(res, icog->nicon);
  for(ico = icog->iconlist; ico; ico = ico->next)
  {
    ...
  }
}
...
```

Здесь одна из повторяющихся ветвей прокомментирована\. Возможно анализатор нашёл место, которое недоделали\. Не ошибка, но решил отметить\.

### Изменение длины строки

[V692](https://pvs-studio.ru/ru/docs/warnings/v692/) An inappropriate attempt to append a null character to a string\. To determine the length of a string by 'strlen' function correctly, a string ending with a null terminator should be used in the first place\. appdefaults\.c 390

```cpp
...
section[strlen(section)] = '\0'; /* remove last backslash  */
...
```

На самом деле ноль запишется в ноль и ничего не изменится\. Для работы функции strlen\(\) строка уже должна заканчиваться терминальным нулём\. Комментарий намекает нам, что, наверное, хотели отрезать косую черту в конце\. Тогда, видимо код должен быть такой:

```cpp
section[strlen(section) - 1] = '\0';
```

### Один счётчик на два цикла

[V535](https://pvs-studio.ru/ru/docs/warnings/v535/) The variable 'i' is being used for this loop and for the outer loop\. Check lines: 980, 1003\. iphlpapi\_main\.c 1003

```cpp
...
for (i = 0; i < num_v6addrs; i++)    // <=
{
    ...
    for (i = 0; i < 8 && !done; i++) // <=
    {
        ...
    }
    ...
    if (i < num_v6addrs - 1)
    {
        prefix->Next = (IP_ADAPTER_PREFIX *)ptr;
        prefix = prefix->Next;
    }
}
...
```

Подозрительное место: вложенный цикл организован с использованием переменной i, которая также используется и во внешнем цикле\.

### Двойное приведение типов

В следующих двух примерах к указателю \*void применяется два приведения к типу: сначала к char\*, потом к DWORD\*, после чего прибавляется смещение\. В выражении не хватает скобок, либо код избыточен\. Есть ли тут ошибка, зависит от того, на сколько хотели увеличить значение указателя\.

[V650](https://pvs-studio.ru/ru/docs/warnings/v650/) Type casting operation is utilized 2 times in succession\. Next, the '\+' operation is executed\. Probably meant: \(T1\)\(\(T2\)a \+ b\)\. typelib\.c 9147

```cpp
...
struct WMSFT_SegContents arraydesc_seg;
typedef struct tagWMSFT_SegContents {
    DWORD len;
    void *data;
} WMSFT_SegContents;
...
DWORD offs = file->arraydesc_seg.len;
DWORD *encoded;
encoded = (DWORD*)((char*)file->arraydesc_seg.data) + offs;// <=
```

Ещё одна схожая ситуация:

V650 Type casting operation is utilized 2 times in succession\. Next, the '\+' operation is executed\. Probably meant: \(T1\)\(\(T2\)a \+ b\)\. protocol\.c 194

```cpp
INT WINAPI
EnumProtocolsW(LPINT protocols, LPVOID buffer, LPDWORD buflen)
{
  ...
  unsigned int string_offset;
  ...
  pi[i].lpProtocol = (WCHAR*)(char*)buffer + string_offset;// <=
  ...
}
```

### Разность беззнаковых чисел

[V555](https://pvs-studio.ru/ru/docs/warnings/v555/) The expression 'This\-\>nStreams \- nr \> 0' will work as 'This\-\>nStreams \!\= nr'\. editstream\.c 172

```cpp
static HRESULT
AVIFILE_RemoveStream(IAVIEditStreamImpl* const This, DWORD nr)
{
  ...
  This->nStreams--;
  if (This->nStreams - nr > 0) { // <=
    memmove(This->pStreams + nr, This->pStreams + nr + 1,
            (This->nStreams - nr) * sizeof(EditStreamTable));
  }
  ...
}
```

Переменная nr имеет баззнаковый тип DWORD\. Вычитая её, результат разности также будет иметь беззнаковый тип\. Если nr будет больше This\-\>nStreams \- условие всё равно будет истинным\.

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

V555 The expression 'This\-\>fInfo\.dwStreams \- nStream \> 0' will work as 'This\-\>fInfo\.dwStreams \!\= nStream'\. avifile\.c 469

### Сначала казнь, потом обед

[V595](https://pvs-studio.ru/ru/docs/warnings/v595/) The 'decl' pointer was utilized before it was verified against nullptr\. Check lines: 1411, 1417\. parser\.y 1411

```cpp
...
var_t *v = decl->var; // <=
expr_list_t *sizes = get_attrp(attrs, ATTR_SIZEIS);
expr_list_t *lengs = get_attrp(attrs, ATTR_LENGTHIS);
int sizeless;
expr_t *dim;
type_t **ptype;
array_dims_t *arr = decl ? decl->array : NULL;     // <=
type_t *func_type = decl ? decl->func_type : NULL; // <=
...
```

Сначала брали значение по указателю, потом решили проверять\.

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

* V595 The 'pcbData' pointer was utilized before it was verified against nullptr\. Check lines: 1859, 1862\. registry\.c 1859
* V595 The 'token\_user' pointer was utilized before it was verified against nullptr\. Check lines: 206, 213\. lsa\.c 206
* V595 The 'psp' pointer was utilized before it was verified against nullptr\. Check lines: 2680, 2689\. propsheet\.c 2680
* V595 The 'lpFindInfo' pointer was utilized before it was verified against nullptr\. Check lines: 6285, 6289\. listview\.c 6285
* V595 The 'compiland' pointer was utilized before it was verified against nullptr\. Check lines: 287, 294\. symbol\.c 287
* V595 The 'graphics' pointer was utilized before it was verified against nullptr\. Check lines: 2096, 2112\. graphics\.c 2096
* V595 The 'current' pointer was utilized before it was verified against nullptr\. Check lines: 240, 251\. request\.c 240

### Печать результата одинаковых функций

[V681](https://pvs-studio.ru/ru/docs/warnings/v681/) The language standard does not define an order in which the 'tlb\_read\_byte' functions will be called during evaluation of arguments\. tlb\.c 650

```cpp
...
printf("\\%2.2x \\%2.2x\n", tlb_read_byte(), tlb_read_byte());
...
```

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

### Сомнительные тесты

В директориях некоторых модулей имеется каталог test с исходными файлами для тестов\. Для вывода отладочной информации используется макрос 'ok'\. Вот несколько подозрительных мест:

[V501](https://pvs-studio.ru/ru/docs/warnings/v501/) There are identical sub\-expressions to the left and to the right of the '\=\=' operator: ddsd3\.lpSurface \=\= ddsd3\.lpSurface dsurface\.c 272

```cpp
...
ok(ddsd3.lpSurface == ddsd3.lpSurface,    // <=
  "lpSurface from GetSurfaceDesc(%p) differs\
    from the one returned by Lock(%p)\n",
  ddsd3.lpSurface, ddsd2.lpSurface);      // <=
...
```

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

[V502](https://pvs-studio.ru/ru/docs/warnings/v502/) Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. url\.c 767

```cpp
...
ok(size == no_callback ? 512 : 13, "size=%d\n", size);
...
```

Приоритет оператора "\=\=" выше '?:', поэтому здесь переменная size не сравнивается со значениями 512 и 13\. Выражение всегда истинно, так как будет равно 512 или 13 и значит проверка ничего не проверят\.

Аналогичные предупреждения:

* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. string\.c 1086
* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. string\.c 1111
* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. reader\.c 761
* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. protocol\.c 2928
* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. dde\.c 1594
* V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\=\=' operator\. reader\.c 761

## Результаты проверки в Clang Static Analyzer

Поиск потенциальных ошибок этим анализатором осуществляется путём обхода возможных сценариев выполнения программы\. Если подозрительное место найдено, то анализатор создаст отчёт для этого файла в формате HTML \(по умолчанию\) или PLIST, в котором будут прокомментированы от одного до нескольких десятков шагов, приводящих к подозрительному участку кода\.

Большинство сообщений, полученных в ходе проверки проекта Wine, имеют один и тот же характер: объявляется переменная без инициализации; в функции, в которую передаётся адрес переменной, пропущена инициализация в некоторых ветвях оператора switch, либо выполнен 'return' до инициализации\. Подобные ситуации в дальнейшем не обрабатываются и неинициализированная переменная продолжает использоваться\. Счёт таких мест в проекте идёт на сотни, поэтому в данном обзоре рассмотрены не будут\. Что\-то из них, думаю, может представлять настоящие серьезные ошибки\. Оставим это на совести разработчиков\.

### Неинициализированная переменная в условии

File:  dlls/atl110/\.\./atl/atl\_ax\.c

Location:  line 1092, column 10

Description:  Branch condition evaluates to a garbage value

```cpp
HRESULT
WINAPI AtlAxCreateControlEx(LPCOLESTR lpszName, HWND hWnd,
  IStream *pStream, IUnknown **ppUnkContainer,
  IUnknown **ppUnkControl, REFIID iidSink, IUnknown *punkSink)
{
  ...
  IUnknown *pContainer;
  ...
  hRes = AtlAxAttachControl( pUnkControl, hWnd, &pContainer );
  if ( FAILED( hRes ) ) 
    WARN("cannot attach control to window\n");
  ...
  if ( pContainer ) // <=
  //Clang: Branch condition evaluates to a garbage value
        IUnknown_Release( pContainer );
  return S_OK;
}
```

Неинициализированная переменная pContainer используется в условии после вызова AtlAxAttachControl\. Описание этой функции представлено ниже\. 

```cpp
HRESULT
WINAPI AtlAxAttachControl(IUnknown *control, HWND hWnd,
                          IUnknown **container)
{
  HRESULT hr;
  ...
  if (!control)
    return E_INVALIDARG;// <=
  hr = IOCS_Create( hWnd, control, container );
  return hWnd ? hr : S_FALSE;
}
```

Здесь может быть выполнен возврат значения E\_INVALIDARG до инициализации переменной container\. В результате, функция AtlAxCreateControlEx выдаст предупреждение и продолжит работать с неинициализированной переменной\.

### Возможное переполнение буфера

File:  tools/widl/typegen\.c

Location:  line 1158, column 28

Description:  String copy function overflows destination buffer

```cpp
static unsigned int write_new_procformatstring_type(...)
{
  char buffer[64];
  ...
  strcpy( buffer, "/* flags:" );
  if (flags & MustSize) strcat( buffer, " must size," );
  if (flags & MustFree) strcat( buffer, " must free," );
  if (flags & IsPipe) strcat( buffer, " pipe," );
  if (flags & IsIn) strcat( buffer, " in," );
  if (flags & IsOut) strcat( buffer, " out," );
  if (flags & IsReturn) strcat( buffer, " return," );
  if (flags & IsBasetype) strcat( buffer, " base type," );
  if (flags & IsByValue) strcat( buffer, " by value," );
  if (flags & IsSimpleRef) strcat( buffer, " simple ref," );
  ...
}
```

При выполнении даже не всех условий, есть вероятность получить слишком длинную строку, которая не поместится в буфер\.

### Потенциальная утечка памяти

File:  libs/wpp/ppl\.yy\.c

Location:  line 4475, column 1

Description:  Potential memory leak

```cpp
static void macro_add_arg(int last)
{
  ..
  if(last || mep->args[mep->nargs-1][0])
  {
    yy_push_state(pp_macexp);
    push_buffer(NULL, NULL, NULL, last ? 2 : 1);
    ppy__scan_string(mep->args[mep->nargs-1]);
    //Clang: Calling 'ppy__scan_string'
    //Clang: Returned allocated memory
  }
    //Clang: Potential memory leak
}
```

Функция pyy\_\_scan\_string имеет возвращаемое значение, которое не используется\. Вызов этой функции так или иначе приводит к возврату значения функцией malloc\(\), после использования которой необходимо освобождать память\.

Давайте рассмотрим, как вызов функции pyy\_\_scan\_string  приводит к вызову malloc\.

```cpp
YY_BUFFER_STATE ppy__scan_string (yyconst char * yystr )
{
  return ppy__scan_bytes(yystr,strlen(yystr) );
}

YY_BUFFER_STATE ppy__scan_bytes  (yyconst char * yybytes,
                                  yy_size_t  _yybytes_len )
{
  YY_BUFFER_STATE b;
  char *buf;
  ...
  buf = (char *) ppy_alloc(n  );
  ...
  b = ppy__scan_buffer(buf,n );
  ...
  return b;
}

YY_BUFFER_STATE ppy__scan_buffer  (char * base, yy_size_t size )
{
  YY_BUFFER_STATE b;
    ...
  b=(YY_BUFFER_STATE) ppy_alloc(sizeof(struct yy_buffer_state));
  ...
  return b;
}

void *ppy_alloc (yy_size_t  size )
{
  return (void *) malloc( size );
}
```

### Деление на ноль

File:  dlls/winex11\.drv/palette\.c

Location:  line 601, column 43

Description:  Division by zero

```cpp
#define NB_RESERVED_COLORS 20
...
static void X11DRV_PALETTE_FillDefaultColors(....)
{
  ...
  int i = 0, idx = 0;
  int red, no_r, inc_r;
  ...
  if (palette_size <= NB_RESERVED_COLORS)
    return;
  while (i*i*i < (palette_size - NB_RESERVED_COLORS)) i++;
  no_r = no_g = no_b = --i;
  ...
  inc_r = (255 - NB_COLORCUBE_START_INDEX)/no_r;
  //Clang: Division by zero
  ...
}
```

Код продолжит выполняться если значение переменной palette\_size равно 21 и более\. При значении 21, переменная 'i' в начале будет увеличена на единицу, а затем уменьшена на единицу\. В результате 'i' останется равна нулю, что и приведёт к делению на ноль\.

### Неинициализированный элемент массива

File:  dlls/avifil32/api\.c

Location:  line 1753, column 10

Description:  Assigned value is garbage or undefined

```cpp
#define MAX_AVISTREAMS 8
...
HRESULT WINAPI AVISaveVW(....int nStreams ....)
{
  ...
  //Объявление массива из 8 элементов, [0..7]
  PAVISTREAM     pInStreams[MAX_AVISTREAMS];
  ...
  if (nStreams >= MAX_AVISTREAMS) {
    WARN(...);
    return AVIERR_INTERNAL;
  }
  ...
  //Инициализация первых семи элементов, [0..6].
  for (curStream = 0; curStream < nStreams; curStream++) {
    pInStreams[curStream]  = NULL;
    pOutStreams[curStream] = NULL;
  }
  ...
  for (curStream = 0; curStream < nStreams; curStream++) {
  ...
  if (curStream + 1 >= nStreams) {
    /* move the others one up */
    PAVISTREAM *ppas = &pInStreams[curStream];
    int            n = nStreams - (curStream + 1);

    do {
      *ppas = pInStreams[curStream + 1];
      //Clang: Assigned value is garbage or undefined
    } while (--n);
  }
  ...
  }
...
}
```

Здесь объявляется массив из 8 элементов\. Код продолжит выполняться если значение переменной nStreams меньше 8, т\.е\. максимум 7\.  Все циклы в этой функции, с условным выражением \(curStream < nStreams\) не досчитываются последнего элемента, как при инициализации, так и при использовании\. В месте, где выдал сообщение Clang, как раз берётся восьмой элемент с индексом 7, так как выражение \(curStream \+ 1 \>\= nStreams\) будет истинно при значениях curStream\=\=6 и nStreams\=\=7\.Обращение к массиву pInStreams\[curStream \+ 1\] даст последний неинициализированный ранее элемент\.

### Нулевой путь

File:  dlls/crypt32/rootstore\.c

Location:  line 413, column 10

Description:  Null pointer passed as an argument to a 'nonnull' parameter

```cpp
static BOOL import_certs_from_path(LPCSTR path,
  HCERTSTORE store, BOOL allow_dir)
{
  ...
  fd = open(path, O_RDONLY);
  //Clang: Null pointer passed as
  //an argument to a 'nonnull' parameter
  ...
}
```

Чтобы понять, почему Clang считает, что сюда может прийти NULL, рассмотрим место, где вызывается эта функция:

```cpp
static BOOL import_certs_from_dir(LPCSTR path, HCERTSTORE store)
{
  ...
  char *filebuf = NULL;
  //Clang: 'filebuf' initialized to a null pointer value
  struct dirent *entry;
  while ((entry = readdir(dir)))
  {
    ...
    size_t name_len = strlen(entry->d_name);

    //Вызов функции для изменения filebuf

    if (!check_buffer_resize(&filebuf, &bufsize,
                              path_len + 1 + name_len + 1))
    {
      ERR(...);
      break;
    }
    snprintf(filebuf, bufsize, "%s/%s", path, entry->d_name);
    if (import_certs_from_path(filebuf, store, FALSE) && !ret)
      //Clang: Passing null pointer value via 1st parameter 'path'
      //Clang: Calling 'import_certs_from_path'
      ret = TRUE;
    ...
  }
}
```

Здесь зовётся функция check\_buffer\_resize, в которой должно изменяться значение переменной filebuf или возвращаться FALSE, но функция может не изменить filebuf и вернуть TRUE, рассмотрим код функции ниже:

```cpp
static BOOL check_buffer_resize(char **ptr_buf,
  size_t *buf_size, size_t check_size)
{
  if (check_size > *buf_size)
  {
    ...
    *ptr_buf = CryptMemAlloc(*buf_size);
    ...
  }
  return TRUE;
}
```

Функция содержит только одно условие, в котором изменяется переменная ptr\_buf и в случае невыполнения условия, истинный результат возврата позволяет в дальнейшем использовать эту переменную\.

Похожая ситуация с функцией memcpy\(\):

File:  server/directory\.c

Location:  line 548, column 21

Description:  Null pointer passed as an argument to a 'nonnull' parameter

### Ненадёжная проверка

File:  dlls/advapi32/registry\.c

Location:  line 1209, column 13

Description:  Array access \(from variable 'str'\) results in a null pointer dereference

```cpp
LSTATUS WINAPI RegSetValueExW(...., const BYTE *data, .... )
{
  ...
  if (data && ((ULONG_PTR)data >> 16) == 0)
    //Assuming pointer value is null
    return ERROR_NOACCESS;

  if (count && is_string(type))
  {
    LPCWSTR str = (LPCWSTR)data;
    //Clang: 'str' initialized to a null pointer value
    if (str[count / sizeof(WCHAR) - 1] &&
        !str[count / sizeof(WCHAR)])
    //Clang: Array access (from variable 'str') results in
    //a null pointer dereference
        count += sizeof(WCHAR);
  }
  ...
}
```

Если придёт нулевой указатель data, то программа продолжит выполняться до обращения к переменной str\.

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

File:  dlls/comctl32/comctl32undoc\.c

Location:  line 964, column 12

Description:  Array access \(from variable 'lpDest'\) results in a null pointer dereference

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

Сравниваемые анализаторы PVS\-Studio и Clang Static Analyzer применяют разную методологию анализа кода, следовательно, были получены различные, но неплохие результаты для обоих анализаторов\.

Стоит отметить, что Clang Static Analyzer отличился отсутствием разнообразия в диагностиках\. По сути он предупреждает везде о том, что переменная имеет некорректное значение \(нулевой указатель, ноль, неинициализированная переменная и так далее\)\. В зависимости от значения переменной и как она используется, и формируется диагностическое сообщение\. PVS\-Studio более разнообразен в типах диагностик и хорошо умеет выявлять различные опечатки\.

Я конечно же мог что\-то пропустить в отчетах\. Поэтому просмотр отчётов любых анализаторов самими авторами исходного кода даст лучший результат\.