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;

No comments:

Post a Comment