General Analysis

The PVS-Studio product contains a set of general-purpose static analysis rules intended to detect a wide range of various defects in C/C++/C++11 applications.

The general-purpose rule set lets you detect logical errors, misprints, code fragments causing access violation, incorrect use of STL library algorithms, etc.

When choosing rules to implement in PVS-Studio, we proceed from their relevance first of all. We consciously refuse to implement such standards as MISRA since most of recommendations described there are of little help to developers using contemporary tools such as Visual Studio and such libraries as STL, MFC, Qt and Boost. In PVS-Studio, we try to implement diagnoses that consider not only C/C++ language's specifics but higher-level patterns and interaction of code and libraries as well. Many checks are unique and have not been implemented in analyzers familiar to us.

Error patterns detected by PVS-Studio are very diverse. So we will further demonstrate only some types of errors the tool can detect. If you want to learn about other types of errors the analyzer detects, please refer to the documentation. The samples of errors given below are taken from real projects.

An error occurring when "+1" is written in a wrong place

if ((t=(char *)realloc(next->name, strlen(name+1))))
{
  next->name=t;
  strcpy(next->name, name);
}

The analyzer found an error here that had been made through inattention. The program allocates 2 bytes less memory than it actually needs. To correct the error, you should take "+1" out of the parentheses. This is the correct code: "realloc(next->name, strlen(name) + 1)".

An incompletely cleared array

#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
memset(_iContMap, -1, CONT_MAP_MAX);

This error refers to the buffer underrun / overrun class. Such errors occur when an array is processed only partly or, on the contrary, memory outside the array is modified. In this sample, the memset() function clears only some little part of the _iContMap array. The error here is that sizeof() is missing:

memset(_iContMap, -1, CONT_MAP_MAX * sizeof(_iContMap[0]));

Pointer dereferencing is missing

