📜 ⬆️ ⬇️

We document errors in Doxygen



This article focuses on the doxygen documentation system verification. This well-known and widely used project, according to a well-founded statement of developers, which has actually become the standard for documenting software written in C ++, has not yet been verified by PVS-Studio. Doxygen scans the source code of the programs and generates documentation on it. The time has come for us to take a look at its sources and see what PVS-Studio can find.


Introduction

Doxygen is a cross-platform source documentation system that has a wide range of target languages: C ++, C, Objective-C, Python, Java, C #, PHP, IDL, Fortran, VHDL and partially supports D. Doxygen generates source-based documentation with comments. of a special kind and can be configured to extract the structure of the program with undocumented sources. Output formats can be HTML, LATEX, man, rtf, xml. Doxygen is used in KDE, Mozilla, Drupal, Pidgin, AbiWorld, FOX toolkit, Torque Game Engine, Crystal Space projects.
')

Preparation and verification

The most recent doxygen sources can be taken from github.com/doxygen/doxygen . Initially, the repository does not contain project files for Visual Studio, but since developers use cmake, they can be generated. I used the console version of the program and the “cmake -G„ Visual Studio 12 ”command to generate the project file for VS 2013. To start the check, just click Check Solution in the PVS-Studio tab in Visual Studio.

Alert Analysis

Before the immediate analysis of warnings, I would like to pay attention to the writing style of doxygen. Often in the code, for some unknown reason, the programmer tried to put everything in one line or neglected the spaces between variables and operators, which significantly reduced the readability of the code. In some places came across strange formatting. And sometimes there was such . To fit some code in the article, I had to format it. After a short digression, I suggest looking at the most interesting errors found in doxygen.

PVS-Studio warning : V519 The '* outListType1' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 8326, 8327. util.cpp 8327
void convertProtectionLevel(MemberListType inListType, int *outListType1, int *outListType2) { static bool extractPrivate; .... switch (inListType) { .... case MemberListType_priSlots: if (extractPrivate) { *outListType1=MemberListType_pubSlots; *outListType1=MemberListType_proSlots; <<<<==== } else { *outListType1=-1; *outListType2=-1; } break; .... } } 

The if body contains a double assignment of the same variable. Obviously, this is a typo or error when copying. The else block suggests that the value "MemberListType_proSlots" should be written to "* outListType2". Another similar error can be found here: doxygen.cpp 5742 (see the 'da-> type' variable).

The next warning to PVS-Studio: V519 The 'pageTitle' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 970, 971. vhdldocgen.cpp 971
 QCString VhdlDocGen::getClassTitle(const ClassDef *cd) { QCString pageTitle; if (cd == 0) return ""; pageTitle += cd->displayName(); pageTitle = VhdlDocGen::getClassName(cd); .... } 

Pay attention to the assignment operation. Most likely, this is a typo and instead of "=" should be used "+ =". On the issue of style, this function does not contain spaces between operators and values ​​in the source code, which significantly worsens the readability of the code. This increases the chance of an error, as it is hard to see the absence of a "+" among the continuous stream of characters. After adding spaces, the error becomes more noticeable. Another similar bug is hidden here:

V519 The 'nn' variable is assigned values ​​twice successively. Perhaps this is a mistake. Check lines: 2166, 2167. vhdldocgen.cpp 2167

Let's move on to the next warning.

PVS-Studio warning : V523 The 'then' statement is equivalent to the 'else' statement. docparser.cpp 521
 static void checkUndocumentedParams() { .... if (g_memberDef->inheritsDocsFrom()) { warn_doc_error(g_memberDef->getDefFileName(), g_memberDef->getDefLine(), substitute(errMsg,"%","%%")); } else { warn_doc_error(g_memberDef->getDefFileName(), g_memberDef->getDefLine(), substitute(errMsg,"%","%%")); } .... } 

The copy-paste programming method can not only save time when writing code, but also introduce errors into it. Here the code from the if block was copied for use in the else block, but not changed after insertion. Each time you use copy-paste, you should follow the rule “Copy once, check seven times”.

PVS-Studio warning : V523 The 'then' statement is equivalent to the 'else' statement. translator_tw.h 769
 class TranslatorChinesetraditional : public Translator { public: .... virtual QCString trGeneratedFromFiles(bool single, ....) { .... QCString result=(QCString)"?"; .... if (single) result+=":"; else result+=":"; .... } .... } 

Similar to the previous warning. In the if block, regardless of the condition, the same character is added to the result string. This is hardly the expected behavior, because then the condition itself does not make sense. And again, I am inclined to think that if, following the generally accepted style, this block was separated into 4 lines, then it would not just look prettier, but also make the typo more obvious. Interestingly, this construct was copied twice for further use in functions, but the error was never noticed. As a result, two more warnings fall on these lines:
PVS-Studio warning : V530. classdef.cpp 1963
 void ClassDef::writeDocumentationContents(....) { QCString pageType = " "; pageType += compoundTypeString(); toupper(pageType.at(1)); .... } 

Here the programmer misunderstood how the toupper function works. He may have expected the function to change the transmitted character to a capital letter. In fact, the function returns the capital equivalent of the symbol-argument, and does not change it. This is how the toupper function is declared in the “ctype.h” header file.
 int toupper (int __c); 

Already from the function declaration, one can see that the parameter is accepted by value, which means that the transmitted symbol cannot be changed. To avoid such errors, it is enough to carefully read the description of the functions used, the behavior of which you are not sure.

PVS-Studio warning:
V560 A part of the conditional expression is always false: (flags () &! 0x0008). qfile_win32.cpp 267
 #define IO_Truncate 0x0008 bool QFile::open(....) { .... int length = INT_MAX; if ((flags() & !IO_Truncate) && length == 0 && isReadable()) .... } 

