Let the world tremble! We've released PVS-Studio 4.00 with a free general-purpose analyzer!


Published on

Programmers, meet a new tool to search for errors in source code of software written in C/C++. Within the scope of the PVS-Studio analyzer, we implemented a new set of general-purpose rules. This functionality is free for now.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Let the world tremble! We've released PVS-Studio 4.00 with a free general-purpose analyzer!

  1. 1. Let the world tremble! Weve releasedPVS-Studio 4.00 with a free general-purpose analyzer!Author: Andrey KarpovDate: 01.12.2010Programmers, meet a new tool to search for errors in source code of software written in C/C++. Withinthe scope of the PVS-Studio analyzer, we implemented a new set of general-purpose rules. Thisfunctionality is free for now.You may download PVS-Studio here http://www.viva64.com/en/pvs-studio-download/.The article briefly describes new features of PVS-Studio and demonstrates the usage of new diagnosticcapabilities with the example of static analysis of the TortoiseSVN projects source code.PVS-Studio is a state-of-the-art source code analyzer integrated into Microsoft Visual Studio2005/2008/2010 environment. The analyzer lets you conveniently handle the warning list and enablesmulti-core processing for analysis. PVS-Studio is meant for developers of modern Windows-software inC/C++/C++0x.Until now, PVS-Studio included two rule sets. The first was intended for detecting issues in 64-bitsoftware while the second was intended for detecting issues in parallel software based on the OpenMPtechnology.Now our analyzer has a third universal set of rules that allows detecting various errors in code. This ruleset is free and may be used without any restrictions. We cannot say whether this set will become paid ornot in future since we are only starting our way in the sphere of general-purpose static analysis.Currently you may study the new set of rules by downloading PVS-Studio 4.00 BETA. We will be glad toreceive feedback from you concerning bugs and your wishes of how we could improve the tool. I wouldlike to note it right away that we implemented only 50 general-purpose rules for a start. It is not toomuch, so if you fail to find any interesting issues in your code after you have downloaded and tried PVS-Studio, please do not jump to conclusions. We suggest that you try PVS-Studio in future when the set of
  2. 2. diagnoses is greatly extended. We intend to significantly enlarge the base of diagnostic rules soon (if wehave enough health and luck) luck).Let us demonstrate the use of PVS e PVS-Studios new rule set by the example of TortoiseSVN TortoiseSVN is TortoiseSVN.a client for the Subversion version control system implemented as a Windows plugin. TortoiseSVN iswell known by many developers, so I think there is no need to describe this application in detail. I only thinkwant to note that TortoiseSVN was acknowledged as the best project in the category "tools and utilitiesfor developers" in year 2007 on SourceForge.net SourceForge.net.Step 1Download PVS-Studio from OOO "Program Verification Systems" companys site ( Studio s (thats us). I hope youwill appreciate that you do not have to fill in any forms or solve captchas Simply download the tool. captchas. implyStep 2Install PVS-Studio. Do not hesitate to c click the "Next" button because you do not have to set anything.The PVS-Studio package is signed with a digital signature. However, some antiviruses might get alertedabout integration of PVS-Studio into Visual Studio. So you should allow any activity if asked by your Studioantivirus.Step 3Download the package of source code of the TortoiseSVN project. We employed version 1.6.11 ofsource code.Step 4Open the TortoiseSVN project and launch the analysis by choosing the Check Solution command in thePVS-Studio menu.Step 5The analyzer will think a bit (the TortoiseSVN project is rather complex and includes a lot of files), so do thenot perform any actions and just wait for a while. The progress dialogue will appear soon and the
  3. 3. analysis will start. The analysis speed depends upon the number of processor cores in your computer. If analysisPVS-Studio consumes too many resources, you may restrain its appetite in the settings Studio settings.The analyzer generates messages in its own window that has controls for enabling/disabling differenttypes of messages. We will use these capabilities because we are not interested in a large number of ypeserrors related to 64 bits now. Besides the 64-bit analysis module is charged and therefore is shipped in Besides,the trial mode (for more detailed information about the trial mode, go here).In the window, you may see a group of three buttons responsible for displaying messages of the threerule sets.1) 64 is responsible for displaying diagnostic messages about 64-bit issues (Viva64);2) MP shows diagnostic messages about parallel defects (VivaMP);3) GA shows the General Analysis diagnostic messages messages.Now we are interested only in the general analysis rule set Uncheck the other buttons and the set.unnecessary messages will be also hidden in the list list.Wait for the analysis to finish finish.Step 6Analysis is over and we may see the list of all the fragments in the program where code review is nalysisnecessary.
  4. 4. All the warnings are arranged according to 3 priority levels (this is a new feature in PVS-Studio 4.00).Usually you have to review only messages of the 1-st and 2-nd levels. PVS-Studio 4.00 BETA generated33 warnings of the 1-st level, 14 warnings of the 2-nd level and 8 warnings of the 3-rd level.Youd better start examining the messages with the first level warnings. So you may uncheck the buttonresponsible for displaying messages of the second level. The third level is disabled by default.Step 7Lets examine interesting code fragments the analyzer has found.Case 1In the beginning, there are two messages at once that refer to the same function. I hope that thisfunction is not used too often in the code.V530 The return value of function empty is required to be utilized. contextmenu.cpp 434V530 The return value of function remove is required to be utilized. contextmenu.cpp 442STDMETHODIMP CShellExt::Initialize(....){ ... ignoredprops.empty(); for (int p=0; p<props.GetCount(); ++p) { if (props.GetItemName(p). compare(SVN_PROP_IGNORE)==0) { std::string st = props.GetItemValue(p); ignoredprops = UTF8ToWide(st.c_str()); // remove all escape chars () std::remove(ignoredprops.begin(), ignoredprops.end(), ); break; } } ...
  5. 5. }Here and further we will give only brief comments on code fragments. To learn more why these codefragments are considered unsafe, refer to PVS-Studios online-documentation (in Russian or English).The PVS-Studio distribution package also includes documentation in the pdf format (it is absolutely thesame as the online-documentation). Further we will give links to the corresponding descriptions of thediagnostic messages.The V530 message warns us that "ignoredprops.empty()" does not clear the string at all while"std::remove()" will never remove the characters.Case 2Here it is checked whether a variable of the char type is above or equal to value 0x80.V547 Expression c >= 0x80 is always false. The value range of signed char type: [-128, 127].pathutils.cpp 559CString CPathUtils::PathUnescape (const char* path){ // try quick path size_t i = 0; for (; char c = path[i]; ++i) if ((c >= 0x80) || (c == %)) { // quick path does not work for // non-latin or escaped chars std::string utf8Path (path); CPathUtils::Unescape (&utf8Path[0]); return CUnicodeUtils::UTF8ToUTF16 (utf8Path); } ...}The V547 message tells you that such a check is meaningless. A char-value is always below 0x80, so thecondition above is always false. Perhaps it is because of this error that developers left the comment"quick path does not work for non-latin or escaped chars". Surely it does not, but not because of thecode failing to convert the string: when it encounters a non-latin character, we simply cannot get insidethe if operators body.Case 3
  6. 6. Many threads are spawned and terminated by functions CreateThread/ExitThread. Therefore we riskquickly overflowing a threads stack or failing to release some resources when a thread is terminated.For more information, refer to the V513 warnings description. It is much safer to use functions_beginthreadex() and _endthreadex() for these purposes.There is no need to give the code sample here, and the text of all the messages is the same:V513 Use _beginthreadex/_endthreadex functions instead of CreateThread/ExitThread functions.crashhandler.cpp 379Case 4It refers to TortoiseSVN companion utilities. It is highly probable that when you handle CrashLog, youwill encounter another Crash.V510 The printf_s function is not expected to receive class-type variable as fourth actual argument.excprpt.cpp 199string CExceptionReport::getCrashLog(){ ... _tprintf_s(buf, _T("%s%s.xml"), getenv("TEMP"), CUtility::getAppName()); ...}The V510 message warns you that it is a bad thing to pass an argument of the std::string type into theprintf_s function. But it is std::string that the CUtility::getAppName() function returns. The error here isthat the programmer forgot to write ".c_str()". This may result either in an incorrect data output orprogram crash.Case 5The developers intended to clear an array here but failed.V530 The return value of function empty is required to be utilized. mailmsg.cpp 40CMailMsg& CMailMsg::SetFrom(string sAddress, string sName){ if (initIfNeeded()) { // only one sender allowed if (m_from.size())
  7. 7. m_from.empty(); m_from.push_back(TStrStrPair(sAddress,sName)); } return *this;}Again, the V530 message tells us that it is "empty()" which is accidentally written instead of "clear()".Case 6In the SetTo() function, the developers also failed to clear an array.V530 The return value of function empty is required to be utilized. mailmsg.cpp 54CMailMsg& CMailMsg::SetTo(string sAddress, string sName){ if (initIfNeeded()) { // only one recipient allowed if (m_to.size()) m_to.empty(); m_to.push_back(TStrStrPair(sAddress,sName)); } return *this;}Case 7Of course the analyzer generates false alarms as well. For instance, this code fragment from the zliblibrary is included into the TortoiseSVN project. There is no error here but it is rather helpful to marksuch a fragment with the V501 warning.V501 There are identical sub-expressions to the left and to the right of the - operator: size - size zutil.c213voidpf zcalloc (opaque, items, size) voidpf opaque; unsigned items; unsigned size;
  8. 8. { /* make compiler happy */ if (opaque) items += size - size; return (voidpf)calloc(items, size);}Of course the compiler feels happy here, but the subtraction operation looks suspicious.Case 8There are other grey areas concerning encodings. Here is one more condition which is always false.V547 Expression * utf8CheckBuf >= 0xF5 is always false. The value range of signed char type: [-128,127]. tortoiseblame.cpp 312BOOL TortoiseBlame::OpenFile(const TCHAR *fileName){ ... // check each line for illegal utf8 sequences. // If one is found, we treat // the file as ASCII, otherwise we assume // an UTF8 file. char * utf8CheckBuf = lineptr; while ((bUTF8)&&(*utf8CheckBuf)) { if ((*utf8CheckBuf == 0xC0)|| (*utf8CheckBuf == 0xC1)|| (*utf8CheckBuf >= 0xF5)) { bUTF8 = false; break; } ... }
  9. 9. By the way, conditions "*utf8CheckBuf == 0xC0" and "*utf8CheckBuf == 0xC1" are always false too. Thatis why the code "bUTF8 = false;" will never get control. The fact that the PVS-Studio analyzer kept silentabout the "*utf8CheckBuf == 0xC0" expression is its drawback. We have noted it and will make theanalyzer to scold this issue in the next version.Case 9The next message is not so simple: in theory we have an error but in practice everything works well.V507 Pointer to local array stringbuf is stored outside the scope of this array. Such a pointer willbecome invalid. mainwindow.cpp 277LRESULT CALLBACK CMainWindow::WinMsgHandler(....){ ... if (pNMHDR->code == TTN_GETDISPINFO) { LPTOOLTIPTEXT lpttt; lpttt = (LPTOOLTIPTEXT) lParam; lpttt->hinst = hResource; // Specify the resource identifier of the // descriptive text for the given button. TCHAR stringbuf[MAX_PATH] = {0}; ... lpttt->lpszText = stringbuf; } ...}The V507 message warns you that the object is being used after it has been destroyed. The stringbufbuffer will be used after exiting the if operators body.If stringbuf was a class object (for instance, of std::string class), the code would behave incorrectly. Wewould use an already destroyed object in that case. But here stringbuf is an array created in the stack.The Visual C++ compiler does not use this stack fragment once again, so the buffer will continue to existuntil the CMainWindow::WinMsgHandler function terminates. Thus, there is no error yet this code ispotentially dangerous.
  10. 10. Case 10Here is one more fragment like the previous one. Again we have code that works but it is rather fragile.V507 Pointer to local array stringbuf is stored outside the scope of this array. Such a pointer willbecome invalid. picwindow.cpp 443if ((HWND)wParam == m_AlphaSlider.GetWindow()){ LPTOOLTIPTEXT lpttt; lpttt = (LPTOOLTIPTEXT) lParam; lpttt->hinst = hResource; TCHAR stringbuf[MAX_PATH] = {0}; _stprintf_s(stringbuf, .....); lpttt->lpszText = stringbuf;}Case 11It is a bad idea to throw exceptions and not to handle them in the destructor.V509 The throw operator inside the destructor should be placed within the try..catch block. Raisingexception inside the destructor is illegal. cachefileoutbuffer.cpp 52CCacheFileOutBuffer::~CCacheFileOutBuffer(){ if (IsOpen()) { streamOffsets.push_back (GetFileSize()); size_t lastOffset = streamOffsets[0]; for (size_t i = 1, count = streamOffsets.size(); i < count; ++i) { size_t offset = streamOffsets[i]; size_t size = offset - lastOffset;
  11. 11. if (size >= (DWORD)(-1)) throw CStreamException("stream too large"); Add ((DWORD)size); lastOffset = offset; } Add ((DWORD)(streamOffsets.size()-1)); }}The V509 message warns you that if the CcacheFileOutBuffer object is destroyed while an exception isbeing handled, a new exception will cause a program crash.Case 12One more meaningless comparison.V547 Expression endRevision < 0 is always false. Unsigned type value is never < 0. cachelogquery.cpp999typedef index_t revision_t;...void CCacheLogQuery::InternalLog ( revision_t startRevision , revision_t endRevision , const CDictionaryBasedTempPath& startPath , int limit , const CLogOptions& options){ ... // we cannot receive logs for rev < 0 if (endRevision < 0) endRevision = 0;
  12. 12. ...}There simply cannot be any negative values here. The endRevision variable has the unsigned type andtherefore endRevision is always above or equal to 0. The potential issue here is that if a negative valuehas been cast to unsigned type somewhere earlier, we will start handling a very large number. Thiscannot be checked.Case 13There are no more useful messages of the first level. Well, for now. It is only the first step in trying PVS-Studios capabilities. We intend to develop not fewer than 150 issues we must teach our tool to detect. Iwould like to thank once again those readers who responded to our previous articles and sent ussamples where one can detect errors with the help of static analysis at the stage of writing the code.Lets look at the second level. We found one interesting error related to using the Copy-Paste method.By the way, there was nothing interesting at the third level, so it is not for nothing that we disable it bydefault.V524 It is odd that the GetDbgHelpVersion function is fully equivalent to the GetImageHlpVersionfunction (SymbolEngine.h, line 98). symbolengine.h 105BOOL GetImageHlpVersion(DWORD &dwMS, DWORD &dwLS){ return(GetInMemoryFileVersion(("DBGHELP.DLL"), dwMS, dwLS)) ;}BOOL GetDbgHelpVersion(DWORD &dwMS, DWORD &dwLS){ return(GetInMemoryFileVersion(("DBGHELP.DLL"), dwMS, dwLS)) ;}The V524 message is generated if the analyzer finds two suspiciously similar functions. It is most likelythat the first function must get the "imagehlp.dll" version of the file instead of "dbghelp.dll ".Step 8Now we must fix the errors we have found. This step is clear and we will skip it.
  13. 13. Concerning the errors found, we will report them to TortoiseSVNs developers.Step 9Now lets speak a bit about false alarms. Let me give you some examples to explain what false alarmsare and how we can deal with them.Here is the first false alarm: PVS-Studio did not understand the playing with the operation of memorycopying.V512 A call of the memcpy function will lead to a buffer overflow or underflow. resmodule.cpp 838const WORD*CResModule::CountMemReplaceMenuExResource(....){ ... if (newMenu != NULL) { CopyMemory(&newMenu[*wordcount], p0, 7 * sizeof(WORD)); } ...}The V512 warning informs you that we have a buffer underflow or, vice versa, a buffer overflow. Theanalyzer made a mistake this time having suggested that we want to copy 7 objects but intend to handleonly one object of the WORD type.Here is the second false alarm. The analyzer suggests that we processed only a part of the array.V512 A call of the memcmp function will lead to a buffer overflow or underflow. sshsha.c 317static int sha1_96_verify(....){ unsigned char correct[20]; sha1_do_hmac(handle, blk, len, seq, correct); return !memcmp(correct, blk + len, 12);}Right, it is only a part of the correct array participating in the comparison operation but we made itintentionally.The third example of a false alarm.V517 The use of if (A) {...} else if (A) {...} pattern was detected. There is a probability of logical errorpresence. tree234.c 195
  14. 14. static void *add234_internal(....){ ... if ((c = t->cmp(e, n->elems[0])) < 0) childnum = 0; else if (c == 0) return n->elems[0]; /* already exists */ else if (n->elems[1] == NULL || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1; else if (c == 0) return n->elems[1]; /* already exists */ else if (n->elems[2] == NULL || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2; else if (c == 0) return n->elems[2]; /* already exists */ else childnum = 3; ...}The analyzer does not like that the check c == 0 is present in code several times. The code is correctsince the c variable is changed inside the other conditions "c = t->cmp(e, n->elems[2])". But thissituation is rather rare. Usually the V517 message points to real defects in code.We will not consider all the rest of false alarms because there is nothing interesting about them. Aprogrammer can easily understand that they are false alarms and he does not have to examine them tooclosely.You may handle false alarms in several ways:1) You may rewrite the code. Sometimes it is rather reasonable. Refactoring would be very helpful forthe last sample with a false alarm (I mean the add234_internal function and warning V517).
  15. 15. 2) You may disable some diagnoses in the settings which always produce false alarms in your projects.After you disable them, all the corresponding messages will disappear from the warning list. For moredetails, see "Settings: Detectable Errors".3) If false alarms refer to code that does not need to be checked, you may exclude separate files orfolders from analysis. You may also use masks. For details, see "Settings: Dont Check Files". This methodis convenient for excluding third-party libraries from analysis.4) You may use the mechanism of suppressing messages containing particular text. For details, see"Settings: Message Suppression".5) There are cases when you should suppress a particular false alarm. Then you may use the "Mark asFalse Alarm" option. If you use it, the analyzer adds a small comment of the "//-Verror_code" kind intothe code. You may hide code fragments marked with this comment by the FA button that enables ordisables displaying the marked messages. For details, see: "False alarm suppression".Thank you for your attention. Try PVS-Studio. Send us your feedback. Ask us questions. Give us yourinteresting samples. Offer new diagnostic rules we could implement.Sincerely yours, Andrey Karpov, one of the PVS-Studios developers.You may contact us on page "Feedback".Or by e-mail: support[@]viva64.com , karpov[@]viva64.com.Write to us, we have a wonderful support service. It is me, the author of this article, who will personallyparticipate in communication unlike most companies where you deal with some abstract human-robot.