Defensive Programming & C++

Defensive programming for most of developers is something strange that old people with beards always talk about without any reason. For some it's a industry requirement. For others - philosophy.

What does it mean?

It's a way of programming with Murphy's Law in mind:

Anything that can go wrong will go wrong

Program should be immune to several things:

  • inexperienced programmer changes program structure incorrectly - now function receives NULL instead of buffer pointer
  • because of OS API changes function that returns file path for currently running program is NULL
  • user ejected CD/DVD program was loaded from during run
  • hacker tries to break program protections from outside using ill-formatted RPC requests

How it is related to C++?

C++ (probably because it's based on C, implements pointers and works close-to-metal) is vulnerable to fatal situations mentioned above, more than other languages like Java or C#. Without proper handling & code structure number of errors that can appear in code is almost unlimited. It is easy to produce 1-liners with at least 3 fatal bugs.

Rules

Few sample rules:

  • do not use raw pointers if you don't have to, replace with references or smart pointers
  • use one return statement for function body, extra return statements are only allowed for function's pre-conditions
  • use constants instead of literals (use BUFFER_SIZE, not 100)
  • do not use derived literals like (99 = buffer size - 1) without referencing base literal (100 here)
  • use exceptions instead of return codes
  • never catch all errors without reporting them if you are not sure what was raised
  • do not write complicated one-liners (expressions including several calculations, loops or lambdas with side effects) - split into named sub-expressions if too complicated if meaning is not obvious for code readers
  • do not use unsafe input/output C/C++ functions: gets, scanf, printf - see F.1
  • express yourself - do not base your assumptions of C++ Operator Precedence, always use parentheses (eg. replace "a || b > 2" by what you really mean, perhaps "a || (b > 2)" )
  • always include "default" section in switch statement
  • undefined behaviours (UB) should be treated as programmer errors (should not pass code review)

Language features & libraries

ASSERT

Use ASSERT anywhere you can to protect your code with extra pre-conditions. These checks are performed during run-time. However keep in mind they will be removed in release code, so if you need these conditions in release version of program - use exceptions.

Links:

static_assert

Static asserts are kind of checks that are performed during compile-time. Implemented in Boost and in C++11. Can be used to check input type has required features during program compilation.

Links:

type_traits

Type traits in C++ is a collection of type checks related to type features like is_floating_point or is_enum. Can be implemented without help of Standard Library, but are also included in C++11. They are used for example with static_assert or enable_if.

Links:

pointers

In C++ raw pointers should now be avoided and replaced by smart pointers or references whenever possible.

Smart pointer types available today:

  • C++11: std::shared_ptr, std::unique_ptr (replacement for std::auto_ptr)
  • Boost: boost::shared_ptr, boost::scoped_ptr
  • C++03: std::auto_ptr - not recommended for STL containers

If you need optional parameters, you can use boost::optional instead of passing NULL raw pointer.

exceptions

Exceptions very often are not well understood, and as a consequence forbidden as a whole. Using of exceptions can be painful and sometimes not possible (like in DLL functions). Also exception handling slows down processing. So when to use them?

It depends on kind of software you are working with - for business applications I recommend exceptions for checking any abnormal situations (like function writes to file for which catalogue or drive does not exist). There is also a good rule I follow: "throw as soon as possible". If you can detect incorrect situation early and there is no way you can handle error without throwing exception - do it. Throwing exceptions in your code leads to safer code because all incorrect situations can be detected earlier in development process.

Of course when you can ignore invalid situation - do it. You can call it "calming down" code:

Example:

/// Read data from external source and put it inside buffer
/// @param[in] buffer pointer to receive data
/// @param[in] size size of buffer
/// @return number of bytes read
int readToBuffer(char *buffer, int size) {
   if (size <= 0) 
     throw InvalidSize();
   ...  
}

Instead you can write "calm" version of code:

size_t readToBuffer(char *buffer, size_t size) {
   if (size == 0) 
     return 0;
   ...  
}

In second version:

  • size_t does not allow you to pass negative size (doesn't make sense)
  • passing zero-size buffer is not an error - just don't read anything

There is a similar technique that can be mistaken with "calming down". See code below:

int readToBuffer(char *buffer, int size) {
    if (size < 0) {
        try {
            log->write("Buffer size not correct");
        } catch (...) {                            
           // <-- error "silencing", size was < 0 but we do not report it
        }   
        return 0;             
    }
    ...
}

In this case (let's call it "error silencing") when size is incorrect and log is not configured correctly we will never see an error. And this can be dangerous (hides problems).

Yoda conditions

In C/C++ assignment operator (=) can be put instead of equality operator (==). Because of this some developers use "Yoda conditions" which looks strange and do not follow standard human logic:

if (100 == percent) {
  cout << "We have whole sum!" << endl;
}

Using such strange conditions is not recommended - it's better to check compiler warnings (VC++: C4706, GCC: add -Wparentheses).

compiler options

Several compilers in one document:

Visual Studio:

GCC:

  • -Weffc++: perform checks related to "Effective C++" book

High integrity C++ standards

There are some standards that should be followed in safety-critical applications.

Links

Books

  • "Secure Coding in C and C++" - Robert C. Seacord, 2005
  • "Safe C++: How to avoid common mistakes" - Vladimir Kushnir

Other resources

See also

Share

follow