if (pSlash != NULL) {
  pSlash++;
  if (pSlash != '\0') {
    ...

The analyzer usually helps detect suspicious pointer handling as, for instance, in the following sample. Here a pointer is compared to 0. If the pointer does not equal 0, it is incremented by one and again compared to 0. These are very strange operations. They certainly contain an error because a mere pointer dereferencing operation is missing: "if (*pSlash != '\0') {".

An incorrect loop

int i, destlen = 0, l, k;
for (i=0; i<srclen; i++)
{
  ...
  for (k=i; i<srclen; k++)
  {
    if (src[k]=='>')
      break;
  }
  i = k;
}

The analyzer found a misprint in this code: it is the "i" variable instead of "k" which is used for comparison in the nested loop. This code is very likely to cause memory access outside the array being processed.

Misprints in variables' names

class CSize : public SIZE
{
  ...
  CSize(POINT pt) { cx = pt.x; cx = pt.y; }

This error was detected in a project where the programmer implemented his own class CSize. Due to a misprint, one and the same variable is assigned different values in turn. This attracted the analyzer's attention. Here is the correct code of the second assignment operation: "cy = pt.y;".

Incorrect use of Windows API

OPENFILENAME lofn;
...
lofn.lpstrFilter = L"Equalizer Preset (*.feq)\0*.feq";

There are structures in Windows API where string-pointers must end with a double zero. In this sample, the lpstrFilter member in the OPENFILENAME structure is such a pointer. This code will cause generating trash in the filter field in the file dialogue. This is the correct code:

lofn.lpstrFilter = L"Equalizer Preset (*.feq)\0*.feq\0";

We wrote 0 manually at the end of the line while the compiler will add one more zero.

Error of handling standard algorithms

All the samples we have discussed above are low-level errors. Now let's look at checks implemented in PVS-Studio that handle higher-level notions such as code blocks or algorithms.

void unregisterThread() {
  Guard<TaskQueue> g(_taskQueue);
  std::remove(_threads.begin(), _threads.end(), ThreadImpl::current());
}

The std::remove function does not remove items from the container. It only shifts the items and returns the iterator to the beginning of the trash. Assume we have the vector<int> container that contains items 1,2,3,1,2,3,1,2,3. If we execute the code "remove( v.begin(), v.end(), 2 )", the container will contain items 1,3,1,3,?,?,? where ? is some trash. Also, the function will return the iterator to the first trash item, so if we want to remove these trash items, we must write the following code: "v.erase(remove(v.begin(), v.end(), 2), v.end())". This is the correct code:

auto trash = std::remove(_threads.begin(), _threads.end(),
                         ThreadImpl::current());
_threads.erase(trash, _threads.end());

An error of writing code by Copy-Paste method

void KeyWordsStyleDialog::updateDlg() 
{
  ...
  Style & w1Style =
    _pUserLang->_styleArray.getStyler(STYLE_WORD1_INDEX);
  styleUpdate(w1Style, _pFgColour[0], _pBgColour[0],
    IDC_KEYWORD1_FONT_COMBO, IDC_KEYWORD1_FONTSIZE_COMBO,
    IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK,
    IDC_KEYWORD1_UNDERLINE_CHECK);

  Style & w2Style =
    _pUserLang->_styleArray.getStyler(STYLE_WORD2_INDEX);
  styleUpdate(w2Style, _pFgColour[1], _pBgColour[1],
    IDC_KEYWORD2_FONT_COMBO, IDC_KEYWORD2_FONTSIZE_COMBO,
    IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK,
    IDC_KEYWORD2_UNDERLINE_CHECK);

  Style & w3Style =
    _pUserLang->_styleArray.getStyler(STYLE_WORD3_INDEX);
  styleUpdate(w3Style, _pFgColour[2], _pBgColour[2],
    IDC_KEYWORD3_FONT_COMBO, IDC_KEYWORD3_FONTSIZE_COMBO,
    IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK,
    IDC_KEYWORD3_UNDERLINE_CHECK);

  Style & w4Style =
    _pUserLang->_styleArray.getStyler(STYLE_WORD4_INDEX);
  styleUpdate(w4Style, _pFgColour[3], _pBgColour[3],
    IDC_KEYWORD4_FONT_COMBO, IDC_KEYWORD4_FONTSIZE_COMBO,
    IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK,
    IDC_KEYWORD4_UNDERLINE_CHECK);
  ...
}

It is very difficult to notice an error in this code. But PVS-Studio is patient and pedantic: "V525: The code containing the collection of similar blocks. Check items '7', '7', '6', '7' in lines 576, 580, 584, 588". Let's abridge the code to make the error more visible:

styleUpdate(...
  IDC_KEYWORD1_BOLD_CHECK, IDC_KEYWORD1_ITALIC_CHECK,
  ...);
styleUpdate(...
  IDC_KEYWORD2_BOLD_CHECK, IDC_KEYWORD2_ITALIC_CHECK,
  ...);
styleUpdate(...
  IDC_KEYWORD3_BOLD_CHECK, IDC_KEYWORD3_BOLD_CHECK,
  ...);
styleUpdate(...
  IDC_KEYWORD4_BOLD_CHECK, IDC_KEYWORD4_ITALIC_CHECK,
  ...);

This code was most likely written by the Copy-Paste method, which resulted in using IDC_KEYWORD3_BOLD_CHECK instead of IDC_KEYWORD3_ITALIC_CHECK. The numbers you see in the diagnostic message arise from macros like these:

#define IDC_KEYWORD1_ITALIC_CHECK (IDC_KEYWORD1 + 7)
#define IDC_KEYWORD3_BOLD_CHECK (IDC_KEYWORD3 + 6)

The last sample is especially significant because it demonstrates that the PVS-Studio analyzer processed a whole large code fragment simultaneously, detected repetitive structures in it and considered this code potentially dangerous relying on heuristic methods.

Advantages of using general-purpose static analysis

What is most valuable in static analysis is that you can detect some errors at the very early development stages. The earlier an error is detected, the easier, quicker and cheaper it is to eliminate it. This reduces costs at the stage of testing and maintenance.

A static analyzer handles source code and examines all the possible ways of program execution. Therefore, you may find errors in code which is used rarely or in code which you intend to use at the next stages of the project.

PVS-Studio is a contemporary analyzer without a heavy burden of the past. It is actively developing and learning to detect new patterns of errors that programmers using Microsoft Visual C++ now encounter. You may contribute to this sphere and offer us diagnostic rules you have invented by yourself. Send us your suggestions using the feedback page.