Here’s a process you can apply to prepare your application for the 64-bit porting process:
Before you start porting, you should rid your original code of problems that will plague you on the 64-bit processor; for example, memory corruption and memory leaks. One of the most effective ways to expose the types of pointer and integer problems that will cause trouble on 64-bit processors is to leverage Mutation Testing for runtime memory error detection.
Runtime memory error detection technologies such as Parasoft Insure++ allow you to leverage techniques from traditional mutation testing to uncover ambiguities that are difficult to detect through other methods or tools. Whereas traditional mutation testing attempts to create “faulty” mutants to create a more effective test suite, mutation testing for runtime memory error detection creates and executes what should be functionally equivalent mutants of the source code under test.
When one of these mutants performs differently than the original program, it indicates that the code’s functionality relies on implicit assumptions which may not always be satisfied during execution. If a mutant causes the program to crash or encounter other serious problems, it’s a sign that when the assumptions are not satisfied, serious errors will occur at runtime.
In C++, this process of creating and running equivalent mutants can uncover:
After you clean the most critical errors exposed by mutation testing, use a static analysis tool to identify code that is likely to cause trouble when it is ported to the new platform/architecture. Static code analyzers use a compiler-like front-end to build a syntactic and semantic model of the software. The syntactic model is then analyzed against a set of rules or “checkers” to see if the code is in violation. These checkers use pattern-matching algorithms to detect errors such as poor use of language constructs, use of insecure functions, and detect poor coding practices that hinder portability (to be covered in my next post) and other violations of coding guidelines.
More sophisticated checkers employ semantic analysis that uses data and control flow to detect complex bugs and security vulnerabilities. To do this, the static analyzer builds an execution model of the software, considers possible paths through the code, and evaluates use of data as it flows from source (e.g. user input) to its destination (e.g. an API call such as a system call). Analyzing every single possible condition and path would be too time consuming, so the analyzer uses heuristics to detect the most likely paths for evaluation. Types of errors detected by these checkers include null pointer deference, buffer overflows, and security vulnerabilities like command and SQL injections.
There are two main tasks to focus on while performing static analysis:
First, check industry-respected C/C++ coding standards that identify coding constructs, which are likely to lead to problems on any platform or architecture. By ensuring that code complies with these coding standards, you prevent errors. This translates to less debugging on the new platform or architecture and reduces the chance of having bugs that elude testing and make their way into the release.
Repeat runtime memory error detection to verify that the modifications you made while fixing static analysis violations did not introduce any runtime errors.
At this point, you might want to perform one more step to ensure that your code is as error free as possible before you port it. This additional step is unit testing. Unit testing is traditionally used to find errors as soon as each application unit is completed. It can also be beneficial later in the development process because, at the unit level, it is easier to design inputs that reach all of the functions. In turn, this helps you find errors faster and expose errors that you might not uncover with application-level testing.
In the next post I cover the use of static analysis to help prepare code for porting to 64-bit platforms.
Parasoft’s industry-leading automated software testing tools support the entire software development process, from when the developer writes the first line of code all the way through unit and functional testing, to performance and security testing, leveraging simulated test environments along the way.