Revise the usage of custom assertions (Design by Contract)
The usage of Design by Contract should be reviewed and there are several steps that needs to be taken:
-
trust your own code -
but write a covering unit tests when refactoring -
reduce the number of assertions just to places with user input -
use -NDEBUG
controlled assertions for production build where appropriate -
keep some run time assertions, but as we progress with refactoring, reduce the number by replacing with proper checks
I am inclined to throw away this whole Design By Contract philosophy and go f.e. with Defensive Programming / Offensive Programming.
I don't buy this whole no-RCE bug in years. I understand it was a good step after BIND 8, but again the world has moved on, and RCE can be much better mitigated at system level, and any class of remote vulnerability is basically the same category now.
This is obviously not something that could be done overnight, but rather we should rethink the whole philosophy and make changes when refactoring the code.
TL;DR
Defensive programming
Defensive programming is an approach to improve software and source code, in terms of:
- General quality – reducing the number of software bugs and problems.
- Making the source code comprehensible – the source code should be readable and understandable so it is approved in a code audit.
- Making the software behave in a predictable manner despite unexpected inputs or user actions.
Offensive programming
Offensive programming is concerned with failing, so to disprove the programmer's assumptions. Producing an error message may be a secondary goal.
Strategies:
- No unnecessary checks: Trusting that other software components behave as specified, so to not paper over any unknown problem, is the basic principle. In particular, some errors may already be guaranteed to crash the program (depending on programming language or running environment), for example dereferencing a null pointer. As such, null pointer checks are unnecessary for the purpose of stopping the program (but can be used to print error messages).
- Assertions – checks that can be disabled – are the preferred way to check things that should be unnecessary to check, such as design contracts between software components.
- Remove fallback code (limp mode) and fallback data (default values): These can hide defects in the main implementation, or, from the user point of view, hide the fact that the software is working suboptimally. Special attention to unimplemented parts may be needed as part of factory acceptance testing, as yet unimplemented code is at no stage of test driven development discoverable by failing unit tests.
- Remove shortcut code (see the strategy pattern): A simplified code path may hide bugs in a more generic code path if the generic code almost never gets to run. Since the two are supposed to produce the same result, the simplified one can be eliminated.