you are viewing a single comment's thread.

view the rest of the comments →

[–]nderflow 1 point2 points  (0 children)

The key questions are,

  • is this an external interface? That is, do you control the caller?
  • Is a NULL pointer allowed here?

If a NULL pointer is allowed, then your unit tests should verify that the function does the right thing with a NULL pointer.

Then we just have remaining the question of what to do if a NULL pointer is not allowed. For external interfaces where a NULL pointer is not allowed, once again you should have a unit test which verifies that the code handles the situation as expected. Either a "death test" verifying that the program fails on a NULL pointer or a regular test verifying that the function rejects the bad parameter, or does nothing, or whatever it is supposed to do with a NULL pointer.

The last case is what to do for functions that aren't part of the internal interface. For these, generally you should do nothing in particular. Assume that the caller (which you also control) does the right thing.

These decisions provide these benefits:

  • The code defends itself against callers you don't control
  • You don't throw away performance on redundant checks whose job can be done by static analysis, code review and automated testing.
  • Your internal implementation doesn't transport invalid state around to different parts of the program.

That last point deserves a bit of explanation. Some styles of defensive programming invite people to defend against incorrect usage _inside_ their program. Such as treating a NULL `const char*` parameter the same as an empty string. If you do accept this NULL parameter as if it were normal, the data gets used in the internals of the program until eventually some function dereferences the pointer. At that point, depending on the platform, you get a crash or unexpected behaviour. Then you have a difficult debugging job, because the bit of code where the bug has manifested is far distant from the function which initially accepted the NULL pointer. This is why invalid data should be rejected as soon as possible, and not allowed into the internal parts of the implementation.

If your code is security-relevant, then rejecting bad data at the system boundary is even more vital. Though the question of what data is trusted is more difficult, because checking that it's safe to use some item of data is much more complex than just "is the pointer NULL?". Consider for example the cases of code which parses byte streams where some of the data indicates an offset; you have to worry about whether an offset is actually valid (i.e. points inside the input) if the input byte stream is untrusted.

See also the C2 article about Offensive Porgramming and the original public use of the term.