Improving C++ Software Quality with Static Code Analysis

interpretation is a formal framework for making semantic approximations of programs for the purpose of static analysis. The basic idea behind the method is to choose a suitable abstraction and apply that abstraction to the program code. This operation will remove the uninteresting parts of the program in order to make the analysis more manageable. The freedom to choose an abstraction provides other useful benefits, such as ensuring that an infinite analysis problem becomes finite and thus solvable. An abstraction like this is certainly less precise than the concrete problem, but it still provides us with useful information and in feasible time. Another interesting feature of abstract interpretation is that the analysis is sound with respect to the abstraction, i.e., it will find all problems evident in the abstracted program. [19] Using abstract interpretation requires one to define the abstraction with the help of an abstract domain. The abstract domain is defined as D = (L,≤ ,⊥,>,t,u), where • (L,≤) is a partially ordered set with the relation ≤, • ⊥ is the least element (uL = ⊥), • > is the greatest element (tL = >), • t is the join (or least upper bound) operator, and • u is the meet (or greatest lower bound) operator. This definition forms a complete lattice, which is a partially ordered set where each subset X v L has a least upper bound tX and a greatest lower bound uX. CHAPTER 3. TECHNICAL ASPECTS OF STATIC ANALYSIS 32 The join operator is conveniently analogous to the semantics of program branching. That is, a branch in the program can be interpreted with a join operator, thus ensuring that the resulting element after either branch is the smallest element greater or equal to both original elements. The meet operator can be defined, but is not required for our examples. Due to the join operator, these abstractions are flow-insensitive, which means that the control sequence is ignored and thus do not require complex control-flow handling. Instead, all possible sequences are considered, which may lead to false positives in case of implausible control-flows. [16, 59] Let us consider the example in Listing 3.9. This example uses a simple abstraction of value signs to detect the possibility of a division by zero. We use the same abstract domain as Slaby [59] to represent the signs of values. The abstract domain elements are thus L = {⊕, , 0,>,⊥}. This abstraction is similar to the one introduced by Rosendahl [54], except it explicitly introduces the elements for uninitialized (⊥) and unknown values (>). This abstract domain forms the lattice illustrated in Figure 3.3. The lattice is ordered using the relation of subsets (v), i.e., the least element is the empty set (⊥ = ∅), and the greatest element contains all values of the domain (> = {⊕, , 0}). With this simple abstraction, we can try to answer the question if a division by zero occurs in our example. Essentially, we pose the question: is n = 0? Through abstract interpretation, we arrive at an answer that maybe it is, i.e., n = >, which in our chosen abstraction represents an unknown but initialized value. In this particular case we are uncertain if a division-by-zero defect is present, but a static analysis tool making this analysis could report this as a potential issue.