This condition will always be false due to the fact that the logical negation of a nonzero value always gives zero. The subsequent use of "AND" does not matter when one of the arguments is zero. As a result, the condition does not depend on the other parameters. Here, it seems more logical to use the bitwise inversion operator ~.

PVS-Studio warning : V560 A part of conditional expression is always true :! Found . util.cpp 4264
 bool getDefs(....) { .... bool found=FALSE; MemberListIterator mmli(*mn); MemberDef *mmd; for (mmli.toFirst();((mmd=mmli.current()) && !found);++mmli) { .... } .... } 

I’ll say right away that in the body of the for loop, the variable found is not changed. Because of this, the exit condition of the loop depends only on the result of the mmli.current method. This is fraught with the fact that the cycle will always go from start to finish, regardless of whether the desired value was found or not.

PVS-Studio warning:
V595 'bfd ’ Check lines: 3371, 3384. dot.cpp 3371
 void DotInclDepGraph::buildGraph(....) { .... FileDef *bfd = ii->fileDef; QCString url=""; .... url=bfd->getSourceFileBase(); .... if (bfd) .... } 

V595 is perhaps the most common warning among all projects. It is not always before using the pointer that you wonder whether it can be zero. Sometimes you think about it after the second or third use and put the check. But between it and the first dereference, there can be a very large amount of code, which makes the error very difficult to detect. Similar warnings:
PVS-Studio warning:
V595 'lne ’pointer Check lines: 4078, 4089. index.cpp 4078
 static void writeIndexHierarchyEntries(OutputList &ol, ....) { QListIterator<LayoutNavEntry> li(entries); LayoutNavEntry *lne; for (li.toFirst();(lne=li.current());++li) { LayoutNavEntry::Kind kind = lne->kind(); .... bool addToIndex=lne==0 || lne->visible(); .... } } 

I do not describe the same type of checks, because they are too boring for an article. However, I want to consider another warning V595. Here, the loop is entered only if the return value of li.current () (which is assigned to the lne pointer) is not NULL. This means that inside the loop, the pointer is guaranteed not to be zero, making subsequent verification unnecessary. I wanted to write out this example, because usually the V595 warning indicates a potential dereference of the null pointer, and here it showed an extra check.

Analyzer Warnings: V601 The bool type is implicitly cast to the class type. docsets.cpp 473
 struct IncludeInfo { .... bool local; }; void DocSets::addIndexItem(Definition *context,MemberDef *md, const char *,const char *) { QCString decl; .... IncludeInfo *ii = cd->includeInfo(); .... decl=ii->local; .... } 

The analyzer paid attention to the strange conversion of bool to the class type. In the QCString class, an overloaded assignment operator is not defined for an argument of type bool, but a constructor is defined with an input parameter of type int, which indicates the size of the string. It is this one that is called to create a temporary object with this assignment. The compiler will find a constructor with the argument int and call it, having first converted bool to int. The local variable can have only 2 values: true or false, which corresponds to 1 and 0. In the first case, the designer will create a string from one element. In the second case, you get an empty string. At the end, an assignment statement will be invoked with an argument of type QCString. A similar, but less obvious cast is carried out in the following places:
PVS-Studio Warning: V614 Potentially uninitialized pointer 't' used. vhdlparser.cc 4127
 QCString VhdlParser::extended_identifier() { Token *t; if (!hasError) t = jj_consume_token(EXTENDED_CHARACTER); return t->image.c_str(); assert(false); } 

In this section of the code, an uninitialized pointer can be dereferenced. The real code is poorly designed, so this error went unnoticed. For the article, I formatted the code and the error became immediately noticeable. Two more such errors can be found here:
PVS-Studio warning : V668 has been defined as the "new" operator. The exception will be generated in the case of memory allocation error. outputgen.cpp 47
 void OutputGenerator::startPlainFile(const char *name) { .... file = new QFile(fileName); if (!file) .... } 

It is no longer a news for anyone that in the case of an unsuccessful allocation of memory, new throws an exception, and does not return nullptr. This code is a kind of archaic programming. For modern compilers, these checks are no longer meaningful, they can be removed. 3 more such checks:
PVS-Studio warning : V701 realloc () possible leak: when realloc () fails in allocating memory, original pointer 'd' is lost. Consider assigning realloc () to a temporary pointer. qcstring.h 396
 class BufStr { public: .... void resize(uint newlen) { .... m_buf = (char *)realloc(m_buf,m_size); .... } private: uint m_size; char *m_buf; .... } 

The analyzer detected an incorrect use of the “realloc” function. In case of unsuccessful allocation of memory, “realloc” will return nullptr, overwriting the previous value of the pointer. To prevent this, it is recommended to store the value of the pointer in a temporary variable before using realloc. In total, in addition to this, 8 potential memory leaks were found in the project:

Conclusion

In conclusion, I would like to say that the analyzer coped well with its work and found many suspicious places, despite the fact that doxygen is a popular project and is widely used by a large number of both small and large companies. I gave the most basic warnings and left such uninteresting as extra checks, unused variables, etc. beyond the scope of this article. It is worth mentioning that in some places I was surprised by the very careless, in my opinion, code design.

I would like to wish the readers to write beautiful, readable code and avoid mistakes. And if the first entirely depends on the programmer, then the analyzer can help with the second. Download and try free PVS-Studio on your project here: http://www.viva64.com/ru/pvs-studio-download/


If you want to share this article with an English-speaking audience, then please use the link to the translation: Igor Shtukarev. Documenting Bugs in Doxygen .

Read the article and have a question?
Often our articles are asked the same questions. We collected answers to them here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please review the list.

Source: https://habr.com/ru/post/265025/


All Articles