With the Clang static-analyzer becoming more and more popular these days, MinGW users on Windows might be looking for some way to also bring the Clang goodness to their shores.
However, well, let’s just say that the LLVM documentation isn’t that intuitive for newcomers, especially if you were expecting to be able to download a nice Windows binary package and roll. This quick recipe on setting up a complete Clang static-analyzer environment for MinGW attempts to remedy that. As usual, what is presented below worked on my environment at the time of the post — I make no guarantee that it will work elsewhere, especially if you choose to deviate from the guide.
Also, before you start, please be aware that this whole process will send you back about 11 GB in terms of disk space, so make sure you have adequate room.
- Install MinGW (32 bit!) using the latest msys-get-inst (just run the GUI installer) picking up the very latest packages.
For the installation directory, I usedD:\Clang
, which I will assume to also be yours for for the rest of the guide — please modify accordingly. With regards to the installation packages, selectMinGW Compiler Suite
→C compiler
(which should be selected by default),MinGW Compiler Suite
→C++ compiler
, andMinGW Developer Toolkit
to ensure that binutils and all the necessary tools to build clang, except Python, are available. You should also selectMSYS Basic System
, so that a shell is available. You probably also want to play it safe by picking up the pre-packaged repository catalog for MinGW. The retrieval and installation of the packages will take a few minutes. - Download Python v2.x (32 bit installer) from http://www.python.org/download/.
UseD:\Clang\bin
as the installation directory and unselect everything butPython Interpreter and Libraries
, as this is all you really need from Python.
IMPORTANT: Using python 3.2.2 produced an error on my machine during the build, so make sure you pick v2.x. - [OBSOLETE: You can skip to the next step as per comments below]
Download the latest(Experimental) Clang Binaries for Mingw32/x86
from http://llvm.org/releases/download.html. Extract its content (bin/
,include/
…) intoD:\Clang
, overwriting any existing files. We do not care about headers being overwritten as this setup will be used for Clang and Clang only. Always make surellvm-gcc
is installed in the/bin
directory of MinGW, unless you want to see aCreateProcess()
error when running it (or want to have to duplicate the binutils files which we already got installed from picking up theMinGW Developer Toolkit
). - After installing an Subversion client such as Tortoise-SVN if needed:
- Checkout
llvm
from http://llvm.org/svn/llvm-project/llvm/trunk (this is a Subversion URI), intoD:\Clang\msys\1.0\src\llvm
- Checkout
clang
from http://llvm.org/svn/llvm-project/cfe/trunk, intoD:\Clang\msys\1.0\src\llvm\tools\clang
NB: you may have to checkoutclang
outside ofllvm
first, then move it to the right location if your Subversion client doesn’t let you checkout a repository inside an existing repository.
- Checkout
- Open an msys shell by launching
D:\Clang\msys\1.0\msys.bat
and issue the following commands:cd /src mkdir build cd build export CC=gcc export CXX=g++ ../llvm/configure --disable-docs --enable-optimized --enable-targets=x86,x86_64 --prefix=/mingw make -j2
The
--prefix
ensures that our files will be installed in thebin
directory of MinGW and the two export lines avoid the need to install the Clang binaries to be able to recompile (As per now obsolete Step 3). Note that, in the msys shell,/mingw
always point to the root directory where you installed MinGW (hereD:\clang
) regardless of the name you used. I would strongly advise against using a different directory, or not specifying the--prefix
option (in which case/usr/local
will be used as default), as I ran into issues when I tried (the static-analyzer failed to locate<stdio.h>
when parsing sources). As usual, the-j2
, which specifies the number of jobs to run in parallel, is there to make use of as many cores as we have (here two). Adjust according to your platform.
Oh, and if you want to know why we use--enable-optimized
, see the very end of this post… - Go for a walk or something. Some of the libraries and executables are huge (hundreds of MB in size) so this whole process will take a while. During the various compilation runs I conducted, I chanced in one that froze (one
make
process using 100% of the CPU and never completing) and another where Windows reported"llvm-tblgen has stopped working"
, so don’t be surprised if the compilation doesn’t work quite right first time ’round — it’s fairly heavy on resources. - If the compilation completed, then you can run:
make install -j2
Note that this operation will take some time too!
- Copy all the content of
D:\Clang\msys\1.0\src\llvm\tools\clang\tools\scan-build
, exceptc++-analyzer
toD:\Clang\bin
.
Copy all the content ofD:\Clang\msys\1.0\src\llvm\tools\clang\tools\scan-view
toD:\Clang\bin
. - Issue the command:
ln -s /mingw/bin/ccc-analyzer /mingw/bin/c++-analyzer
The reason we’re re-creating this symbolic link (which on Windows ends up being a duplicate rather than a link anyway) is that the one from the file provided by LLVM only works on UNIX systems.
- Edit
D:\clang\bin\scan-build
, line 100, and remove the/bin
there to have:my $ClangSB = Cwd::realpath("$RealBin/clang");
If you don’t do that,scan-build
will look for clang in/mingw/bin/bin
and obviously fail to locate it. - Finally, since we had to override them, you shouldn’t forget to undefine the
CC
andCXX
environmental variables:export -n CC CXX unset CC CXX
Success looks like what?
$ cd /d/libusb-pbatard $ scan-build ./autogen.sh (...) $ scan-build make make all-recursive make[1]: Entering directory `/d/libusb-pbatard' Making all in libusb make[2]: Entering directory `/d/libusb-pbatard/libusb' CC libusb_1_0_la-core.lo core.c:1626:3: warning: Value stored to 'r' is never read r = 0; ^ ~ 1 warning generated. CC libusb_1_0_la-descriptor.lo CC libusb_1_0_la-io.lo io.c:1819:8: warning: Call to 'malloc' has an allocation size of 0 bytes fds = malloc(sizeof(*fds) * nfds); ^ ~~~~~~~~~~~~~~~~~~~ 1 warning generated. CC libusb_1_0_la-sync.lo CC libusb_1_0_la-poll_windows.lo CC libusb_1_0_la-windows_usb.lo os/windows_usb.c:1218:26: warning: Value stored to 'discdevs' during its initialization is never read struct discovered_devs *discdevs = *_discdevs; ^ ~~~~~~~~~~ os/windows_usb.c:1297:4: warning: Value stored to 'session_id' is never read session_id = 0; ^ ~ 2 warnings generated. RC libusb-1.0.lo CC libusb_1_0_la-threads_windows.lo os/threads_windows.c:115:2: warning: Value stored to 'prev_pos' is never read prev_pos = pos = NULL; ^ ~~~~~~~~~~ 1 warning generated. CCLD libusb-1.0.la Creating library file: .libs/libusb-1.0.dll.a make[2]: Leaving directory `/d/libusb-pbatard/libusb' Making all in doc make[2]: Entering directory `/d/libusb-pbatard/doc' make[2]: Nothing to be done for `all'. make[2]: Leaving directory `/d/libusb-pbatard/doc' Making all in examples make[2]: Entering directory `/d/libusb-pbatard/examples' CC xusb.o xusb.c: In function 'test_mass_storage': xusb.c:443:9: warning: variable 'junk' set but not used [-Wunused-but-set-variable] xusb.c:520:4: warning: Value stored to 'junk' is never read junk = fwrite(data, 1, size, fd); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. CCLD xusb.exe ./.libs/lt-xusb.c:692:11: warning: Value stored to 'len' during its initialization is never read int len = strlen (new_value); ^ ~~~~~~~~~~~~~~~~~~ 1 warning generated. CC lsusb.o CCLD lsusb.exe ./.libs/lt-lsusb.c:692:11: warning: Value stored to 'len' during its initialization is never read int len = strlen (new_value); ^ ~~~~~~~~~~~~~~~~~~ 1 warning generated. make[2]: Leaving directory `/d/libusb-pbatard/examples' make[2]: Entering directory `/d/libusb-pbatard' make[2]: Leaving directory `/d/libusb-pbatard' make[1]: Leaving directory `/d/libusb-pbatard' scan-build: 8 bugs found. scan-build: Run 'scan-view /tmp/scan-build-2011-10-24-1' to examine bug reports.
This is a typical result that Clang will (eventually — the analysis takes some time!) churn through. One false positive (that ‘junk’ variable is meant to be ignored, and only serves to eliminate a warning about not reading the return value from fwrite) and apart from that malloc code, everything looks fairly minor. Even on its very first report from clang, which of course we will act upon, libusb-1.0 on Windows doesn’t appear to fare too bad!
Bonus:
If you followed the guide above, chances are you’ll be using separate installations for the standard MinGW/MinGW-w64 and the Clangified ones. As such, you may want a title on the msys shell window that reflects that you are in a Clang environment, to identify which is which more easily.
To change the msys commandline window title, just edit your D:\clang\msys\1.0\etc\profile
, locate the line:
export PS1='\[\033]0;$MSYSTEM:\w\007
and replace $MSYSTEM
with the title you want.
Bonus 2:
Displayed after spending more than one hour of compilation, using gcc:
llvm[0]: ***** Completed Debug+Asserts Build llvm[0]: ***** Note: Debug build can be 10 times slower than an llvm[0]: ***** optimized build. Use make ENABLE_OPTIMIZED=1 to llvm[0]: ***** make an optimized build. Alternatively you can llvm[0]: ***** configure with --enable-optimized.
Now you tell me!?! Shouldn’t that be the kind of info you want to display after someone issues configure
?
- Is clang a static analyzer?
- How do you run clang-tidy on Windows?
- What is scan-build in C?
- Is clang better than GCC?
- Is clang-tidy good?
- Does clang-tidy compile?
- How do you run clang-tidy in clion?
- What are clang tools?
- How does scan build work?
- Is clang tidy open source?
- What is Flawfinder?
- Does Apple use Clang or GCC?
- Does Clang work on Windows?
- Is Clang replacing GCC?
Is clang a static analyzer?
The Clang Static Analyzer is a source code analysis tool that finds bugs in C, C++, and Objective-C programs. … The analyzer is 100% open source and is part of the Clang project. Like the rest of Clang, the analyzer is implemented as a C++ library that can be used by other tools and applications.
How do you run clang-tidy on Windows?
You can configure Clang-Tidy to run as part of both Code Analysis and build under the Code Analysis > General page in the Project Properties window. Options to configure the tool can be found under the Clang-Tidy submenu.
What is scan-build in C?
scan-build is a command line utility that enables a user to run the static analyzer over their codebase as part of performing a regular build (from the command line).
Is clang better than GCC?
Clang is much faster and uses far less memory than GCC. Clang aims to provide extremely clear and concise diagnostics (error and warning messages), and includes support for expressive diagnostics. GCC’s warnings are sometimes acceptable, but are often confusing and it does not support expressive diagnostics.
Is clang-tidy good?
Cppcheck is a nice and useful static code analyser, but clang-tidy is definitely worth mentioning as an alternative. It is part of the clang toolchain that compiles C ( clang ) and C++ ( clang++ ) code against the LLVM backend.
Does clang-tidy compile?
Clang-tidy always operates on a single file, or rather, translation unit. We can help the tool to figure out the correct compile flags for each translation unit we compile in our project. The most convenient way to run it is with a compile command database.
How do you run clang-tidy in clion?
Provide the path to your custom Clang-Tidy binary in Settings/Preferences | Languages & Frameworks | C/C++ (or click Specify Clang-Tidy executable in the Inspections Settings dialog). This setting is IDE-wide: your custom Clang-Tidy binary will be used for all projects.
What are clang tools?
Clang Tools are standalone command line (and potentially GUI) tools designed for use by C++ developers who are already using and enjoying Clang as their compiler. These tools provide developer-oriented functionality such as fast syntax checking, automatic formatting, refactoring, etc.
How does scan build work?
scan-build has little or no knowledge about how you build your code. It works by overriding the CC and CXX environment variables to (hopefully) change your build to use a «fake» compiler instead of the one that would normally build your project.
Is clang tidy open source?
CodeCompass is an open-source, extensible code comprehension framework which uses LLVM/Clang to analyze and visualize C and C++ projects. … It provides a user interface to execute analysis of C/C++ projects with Clang SA and Clang-Tidy, which outputs are then stored into a database navigable via a web application.
What is Flawfinder?
Flawfinder is a simple program that scans C/C++ source code and reports potential security flaws. It can be a useful tool for examining software for vulnerabilities, and it can also serve as a simple introduction to static source code analysis tools more generally. It is designed to be easy to install and use.
Does Apple use Clang or GCC?
Besides, Apple software use Objective-C, which is a low priority for GCC developers. … In the end, Apple chose to develop Clang, a new compiler front end that supports C, Objective-C and C++.
Does Clang work on Windows?
On Windows, it’s easy to install the Clang tools. Just grab the “Clang compiler for Windows,” an optional component of the “Desktop development with C++” workload. This will install everything you need to develop with Clang on Windows. You can also install your own copy of Clang/LLVM or even build it from source.
Is Clang replacing GCC?
Clang is designed to provide a frontend compiler that can replace GCC.
The Clang Static Analyzer will attempt to compile your C, C++, or Objective
C source code and then check for common programming errors.
Supported File Types¶
The following are supported by this tool:
-
C/C++:
*.c
,*.cc
,*.cpp
,*.cxx
,
*.c++
-
Objective-C/C++:
*.m
,*.mm
Installation¶
This tool requires a modern version of the Clang compiler to be installed on
the system running the Review Bot worker.
On Ubuntu/Debian:
$ sudo apt-get install clang clang-tools
On macOS, install the XCode command line tools.
On other Linux distributions or operating systems, you may need to follow the
documentation for your system or check your package manager.
Alternatively, you can attempt to download Clang and install it manually.
Configuration¶
Clang Location¶
If the clang command is in a non-standard location, and can’t
be found in Review Bot’s PATH
environment variable, then you’ll
need to specify the path in the
Review Bot worker config file:
exe_paths = { 'clang': '/path/to/clang', }
You will need to restart the Review Bot worker after making this change.
Enabling Full Repository Access¶
This tool requires full repository access, which is available for Git and
Mercurial repositories. Each repository you intend to use must be configured
in the Review Bot worker config file.
See Full Repository Access for instructions.
Preparing Your Build Environment¶
Because C, C++, and Objective C source code often requires numerous external
dependencies or compile-time flags, the tool configuration allows you to
specify additional command line arguments that will be passed to
clang.
It’s recommended that you set up the worker on a system which is already set
up to build your software in order to ensure that the necessary build
environment is available.
Enabling Clang in Review Board¶
First, you’ll need to add a Review Bot configuration in Review Board (see
Tool Configurations).
The following configuration options are available:
- Clang command-line arguments (optional):
-
Additional command line arguments to pass to clang -S —analyze.
You may want to use this if you need to set specific include paths or
options needed to build your software.Note that
-ObjC
or-ObjC++
will be passed automatically if working
with Objective-C/C++ code.
Время на прочтение14 мин
Количество просмотров22K
В статье хочу рассказать о проверке проекта Wine такими статическими анализаторами C/C++ кода, как PVS-Studio и Clang Static Analyzer.
Wine
Wine (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 — статический анализатор, выявляющий ошибки в исходном коде приложений на языке C/C++/C++11. Для проверки проекта использовалась релиз-версия PVS-Studio 5.18.
- Clang Static Analyzer — статический анализатор, который находит ошибки в C, C++ и Objective-C программах. Для проверки проекта использовалась релиз-версия Clang 3.4.2 для openSUSE 13.1.
Результаты проверки в PVS-Studio
Сдвиг отрицательного числа
V610 Undefined behavior. Check the shift operator ‘<<. The left operand ‘(LONGLONG) — 1’ is negative. propvar.c 127
...
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;’, т.е. является знаковым типом. Сдвиги отрицательных чисел по новому стандарту приводят к неопределённому или неуточненному поведению. Почему такой код может работать и как его лучше исправить, можно прочитать в статье: Не зная брода, не лезь в воду. Часть третья.
Опечатки
V501 There are identical sub-expressions ‘!lpScaleWindowExtEx->xNum’ to the left and to the right of the ‘||’ operator. enhmetafile.c 1418
...
if (!lpScaleWindowExtEx->xNum || !lpScaleWindowExtEx->xDenom ||
!lpScaleWindowExtEx->xNum || !lpScaleWindowExtEx->yDenom)
break;
...
В условии два раза проверяется lpScaleWindowExtEx->xNum, скорее всего в одном месте должно быть lpScaleWindowExtEx->yNum. В объявлении используемой структуры такое поле есть:
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
....
for(i = 1; i < count; i++){
....
if((i + 2 >= count) ||
!(types[i + 1] & PathPointTypeBezier) ||
!(types[i + 1] & PathPointTypeBezier)){
....
}
i += 2;
}
....
Это место обнаружено аналогичной диагностикой V501, но причина одинаковых условий тут не так очевидна. Скорее всего, в одном условии должно быть types[i + 2], потому что выше проверили возможность обратиться к элементу массива с индексом на 2 больше, чем ‘i’.
V593 Consider reviewing the expression of the ‘A = B != C’ kind. The expression is calculated as following: ‘A = (B != C)’. request.c 3354
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 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
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 The ‘then’ statement is equivalent to the ‘else’ statement. filedlg.c 3302
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
...
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 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
...
section[strlen(section)] = '\0'; /* remove last backslash */
...
На самом деле ноль запишется в ноль и ничего не изменится. Для работы функции strlen() строка уже должна заканчиваться терминальным нулём. Комментарий намекает нам, что, наверное, хотели отрезать косую черту в конце. Тогда, видимо код должен быть такой:
section[strlen(section) - 1] = '\0';
Один счётчик на два цикла
V535 The variable ‘i’ is being used for this loop and for the outer loop. Check lines: 980, 1003. iphlpapi_main.c 1003
...
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 Type casting operation is utilized 2 times in succession. Next, the ‘+’ operation is executed. Probably meant: (T1)((T2)a + b). typelib.c 9147
...
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
INT WINAPI
EnumProtocolsW(LPINT protocols, LPVOID buffer, LPDWORD buflen)
{
...
unsigned int string_offset;
...
pi[i].lpProtocol = (WCHAR*)(char*)buffer + string_offset;//<==
...
}
Разность беззнаковых чисел
V555 The expression ‘This->nStreams — nr > 0’ will work as ‘This->nStreams != nr’. editstream.c 172
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 The ‘decl’ pointer was utilized before it was verified against nullptr. Check lines: 1411, 1417. parser.y 1411
...
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 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
...
printf("\\%2.2x \\%2.2x\n", tlb_read_byte(), tlb_read_byte());
...
Согласно стандарту языка Си++, не определена последовательность вычисления фактических аргументов функции. Какая функция будет вызвана первой, зависит от компилятора, параметров компиляции и так далее.
Сомнительные тесты
В директориях некоторых модулей имеется каталог test с исходными файлами для тестов. Для вывода отладочной информации используется макрос ‘ok’. Вот несколько подозрительных мест:
V501 There are identical sub-expressions to the left and to the right of the ‘==’ operator: ddsd3.lpSurface == ddsd3.lpSurface dsurface.c 272
...
ok(ddsd3.lpSurface == ddsd3.lpSurface, //<==
"lpSurface from GetSurfaceDesc(%p) differs\
from the one returned by Lock(%p)\n",
ddsd3.lpSurface, ddsd2.lpSurface); //<==
...
Очень похоже на опечатку. Скорее всего здесь должны сравниваться те же переменные, которые выводятся на печать.
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
...
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
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. Описание этой функции представлено ниже.
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
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
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.
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
#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
#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
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, рассмотрим место, где вызывается эта функция:
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, рассмотрим код функции ниже:
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
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 более разнообразен в типах диагностик и хорошо умеет выявлять различные опечатки.
Я конечно же мог что-то пропустить в отчетах. Поэтому просмотр отчётов любых анализаторов самими авторами исходного кода даст лучший результат.
Эта статья на английском
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. Checking Wine with PVS-Studio and Clang Static Analyzer.
With software development becoming increasingly complex, ensuring code quality and security is essential.
In languages such as C and C++, which offer high performance but also require special care due to manual memory management and the possibility of subtle errors, the use of static analyzers has become an indispensable practice.
These tools help identify bugs, vulnerabilities and style issues even before the program is executed, saving time, effort and, most importantly, avoiding critical failures in production.
In this article, we present a list of 10 of the best static analyzers available for C and C++, highlighting their main features, advantages and usage scenarios. Whether you are an experienced developer or someone who is just starting out, knowing these tools will be an important differentiator to improve the quality of your code and reduce development risks.
1. CppCheck
- Description: A free and popular static analyzer, focused on identifying specific C++ errors and bad programming practices.
- Highlights:
- Detects memory leaks, dead code and other common problems.
- Support for integration with IDEs such as Visual Studio and CLion.
- Extensible via custom configurations.
Address: https://github.com/danmar/cppcheck
2. IKOS (Inferencing Kernel for Open Static Analysis)
- Description: Static analysis tool developed by NASA to find bugs in C/C++ code using abstract interpretation. — Highlights:
- Focus on security and mathematical precision analysis.
- Detects possible division by zero problems, buffer overflows, among others.
- Open-source tool.
Address: https://github.com/NASA-SW-VnV/ikos
3. Clang Static Analyzer
- Description: An analyzer integrated into the Clang compiler to find problems at compile time.
- Highlights:
- Easy to use with the
scan-build
command. - Detects problems such as memory leaks and out-of-bounds accesses.
- Good integration with projects using LLVM/Clang.
Address: https://clang.llvm.org/docs/ClangStaticAnalyzer.html
4. SonarQube
- Description: A widely used tool for code quality and security analysis, with support for C++.
- Highlights:
- Identifies vulnerabilities, code duplication, and complexity.
- Supports continuous analysis via CI/CD integration.
- Offers both a free and commercial version.
Address: https://www.sonarsource.com/products/sonarqube/
5. PVS-Studio
- Description: A commercial static analyzer that offers advanced support for C++ with a focus on quality and security.
- Highlights:
- Detects hundreds of error classes, including portability and performance issues.
- Integration with multiple IDEs (Visual Studio, CLion, etc.).
- Detailed reports with error explanations.
Address: https://pvs-studio.com/en/pvs-studio/
6. Coverity Scan
- Description: A tool widely used in commercial and open-source projects to detect bugs and vulnerabilities.
- Highlights:
- Detects problems such as memory leaks and buffer overflows.
- Supports continuous analysis.
- Free for open-source projects.
Address: https://scan.coverity.com/
7. Flawfinder
- Description: Focused on security, it helps identify vulnerabilities in C/C++ code.
- Highlights:
- Lightweight and easy to use tool.
- Detects potentially insecure code patterns.
- Simple and clear reports.
Address: https://github.com/david-a-wheeler/flawfinder
8. OCLint
- Description: A static analyzer focused on detecting quality and style issues in code.
- Highlights:
- Detects redundant code, antipatterns and complexity issues.
- Good integration with tools like CMake.
- Open source tool.
Address: https://github.com/oclint/oclint
9. Infer
- Description: Developed by Facebook, it is a static analyzer that finds problems before execution.
- Highlights:
- Ideal for detecting problems at compile time, such as race conditions and incorrect memory usage.
- Integration with CI/CD.
- Support for multiple languages, including C++.
Address: https://fbinfer.com/
10. EsLint (with C++-specific plugins)
- Description: Although originally for JavaScript, with plugins like
eslint-plugin-cpp
, it is possible to use it to check the style and quality of C++ code. - Highlights:
- Customizable and extensible checking.
- Good integration with modern editors.
- Useful for style and readability analysis.
Address: https://learn.microsoft.com/pt-br/cpp/ide/cpp-linter-overview?view=msvc-170
Comparison and Choice
- Free and popular: CppCheck, IKOS, Clang Static Analyzer.
- Commercial and advanced: PVS-Studio, Coverity Scan.
- Security-focused: Flawfinder, IKOS.
- Integrated into overall quality: SonarQube, OCLint.
cpp
cppdaily
clanguage