PVS-Studio and the hostile environment

    hostile habitat
    Another story is how difficult it is for programs to interact with the outside world. At first glance, a static analyzer should not have any problems. It receives input files, additional information and must generate a report. But as always, the devil is in the details.

    I consider PVS-Studio a very high-quality product. We can make and lay out the distribution kit almost any day. We use a very large number of automated tests of various levels and types. Here is a description of some of them: " How we test the code analyzer ." Now there are more of them. For example, now for static analysis we use not only our own analyzer, but also Clang. If the corrected version has passed all the tests, then it can be safely issued to users.


    Unfortunately, all the beauty and reliability of the internal code sometimes falls apart due to the effects of a hostile environment. As a result, the whole impression of the product deteriorates. It seems that we are not to blame, but our product does not work. I can give a large number of examples. The first thing that comes to mind:
    • Third-party add-in spoils something in the Visual Studio environment. As a result, you have to create a crutch to work around the problem or come to terms and answer "sorry, we can’t do anything." One such example is " Description of the Intel Parallel Studio Service Pack 1 Integration Error in Visual Studio 2005/2008 ."
    • Visual Studio COM interfaces for project information may unexpectedly throw an exception. Apparently, the environment at this unfortunate moment is busy with something else. Calls have to be wrapped in a cycle for their repeated repetition. Dances with a tambourine that do not always help.
    • The developer has antivirus X and, according to corporate policy, he does not have rights to change his settings. This antivirus “holds” some temporary files for some time, and the analyzer cannot delete them. As a result, the analyzer “craps” into the project folder.
    • Miscellaneous can be remembered. Some examples can be seen here , here and here .

    Now I’ll tell you another such story, how easy it is to spoil the impression of our product, being innocent.

    One of the potential users sent a question about the strange behavior of PVS-Studio:

    We are now chasing the trial version and are considering buying the full one. However, during the analysis we came across one nuance that casts doubt on the validity of the conclusions.

    Below is a screenshot that shows an error.

    filePath and cachePath are marked as unused (warning V808 ), although it can be seen that they are used literally on the next line after the declaration.

    Could you explain the similar behavior of the analyzer?

    In the screenshot you can see the code of the following form (code changed):
    std::string Foo()
    {  
      std::string filePath(MAX_PATH + 1, 0);
      std::string cachePath = "d:\\tmp";
      if (!GetTempFileName(cachePath.c_str(), "tmp", 0,
                           &filePath.front()))
        throw MakeSystemError("...", GetLastError(), __SOURCE__);
      return std::move(filePath);
    }

    Well what to say. Shame on the analyzer. After all, it really says stupidity. The variables filePath and cachePath are explicitly used. There is no reason for warning. And well, the function would be 1000 lines long. No, the function is simple for disgrace.

    Everything, the first impression is spoiled. Now I’ll talk about the results of the investigation.

    The PVS-Studio analyzer uses the Visual C ++ compiler (CL.exe) or Clang to preprocess files. More details about how we use Clang are described in the note: " A bit about the interaction of PVS-Studio and Clang ."

    The Visual C ++ compiler preprocessor works well, but it is extremely slow. Clang is fast, but much does not support or is not working properly. Clang developers claim to be very well compatible with Visual C ++, but this is not true. There are a lot of little things that they do not support or do differently from Visual C ++. For the analyzer, these little things are fatal, as happened this time.

    By default, the PVS-Studio analyzer at the beginning tries to preprocess the file using Clang. However, he knows: Clang is far from always able to preprocess what Visual C ++ can. If a preprocessing error occurs, then CL.exe is launched. This takes a little time to uselessly launch Clang, but overall, this approach saves a lot of time getting * .i files.

    In this case, it did not help. Clang "successfully" preprocessed the file, although the output was abracadabra.

    The reason for the incorrect behavior was the __SOURCE__ macro, declared in the code as follows:
    #define __SLINE_0__(_line) #_line
    #define __SLINE__(_line) __SLINE_0__(_line)
    #define __SOURCE__ __FILE__":"__SLINE__(__LINE__)

    When preprocessing a string:
    throw MakeSystemError(_T("GetTempFileName"), GetLastError(),
                          __SOURCE__);

    It should turn into:
    MakeSystemError("GetTempFileName", GetLastError(),
                    "..path.."":""37");

    This is exactly what the Visual C ++ compiler does. Everything is fine. The analyzer will correctly process this code.

    If you explicitly set PVS-Studio in the settings to always use CL.exe, then false positives will disappear. However, if Clang is launched, the analyzer will deal with incorrect code.

    Clang cannot overpower macros, and at the output we have:
    throw MakeSystemError("GetTempFileName", GetLastError(),
                          "..path.."":"__SLINE__(37));

    The __SLINE__ macro remained undisclosed.

    The result was an incorrect design, unacceptable from the point of view of the C ++ language. Having met it, the PVS-Studio analyzer tries to somehow bypass the incorrect code in order to continue the analysis further. Better to skip something than not completely process the file. Often, such omissions do not affect the results of the analysis.

    But this time, it was not painless to get around the wrong place. The whole block was thrown:
    if (!GetTempFileName(cachePath.c_str(), "tmp", 0, &filePath.front()))
      throw MakeSystemError("....", GetLastError(), __SOURCE__);
    return std::move(filePath); 

    It just so happened ... The analyzer did everything it could.

    Since this fragment does not exist for the analyzer, the variables are not initialized, are not used in any way. This leads to the issuance of a false positive.

    One solution to the problem is to always use a preprocessor from Visual C ++. But there is a drawback - a slow analysis.

    Therefore, in this case, we have chosen a different path. Since the company from which we were contacted is considering purchasing PVS-Studio, we examined this particular case and made another backup in the code. It is ugly, but practical. We already have many different special places that bypass some nuances encountered in the projects of our users. This is a form of support.

    So, this time we were let down by a preprocessor implemented in Clang. It is interesting what will be the reason to write the following similar article about external errors.

    That is how we live. Thanks for attention.

    Try our static code analyzer on your projects and, if something goes wrong, write to us . Often we turn a negative attitude into a positive one.

    Also popular now: