Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Thursday, February 10, 2011

Doxygen tips-n-tricks


Doxygen documentation parser written by Dimitri van Heesch (big thanks!) has been a part of my professional tool chest for a good part of the last ten years. Along with Graphvis (layered drawings of directed graphs), a graph visualization toolkit from AT&T and Lucent Bell Labs, it provides a quick and intuitive way of documenting your source code. Doxygen output also helped me tremendously to navigate through a maze and learn quickly the inner workings of every new project I came across in the past.

First, you need to create a configuration file, Doxyfile, either by running doxywizard GUI or from command-line with 'doxygen -g' command.

Adjust Configuration File

First thing you would want to adjust is project name/number, document encoding and output directory.

PROJECT_NAME = Granule
PROJECT_NUMBER = 1.4.0

OUTPUT_DIRECTORY = dox
DOXYFILE_ENCODING = UTF-8
TAB_SIZE = 4
FULL_PATH_NAMES = NO
HIDE_UNDOC_CLASSES = NO
HIDE_IN_BODY_DOCS = YES 
EXTRACT_ALL = YES
RECURSIVE = YES

If it is a "C" project, optimize for "C":

OPTIMIZE_OUTPUT_FOR_C = YES

For C++ project,

BUILTIN_STL_SUPPORT = YES
TEMPLATE_RELATIONS = YES
CLASS_GRAPH = YES
UML_LOOK = YES
GRAPHICAL_HIERARCHY = YES


You need to tell doxygen the file types to parse:

FILE_PATTERNS = *.h *.cpp

One thing I like about doxygen is its ability to shed some light on the function call chain in a program. For every function, it can optionally list all callers as well as call chain.

CALL_GRAPH = YES
CALLER_GRAPH = YES
REFERENCED_BY_RELATION = YES

COLLABORATION_GRAPH = YES
SOURCE_BROWSER = YES
INLINE_SOURCES = YES

ALPHABETICAL_INDEX = YES
GENERATE_TREEVIEW = YES   (side pane) 
SEARCHENGINE = YES

GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER = 
HTML_STYLESHEET = 
HTML_ALIGN_MEMBERS = YES
DISABLE_INDEX = NO

HAS_DOT = YES
DOT_IMAGE_FORMAT = png
DOT_FONTNAME = FreeSans

Cross-Referencing with Tag Files

If your project is an collection of disjointed libraries each within the boundary of its own package, you might want to enable cross-referencing with tagging feature of doxygen.

To better illustrate the concept, I will use my own software as an example. My flashcards program, Granule, internally relies on libassa (a collection of reusable shared code). In my Linux development environment, I have the following code arrangement:

Library

/path/to/libs/libassa/assa/
|
|-/dox/
|    \--html/
|         \--index.html
|--{*}.h
|--{*}.cpp
|--Makefile
\--Doxygen-lib

Application

/path/to/apps/granule/src
|
|--/dox/
|    \--html/
|         \--index.html
|--{*}.h
|--{*}.cpp
|--Makefile
\--Doxygen-app

The two paths are distinctly different. In order for the HTML documentation generated by Doxygen-app include references to the documentation generated by Doxygen-lib, we need
  • configure Doxygen-lib to generate its tag file
  • configure Doxygen-app to include external tag file(s) to resolve external references
With this in mind, let's look at the syntax of both configuration files:

--- Doxygen-lib ---

GENERATE_TAGFILE = dox/html/libassa.tag


--- Doxygen-app --

TAGFILES = /path/to/libs/libassa/assa/dox/html/libassa.tag=/path/to/libs/libassa/assa/dox/html


BTW, the path can be either absolute (as show in the example above) or relative. As you can see for yourself, the syntax of the TAGFILES parameter is a bit awkward by any standards. I would prefer instead an option of an environment variable to resolve the location of tag files which is more suited for a team of collaborative developers.

However, it gets the job done and that is what's important at the end of the day.

Happy coding!

Tuesday, January 11, 2011

Safe sprintf() C++ style

Scanning the legacy "C" code, often I find code snippets similar to the following code:

#include <stdio.h>

static const char msg_buf [1024];

static void log (const char* msg) {  /* log the message */ }
/* ... some place later */

int i = 43;
const char* msg = "Safety first"

sprintf (msg_buf, "%s : %d\n", msg, i);
log (msg_buf);

Any static code analysis tool such as (cppcheck, flawfinder, RATS, etc.) will mark call to sprintf() as unsafe on the ground that it doesn't check for buffer overflow.

However, if you can compile your program with C++ compiler, the easiest fix to such a problem would be to use ostringstream C++ standard library string buffer instead:

#include <sstream>

static void log (const char* msg) 
{  
   /* log the message */ 
}

/* Add a wrapper version of log() function
* that accepts stream string as an argument
* and clears up buffer after the message
* has been logged.
*/ 
static void log (const std::ostringstream& msg)
{
    log (msg.str().c_str());
    msg.str (""); 
}

namespace {
    std::ostringstream msg_buf;
}

/* ... some place later */

int i = 43;
const char* msg = "Safety first."

msg_buf << msg << " " << i << std::endl;

log (msg_buf);  /* overloaded */ 


Note that we added a wrapper log() function to help us hide the syntax and to clean up the buffer after the message has been logged.

Formatting Options

It takes time to get used to C++ stream formatting.
For example, the following code snippet prints an integer number as upper case hex to the buffer:

#include <stdio.h>

int i = 43; 
char msg_buf[1024];

snprintf (msg_buf, 1024, "Print hex number : %0X.", i);

To achieve equivalent result with C++ stream, you need to insert a number of stream manipulators first.

std::hex manipulator specifies that integers should be treated as hexadecimal (base 16) values.

std::showbase manipulator specifies that the base of a number is to be output ahead of the number (a leading 0 for octals; a leading 0x or 0X for hexadecimals). This setting is reset with stream manipulator noshowbase.

std::setfill (char) manipulator pads empty space of the justified field with char character. By default, the filler is a whitespace.

std::uppercase manipulator converts the stream to upper case.

std::width (d) manipulator specifies the minimum widht of the data field.

#include <sstream>
#include <iomanip>

int i = 43;
std::ostringstream msg_buf;

msg_buf << "Hex number : "
        << std::hex << std::showbase 
        << std::setfill('0') << std::uppercase
        << i << ".";


For example, to print a date in YYYYDDMM format, the "C" code might look like this:

#include <stdio.h>

int year  = 2011;
int day   = 14;
int month = 1;
char msg_buf[1024];

snprintf (msg_buf, 1024,
          "Today's date: %04d%02d%02d", 
          year, day, month);

We can easily rework the solution in a safe C++ manner:

#include <sstream>
#include <iomanip>

int year  = 2011;
int day   = 14;
int month = 1;

std::ostringstream msg_buf;

msg_buf << std::setw(4)
        << year
        << std::setw(2) << std::setfill('0') 
        << day
        << std::setw(2) << std::setfill('0') 
        << month;