We’ve mentioned in the past that the code we write isn’t maintaining heartbeats or being used in life-critical settings, but what if your code is? NASA happens to be a company who has code that is life and mission critical and it’s very possible it won’t even be accessible once it leaves earth. In this episode, we explore what has been deemed “The Power of 10” for writing safety-critical code.
If you’re listening on a podcast player and would like to see all the show notes, head to:
NASA’s Power of Ten
Their rules to ease the use of static analysis
Rule #1 Simple Control Flow
- You’re not allowed to use:
Rule #2 Limit all loops to a fixed upper bound
- For example, linked list traversals include checking against some maximum iteration boundary, and the boundary is always an integer.
- “If a tool cannot prove the loop bound statically, the rule is considered violated.”
Rule #3 Do not use the heap
- So no need for malloc and free.
- Rely soly on the use of stack memory instead.
- This is because quite a few memory bugs are due to the use of the heap and garbage collectors. Things like:
- memory leaks
- heap overflows
- heap exhaustion
- Applications that make use of the heap cannot be proven by a static analyzer.
- By only using stack memory, including upper boundaries where necessary, you can commute exactly the most amount of memory your application will need.
- And bonus, by only using the stack, you the eliminate the possibility of the previously mentioned memory bugs.
Rule #4 Function sizes are limited
- Functions should only do one thing but might require multiple steps.
- Very consistent with Uncle Bob’s series and other best practice principles.
- NASA recommends that functions be limited to 60 lines or what can fit on a single printed page.
- This makes it easier for someone reviewing your code to read and understand that function in it’s entirety.
- This limitation also helps to make sure that your function is testable, i.e. it’s not just some meandering 2,000 line long function with 18 levels of nesting.
Rule #5 Hide your data
"Data hiding is a technique of hiding internal object details. Data hiding restricts the data access to class members. It maintains data integrity."
- The idea here is to declare variables as close as possible to where they are used at the lowest possible scope.
- Meaning, rather than declare some variable that you only need inside of a loop at the function level, instead, declare that same variable inside the loop.
- Doing this accomplishes two things:
- First, it reduces the amount of code that can access those variables, and more importantly, you …
- Second, reduce the number of touch points that might change a variable’s value which aides debugging efforts when you want to understand why it got some unexpected value.
Rule #6 Check the $#*!#**#@ return values
- How many times have you been bitten by code that made some call, even shell scripts/commands, where the call failed, but the return value that would have saved you wasn’t checked?
- Check the return values of everything that returns something.
- Always check them
- All of them
- If you explicitly want to ignore a return value, such as when printing to the screen, you are to explicitly cast the return to void so that a reviewer knows that you do not care about that particular return value.
- Failure to check a return value or cast it to void will be brought up in a code review.
Rule #7 Use of the Preprocessor is very limited
- NASA limits the use of the C preprocessor to only file inclusions and very simple macros.
- “The C preprocessor is a powerful obfuscation tool that can destroy code clarity and befuddle many text-based checkers.”
- Specifically, conditionals that can change the code at compile time are called out by the Power of 10
- For example, if you have 10 different flags that can be changed at compile time, you have 2^10 build targets that need to be tested.
- In other words, preprocessor conditionals can exponentially increase your testing requirements.
Rule #8 Pointer use is restricted
- “No more than one level of dereferencing should be used. Dereference operations may not be hidden in macro definitions or inside typedef declarations.”
- This is because pointers, albeit powerful, are easy to misuse.
- Doing this limits you to structures that more properly track your pointers.
- Pointer restrictions also include not using function pointers.
- This is because function pointers obfuscate the control flow of your application.
- They also make it more difficult to statically analysis your code.
Rule #9 Compile in Pedantic mode
- Meaning, compile with all warnings enabled.
- This will allow the compiler to alert you to every issue it sees.
- Treat your warnings as errors and address them before release.
Rule #10 Analyze and test
- Use multiple static code analyzers to evaluate your code
- They don’t all work the same and sometimes use different rules.
- Test, test, and test again. Preferably with some form of automated testing, i.e. unit tests vs integration tests, etc.
Resources We Like
Tips of the Week
- A couple years ago I mentioned an Instrumental album from a band called Night Verses that I was really excited about. It was great working music that kept things moving, and now they’ve gone and released a new album. The bass player from Tool (Justin Chancellor) guest stars on one of the tracks too.
- View Pull Requests INSIDE IntelliJ
- Or inside Visual Studio Code
- Do AI locally using Llama 2 from Meta
- Get started with ML and AI without having to find all the tools yourself using Anaconda
- Maven build cache extension to improve local build times