Static Analysis and Run Time Error Detection on 64-Bit Platforms
October 14, 2010
3 min read
In my previous posts, I covered a four-step strategy for preparing the application for the 64-bit porting process:
- Apply mutation testing to perform run time error detection
- Use static code analysis to identify error-prone and non-portable code
- Repeat run time error detection
- Perform unit testing (Optional)
To wrap up this topic, I’d like to introduce a strategy for identifying defects on the 64-bit processor:
- Recompile the code on the 64-bit processor
- Repeat static analysis code inspection
- Link and build
- Get the application running
- Repeat runtime error detection
Step 1: Recompile the code on the 64-bit processor
Recompile your application on the 64-bit processor. If you have trouble compiling it, work out all of the quirks related to compiler variations. You might also want to create static code analysis rules that will automatically identify the code associated with these quirks so that you can prevent these compilation problems from occurring in the future.
Some rules to consider are those that point out hard to port (to 64 bit) and error-prone code. For example, consider the following examples (more details are provided in this post):
Rules that Expose Error-Prone Code
- Never return a reference to a local object or a dereferenced pointer initialized by “new” within the function.
- Never convert a const to non-const. This can undermine the data integrity by allowing values to change that are assumed to be constant.
- If a class has any virtual functions it shall have a virtual destructor. This standard prevents memory leaks in derived classes.
- Public member functions shall return const handles to member data.
- When you provide non-const handles to member data, you undermine encapsulation by allowing callers to modify member data outside of member functions.
- A pointer to a class shall not be converted to a pointer of a second class unless it inherits from the second. This “invalid” down casting can result in wild pointers, data corruption problems, and other errors.
- Do not directly access global data from a constructor.
A custom ruleset can be created from picking the critical rules needed just for this particular task. More rules can be added later, as needed.
Rules that Expose Difficult-to-Port Code
After you locate and repair this error-prone code, start looking for code that works fine on your current platform/architecture, but that might not port well. Some rules that are applicable to most 64-bit porting projects include:
- Use standard types whenever applicable.
- Review all existing uses of long data types in the source code.
- Examine all instances of narrowing assignment. Avoid such assignments because the assignment of a long value to an int will result in truncation of the 64 bit value.
- Find narrowing casts. Use narrowing casts on expressions, not operands.
- Find casts from long* to int*. In a 32 bit environment, these might have been used interchangeably. Examine all instances of incompatible pointer assignments.
- Find casts from int* to long*. In a 32 bit environment, these might have been used interchangeably. Examine all instances of incompatible pointer assignments.
- Find long values that are initialized with int literals. Avoid such initializations because integral constants might be represented as 32 bit types even when used in expressions with 64 bit types.
- Locate int literals in binary operations for which the result is assigned to a long value. 64 bit multiplication is desired if the result is a 64 bit value.
- Find int constants used in 64 bit expressions. Use 64 bit values in 64 bit expressions.
- Find uses of multiplicative expressions not containing a 64-bit type in either operand. To have integral expressions produce 64 bit results, at least one of the operands must have a 64-bit signed or unsigned data type.
- Use appropriate suffixes on integer and floating literal constants if your compiler allows them.
Step 2: Repeat static analysis code inspection
Once you recompile the code, perform static code analysis again to check if the new code complies with all appropriate coding standards. At this point, every change that should have been made but that was not made is an error. Fix these errors immediately! You don’t want to look for these errors as the application is running.
Step 3: Link and build
Link your application and try to build it.
Step 4: Get the application running
At this point, you should try to run your code. If you have a problem getting code running on the 64-bit processor, use a unit testing framework to run the code function by function; that way, you can flush exactly what in the code is not portable. Test the function, and then fix the problem. Continue this process until the entire application is running.
Step 5: Repeat run time error detection
Once the application is running, you’ll want to repeat run time error detection because the porting process is likely to cause some new problems that could not be detected before the port (for example, new memory corruption problems or different behaviors). If the run time error detection exposes problems, fix every bug related to porting.
Photo Credit: jurvetson
Want to learn more? Take a look at our Parasoft Insure++‘s product page to see how runtime error detection can expose memory corruption, memory leaks, access outside of array bounds, invalid pointers, and other defects.