• No results found

Parallelized Program Analysis

N/A
N/A
Protected

Academic year: 2022

Share "Parallelized Program Analysis"

Copied!
141
0
0

Loading.... (view fulltext now)

Full text

(1)

Abstract

This thesis presents a framework for parallelized program analysis. It uses available parallel processing power in standard desktop computers to speed up static program analysis.

Today, processor chip manufacturers produce single processor chips contain- ing multiple cores, each with a processing power of previous single-core proces- sors. Major processor suppliers have released processors with 2, 4, and 6 cores and are currently working on processors with 8 and 16 cores, and are expected to produce processors with a few hundred cores in the near future.

Static program analysis finds programs’ runtime properties at compile time.

It is time-consuming to produce results of high quality, which is necessary for the results to be useful. One way to reduce analysis time is to take advantage of the processing power of multi-core processors.

The presented framework supports sequences of static program analyses, each producing partial results. In this thesis, the framework is instantiated with points-to analysis, reachability analysis, and escape analysis. Points-to analysis calculates object references in a program and provides the input for subsequent analyses. Reachability analysis calculates reachable methods and provides further input to the escape analysis, which finds object and method lifetimes. It is possible to implement other data-flow analyses using the same parallelized approach by choosing different analysis value types and transfer functions. The framework also facilitates the implementation of static analyses that automatically benefit from the parallelized approaches that the framework offers.

The framework shortens the analysis time for static program analysis based on points-to analysis in two ways: using a parallelized approach for points- to analysis, and using parallelized approaches for analyses relying on points-to analysis results. It is also possible to shorten the development time of other analyses when reusing the functionality of the framework.

To further improve the usefulness of the framework, requirements for addi- tional static analysis should be captured, and the framework should be extended accordingly. It should also facilitate to perform independent client analyses in parallel, rather than in sequence. These challenges are part of our future work.

(2)
(3)

Acknowledgment

First of all, I would like to thank my supervisor Welf L¨owe for his support and commitment during the last few years. This thesis would not exist without his encouragement and positive attitude.

I would also like to thank my former and present colleagues in computer science at DFM, Linnaeus University. Special gratitude is due to Jesper Ander- sson for encouraging me to take on doctoral studies, Jonas Lundberg for fruitful collaboration and discussions, and Ola Petersson for acting as opponent on my final seminar.

Thank you Peter Andersson for taking the time to read and comment the thesis; I truly appreciate it. Thank you Gunn Jensen for your support and commitment during my time as a doctoral student; it has been very valuable.

I am very grateful to all my friends, and family for supporting me and my work and providing me with many fun and memorable moments over the years.

Most importantly, thank you Cecylia, for your love and support.

(4)
(5)

Contents

1 Introduction 1

1.1 Goal and Goal Criteria . . . 1

1.2 Motivation . . . 2

1.3 Contributions . . . 4

1.4 Reading Directions . . . 4

1.5 Publications . . . 5

2 Static Program Analysis 7 2.1 Introduction . . . 7

2.2 Program Representations . . . 9

2.2.1 Abstract Syntax Trees . . . 9

2.2.2 Basic Block Graphs . . . 9

2.2.3 SSA Graphs . . . 10

2.2.4 Sparse SSA Graphs . . . 12

2.3 Data-flow Analysis . . . 12

2.3.1 Monotone Data-Flow Framework . . . 12

2.3.2 Simulated Execution . . . 15

2.3.3 Points-to Analysis . . . 15

2.4 Dimensions of Precision . . . 17

2.4.1 Object Representation . . . 17

2.4.2 Inter/Intraprocedural Analysis and Context-sensitivity . . 18

2.4.3 Field-sensitivity . . . 19

2.4.4 Flow-sensitivity . . . 21

2.4.5 Conservative Analysis and Analysis Precision . . . 21

2.5 Client Analyses . . . 22

2.5.1 Reachability Analysis . . . 22

2.5.2 Escape Analysis . . . 23

2.5.3 Side-Effects Analysis . . . 25

2.6 Conclusions . . . 27

3 Pre-study 29 3.1 Parallel Random Access Machines . . . 29

3.1.1 Work, Parallel Time, and Speed-up . . . 30

3.1.2 Oblivious PRAM Programs . . . 30

(6)

3.3.1 Parallel Processing Order . . . 32

3.3.2 Interpretation of Process-Order-Graph . . . 36

3.4 Experiments . . . 38

3.4.1 Benchmark Programs . . . 39

3.4.2 Metrics . . . 39

3.4.3 Results . . . 41

3.5 Conclusions . . . 43

4 Parallelized Points-to Analysis 45 4.1 Parallelized Simulated Execution . . . 45

4.1.1 Independent Branches (a) . . . 47

4.1.2 Polymorphic Calls (b) . . . 47

4.1.3 Context-sensitive Analysis (c) . . . 50

4.2 Implementations . . . 50

4.2.1 Baseline . . . 50

4.2.2 Threshold . . . 51

4.2.3 Avoid Redundancy . . . 52

4.2.4 Reduce Redundancy . . . 53

4.3 Experiments . . . 54

4.3.1 Experimental Setup . . . 54

4.3.2 Benchmark Programs . . . 55

4.3.3 Metrics . . . 55

4.3.4 Settings . . . 56

4.3.5 Reduced Work . . . 57

4.3.6 Results . . . 59

4.4 Pre-study vs Parallelized Points-to Analysis . . . 63

4.5 Conclusions . . . 63

5 Parallelized Client Analyses 67 5.1 Clients of Points-to Analysis . . . 67

5.2 Parallelized Clients . . . 69

5.2.1 On-line vs. Off-line . . . 69

5.2.2 Reachability Analysis . . . 70

5.2.3 Escape Analysis . . . 71

5.3 Experiments . . . 72

5.3.1 Experimental Setup . . . 72

5.3.2 Benchmark Programs . . . 72

5.3.3 Settings . . . 73

5.3.4 Metrics . . . 73

5.3.5 Results . . . 73

5.4 Conclusions . . . 78

(7)

6 Framework 79

6.1 Object-Oriented Frameworks . . . 79

6.2 Goal and Method . . . 80

6.3 Commonalities . . . 80

6.3.1 Data Structures . . . 82

6.3.2 Analyses . . . 83

6.4 Variation Points . . . 84

6.5 Discussion . . . 85

6.6 Conclusions . . . 86

7 Related Work 89 7.1 Goal Criteria . . . 89

7.2 Comparison Method . . . 90

7.3 Static Analysis Frameworks . . . 94

7.3.1 Monotone Data-Flow Framework . . . 94

7.3.2 PAG . . . 95

7.3.3 CoSy . . . 96

7.4 Points-to Analysis . . . 97

7.5 Parallelized Data-Flow Analysis . . . 100

7.6 Client Analyses . . . 101

7.6.1 Escape Analysis . . . 102

7.6.2 Side-Effects Analysis . . . 107

7.6.3 Summary of Client Analyses . . . 112

7.7 Conclusions . . . 114

8 Conclusions and Future Work 117 8.1 Summary . . . 117

8.2 Conclusions . . . 118

8.3 Future Directions . . . 119

(8)
(9)

List of Figures

2.1 Abstract syntax tree example. . . 10

2.2 Basic block graph example. . . 10

2.3 SSA graph example. . . 11

2.4 Call context graph example. . . 20

2.5 Reachability analysis example. . . 23

2.6 Context-sensitive escape analysis. . . 24

2.7 Escape analysis example. . . 25

2.8 Context-sensitive side-effects and purity analysis. . . 26

2.9 Different side-effects and purity scenarios used in Examples 4 and 5. 27 3.1 PRAM implementation of a na¨ıve worklist-based data-flow analysis. 32 3.2 Algorithm for creating a program graph. . . 33

3.3 Algorithm for creating a loop tree. . . 35

3.4 Algorithm for creating a process order graph (POG). . . 36

3.5 PRAM implementation of data-flow analysis based on POG. . . . 38

3.6 Algorithm for calculation processing order numbers. . . 40

3.7 Processing queue and node depth metrics for antlr and javacc. 42 4.1 Source code for independent branches example. . . 47

4.2 SSA graphs for independent branches example. . . 48

4.3 Source code for polymorphic calls example. . . 49

4.4 SSA graphs for polymorphic calls example. . . 49

4.5 Algorithm for creating and executing tasks independently. . . 51

4.6 Algorithm for inspecting tasks currently executed independently. 52 4.7 Algorithm for creating the decision table. . . 54

4.8 Results for the parallelized points-to analysis I. . . 60

4.9 Results for the parallelized points-to analysis II. . . 62

5.1 Points-to analysis and client analyses from an end user’s perspec- tive. . . 68

5.2 Dependencies between points-to analysis and its client analyses. . 69

5.3 Average speed-up vs. thread count. . . 77

6.1 Context-sensitive escape, side-effects and purity analysis. . . 81

(10)
(11)

List of Tables

2.1 Points-to SSA node types. . . 13

3.1 Benchmark information. . . 39

3.2 Speed-up S(p) of parallel analysis. . . 43

3.3 Efficiency E(p) of processor usage. . . 43

4.1 Benchmark information. . . 56

4.2 Polymorphic calls. Processed methods in the parallelized variants relative to the sequential points-to analysis. . . 58

4.3 Polymorphic calls and independent branches. Processed methods in the parallelized variants relative to the sequential points-to analysis. . . 58

4.4 Polymorphic calls. Settings for the three optimal variants and average speed-up. . . 59

4.5 Polymorphic calls and independent branches. Settings for the three optimal variants and average speed-up. . . 61

5.1 Benchmark information. . . 74

5.2 Speed-up for the reachability analysis. . . 75

5.3 Speed-up for the escape analysis. . . 76

7.1 Evaluation of the related work with respect to the goal criteria. . 93

7.2 Legend for Table 7.1. . . 94

7.3 Evaluation of the related work with respect to the goal criteria. . 115

7.4 Legend for Table 7.3. . . 116

(12)
(13)

Chapter 1

Introduction

Tools analyzing software systems assist developers in understanding these sys- tems. Such tools can detect relevant information about software, for instance, structure, dependencies, bugs and clues to other problems. Among other tech- niques, these tools use static program analysis. Typically, users repeatedly run static program analysis to update results after changes are made to the analyzed software. Naturally, users want results without great delay. If forced to wait too long every time the analysis is run, users will resort to other ways to remedy the problems.

There are also other tools supporting software development processes using static program analysis, e.g., test tools and optimizing compilers. Some test tools combine static with dynamic program analysis, to generate test cases.

Software testing is a crucial part of any software development process and can occupy half the budget for research and development in software development organizations [1]. Optimizing compilers use static program analysis results to produce faster and more resource-efficient code. Such tools use (static program) analysis less frequently than user-centric software development tools. Test cases do not need to be regenerated several times per day and highly optimized com- pilation is only needed for daily builds or even less frequently. However, the analysis time is still important.

Section 1.1 presents the goal and goal criteria used in this thesis. Section 1.2 motivates the thesis goal. Section 1.3 presents the contributions of the thesis.

Section 1.4 presents the thesis chapters for reading directions. Section 1.5 lists publications included in this thesis.

1.1 Goal and Goal Criteria

We aim at speeding up static program analysis in order to make software de- velopment and optimization more efficient.

The two types of usage scenarios for static program analysis, on-line in soft- ware development and off-line in testing and optimizations are somewhat dif-

(14)

ferent regarding their needs. A user in the user-centric scenario wants accurate information fast. The user may be willing to trade accuracy for speed. The off- line scenario is more dependent on accurate information, and may trade speed for accuracy.

From the above discussion, we define the following goal criteria:

1. Trade accuracy for analysis time.

We want to provide an architecture for program analysis allowing more freedom when changing the level of accuracy than currently possible.

2. The fastest analysis for each level of accuracy.

When trading accuracy for analysis time, there are partially ordered levels of accuracy. We want the fastest analysis for each level of accuracy. We consider this the most important goal criterion.

It is also important to have the possibility to rapidly adapt or change an ex- isting analysis [2]. For example, adapting an analysis implementation to include the possibility to trade accuracy for analysis time requires changing the analysis.

Further, finding solutions to new analysis problems requires the development of new analyses, or modification of existing analyses. The effort involved for mod- ifying or creating analyses should be small, i.e., development efficiency should be high. Therefore, we also consider the following criterion:

3. Development efficiency.

Increased development efficiency comes from support given in the develop- ment process in form of reuse and tools. We want a high level of support given to the development process to increase development efficiency and reduce development time.

1.2 Motivation

Software should be developed efficiently and with a minimum number of errors.

A short development time and the efficient use of existing resources lowers the overall cost of the development process. A lower error count in the developed software reduces the work of correcting them afterwards. If an error is cor- rected late in the development process or even after the software is completed its correction is likely to take more resources and time compared to errors cor- rected early in the process. Software developers who maintain a specific piece of software have to understand its structure and how it behaves at runtime.

Finding this out is usually a very complex task, even for small systems and if the developer is familiar with it. This is even harder for developers working with large software systems they are not familiar with. For example, complex- ity comes from dependencies between parts of a software system. If one part changes other parts may have to change as well. Understanding software sys- tems, their structure and how they behave at runtime is also very important for maintaining software, e.g., as regards correcting errors and adding, changing or removing features.

(15)

1.2. Motivation

Tools analyzing software systems assist developers understanding these sys- tems, which, in turn, can help them be more efficient in their development and maintenance tasks. Static program analysis provides these tools with approxi- mations of runtime properties for the analyzed systems. The usefulness of this information depends on its quality while the time required for performing the analysis grows both with size and complexity of the analyzed system and with the level of quality of its results, i.e., its accuracy. Naturally, users find it desir- able to have high analysis accuracy and short analysis time. They will not use tools that take too long to deliver results or that deliver results of unacceptable quality. This is also true for less user-centric tools, such as test-environments and optimizing compilers using static program analysis. They will not produce high-quality results if the quality of the underlying analysis results is poor and they will not be usable if taking too long to complete.

Traditional approaches used in these tools are sequential, i.e., they are con- structed to run on computers with one processor. With these approaches, it was possible to benefit from the increased computational power in new processors.

Until a few years ago, processor chip manufacturers were able to increase proces- sor performance by increasing clock speed and decreasing the size of transistors, thus fitting more transistors on the same chip area [3]. More transistors enable more advanced logic implemented in circuits. However, higher clock speed forces the transistors to switch faster, which leads to higher power consumption and more generated heat. Even though the effect of these issues can be reduced, they will grow unmanageable as clock speed increases. The modern approach is dif- ferent: single processor chips contain multiple cores, each having the processing power of previous single-core processors [4]. Clock speed is kept at levels where the heat generated is manageable. Major processor suppliers have released pro- cessors with 2, 4, and 6 cores and are currently developing processors with 8 and 16 cores [5, 3]. Standard desktop computers can even contain two of those processors, making it already possible to have 8 or 12 cores. It is currently not possible to ride the wave of performance improvements of single-core processors.

To speed up applications, independent tasks need to run in parallel, e.g., using multiple cores, allocating independent tasks to separate cores. However, this requires that the problems can be parallelized, i.e., that independent tasks are identified.

The quality of results from static program analysis depends on the level of detail in the analysis model used. A more detailed model enables higher quality results but is at the same time potentially more time-consuming to analyze. There are a number of known dimensions to vary the level of details, such as flow-, field-, and context-sensitivity, cf. Chapter 2. We aim to add a new dimension, by parallelizing approaches used in static program analysis, to decrease the required time to approximate runtime properties of software systems.

(16)

1.3 Contributions

This thesis presents approaches that enable data-flow based program analysis to make use of modern multi-core processors. The approaches are integrated into a framework for program analysis, enabling future reuse. The contributions of this thesis are divided into two parts:

1. We shorten the analysis time for static program analysis.

(a) We present a parallelized approach to solve data-flow analysis prob- lems. It is evaluated on a specific data-flow analysis; namely points-to analysis.

(b) We present a parallelized approach to data-flow analysis clients. It is evaluated on reachability analysis and escape analysis, which are based on points-to analysis information.

2. We present a framework for parallelized program analysis. The framework handles workflows of analyses, each producing partial results and with possible inter-analysis dependencies. The framework is instantiated with a parallelized points-to analysis, a parallelized reachability analysis using the points-to results, and a parallelized escape analysis using the points- to results and the reachability results. The framework can be used to implement other analyses with similar inter-analysis dependencies making it possible for the parallelized approaches to be reused in other client analyses.

1.4 Reading Directions

The chapters are listed and described as follows. The referenced papers are listed in Section 1.5.

Chapter 2, Static Program Analysis, presents important notions and concepts for static program analysis.

Chapter 3, Pre-study, presents a feasibility study of parallelized data-flow analysis, which is published in Paper I. For the parallelized approach to data- flow analysis to be successful, the analysis problems have to provide enough independent tasks to be executed in parallel. The chapter presents a static met- ric for the parallelism, which is evaluated on a set of benchmark programs. The results show promise for an implementation of parallelized data-flow analysis.

Chapter 4, Parallelized Points-to Analysis, presents an implementation of parallelized points-to analysis, published in Papers II and III. The parallelized approach is based on an already very efficient sequential algorithm for data-flow analysis. The sequential algorithm is presented in Paper V. The chapter presents three constructs that enable the creation of independent tasks and how these are used for parallelization. Its performance is evaluated on a set of benchmark programs on an eight-core machine.

(17)

1.5. Publications

Chapter 5, Parallelized Client Analyses, presents two parallelized analyses using points-to analysis information, reachability analysis and escape analysis, cf. also Paper IV. Their performance is evaluated on a set of benchmark pro- grams on an eight-core machine.

Chapter 6, Framework, presents the framework for static program analysis and its parallel components. The basis of the framework is presented in Paper V and the Licentiate thesis. The latter defines a sequential framework for static program analysis. The framework is instantiated with a parallelized points-to analysis and the two parallelized client analyses. The chapter also presents how the framework can be instantiated, i.e., how to instantiate the framework’s variation points.

Chapter 7, Related Work, presents previous work of others that is related to points-to analysis, parallelized points-to analysis, escape analysis, and frame- works for static program analysis.

Chapter 8, Conclusions and Future Work, presents the conclusions of the thesis, its contributions and gives some directions for future work.

1.5 Publications

This thesis is based on the contents of the following publications.

Paper I Marcus Edvinsson and Welf L¨owe. “A Parallel Approach for Solv- ing Data-Flow Problems”. Proceedings of the 20th IASTED Interna- tional Conference on Parallel and Distributed Computing and Systems (PDCS’08), IASTED, ACTA Press, November 2008.

Paper II Marcus Edvinsson and Welf L¨owe. “A Multi-Threaded Approach for Data-Flow Analysis”. Workshop on Multithreaded Architectures and Applications (MTAAP’10), IEEE, April, 2010.

Paper III Marcus Edvinsson, Jonas Lundberg, Welf L¨owe. “Parallel Data- Flow Analysis for Multi-Core Machines”. High Performance Embedded Architectures and Compilers (HiPEAC’11), ACM, January 2011.

Paper IV Marcus Edvinsson, Jonas Lundberg, Welf L¨owe. “Parallel Reach- ability and Escape Analysis”. Source Code Analysis and Manipulation (SCAM’10), IEEE, September 2010.

Paper V Jonas Lundberg, Tobias Gutzmann, Marcus Edvinsson, Welf L¨owe.

“Fast and Precise Points-to Analysis”, Information and Software Technol- ogy 51(10), p. 1428-1439, 2009.

Licentiate Marcus Edvinsson. “Towards a Framework for Static Analysis Based on Points-to Information”, Reports from MSI, 2007, Licentiate The- sis.

(18)
(19)

Chapter 2

Static Program Analysis

This chapter presents notions and definitions that are used later in the thesis, either to present our approaches and results, or related work.

The definitions and notions are mutually dependent. Unfortunately, it seems impossible to present them without forward-references. This problem is caused by the following relations: the analyses use data structures representing the analyzed program, the data structures are tailored for specific usage in the analyses, and the analyses produce analysis results, which are also referred to by the data structures.

Section 2.1 informally introduces the very basics of static program analysis.

It focuses on data-flow based analysis, but it also presents three related tech- niques. Section 2.2 presents program representations used in static program analysis and discusses their strengths and weaknesses. It also motivates why we chose a particular program representation for our analyses. Section 2.3 dis- cusses data-flow analysis in general and points-to analysis in particular. The monotone data-flow framework is presented as the foundation of data-flow anal- ysis. The traditional algorithms for points-to analysis are presented together with the algorithm we use for points-to analysis, called simulated execution.

Section 2.4 presents four dimensions of precision: object representation, flow- sensitivity, field-sensitivity, and context-sensitivity. It is possible to vary each of these dimensions individually to get a particular level of precision. Section 2.5 presents three client analyses of points-to information, reachability analysis, escape analysis, and side-effects analysis.

2.1 Introduction

Static program analysis is used to deduce information about a program at com- pile time. The program is not executed as is done in dynamic program analysis.

Instead, its source (or binary) code is interpreted abstractly without concrete input. The extracted information can be used to answer various questions about runtime properties of the program. Such questions include, among many others,

(20)

(i) which parts of the program will never be executed, or (ii) what memory usage patterns a program shows. The parts of the program that can be identified as dead code in question (i) may be removed during the compilation process, thus saving space in the compiled code and reducing execution time. The answers to question (ii) allow the optimization of the program’s memory usage. For instance, in a managed memory execution environment, the garbage collector can ignore objects that have a limited lifetime. These objects may be collected automatically, thus saving time and resources.

Information retrieved from static program analysis usually answers questions about what may happen when the program is executed. For the results to be useful, e.g., in automated program optimization, it is important that the retrieved information is conservative and accurate. The analysis is conservative if it intends to answer for all possible executions, regardless of specific input data. The accuracy is a measure of how well an analysis avoids including false information, i.e., information that is not valid for any execution of the program.

For important analysis questions, the analysis time grows exponentially with the required accuracy.

Nielson et al. [6] present four theoretical frameworks for static program anal- ysis: data-flow analysis, constraint-based analysis, abstract interpretation, and type and effect systems. This thesis primarily focuses on data-flow analysis.

Data-flow analysis makes analysis values flow through one or several graphs representing the analyzed program. Analysis values, i.e., results of a particular analysis, are properties about a program. The graphs represent the analyzed program – statements are nodes and edges represent control- and data-flow.

Different node types are used for different types of statements. Two types of graphs are common: method graphs and program graphs. Method graphs represent each method in the program. The program graph merges all method graphs to represent inter-method dependencies. Edges are added to connect them where there are calls between methods. Data-flow approaches propagates analysis values between the nodes of the graphs along its edges. The analysis is data-driven: A node is processed when there are new input values and the results are pushed along the edges to other nodes. Each node type has a certain effect on the analysis values. Calculation stops when a fixed point is reached, i.e., when no new information is produced from calculating the effect of any of the statements in the analyzed program. A more exhaustive presentation of the data-flow analysis approach is given in the presentation of the monotone data-flow framework in Section 2.3.1.

Constraint-based analysis constructs a set of constraints from the analyzed program. For each program, constraints are generated from rules based on the syntax and semantics of its programming language. The generated constraint system is solved by transforming iteratively these constraints, i.e., by applying rewrite rules until a fixed point.

Abstract interpretation is used to reason about and prove correct the actual calculation of analyses. It can be applied to both data-flow and constraint-based analysis. It allows reasoning about correctness and approximations to reduce the number of calculations needed to reach the final results, i.e., fixed points.

(21)

2.2. Program Representations

Type and effect systems consist of two parts: an effect system and an an- notated type system. The effect system gives information about what happens when a statement is executed, e.g., which exceptions occur, what errors occur or which files are modified. The annotated type system finds properties about the program based on its statements. In both, rules are mapped to statement types.

Analysis results are calculated with an inference algorithm using substitution and unification.

Even though these techniques are quite different, they have some things in common. In the subsections below, we present the most important characteris- tics of static analysis. This thesis is mostly concerned with a specific data-flow analysis, points-to analysis. Therefore, some parts are specifically biased to- wards points-to analysis.

2.2 Program Representations

There are a number of data structures that represent a program and that are used in static analysis. Here, we present four of them: Abstract Syntax Tree (AST), Basic Block Graph, Static Single Assignment (SSA) graph, and Sparse SSA graph. We chose to use a variant of Sparse SSA graphs for program repre- sentation because it is explicit with regard to value definitions and usage, and it is optimized for points-to analysis, i.e., all irrelevant information is removed.

2.2.1 Abstract Syntax Trees

The abstract syntax tree (AST) is a reduced form of the parse tree useful to represent program language constructs [7]. It is a labeled, directed, or- dered tree that represents a program by nodes representing operators/state- ments and children nodes representing operands [8], either being other sub- operators/statements or variables/constants. While the parse tree has nonter- minals as internal nodes, the abstract syntax tree has programming constructs (operators). Many programming constructs in an AST have corresponding non- terminals in the parse tree, but for some nonterminals this mapping cannot be made. They may exist only to ensure, for instance, correct precedence. An example of an AST is given in Figure 2.1.

An AST does not explicitly represent data dependencies in a program, which is an important aspect in data-flow analysis. Therefore, we decided not to use the AST data structure for program representation in our analysis.

2.2.2 Basic Block Graphs

A basic block is a sequence of statements in a programming language, such that the control flow will always start with the first statement, continue through the sequence, and end with the last statement [8]. There are no jumps to or from the block other than to the first statement and from the last statement in the sequence. A basic block graph is a graph where nodes are basic blocks and edges

(22)

Grammar

Assign ::= Variable ‘=’ Expr Variable ::= literal /* name */

Expr ::= Number Op Number Number ::= integer | float Op ::= ‘+’ | ‘-’ | ‘*’ | ‘/’

a = 2 + 7 Example statement

AST

Assign Variable

7 Op

‘+’

Number 2

Number Expr

‘a’

Figure 2.1: Abstract syntax tree example.

int i= 0;

int c= 0;

while (i<10) { c= c + i;

i++;

}print(“Result= ” + c);

int i= 0;

int c= 0;

while (i<10) c= c + i;

i++;

print(“Result= ” + c);

Source code Basic Block graph

Figure 2.2: Basic block graph example.

represent the control-flow between the basic blocks. An example of a basic block graph representation of a program is shown in Figure 2.2.

Basic block graphs do not represent data dependencies in a program, which is an important aspect in data-flow analysis. Therefore, we decided not to use the basic block graph data structure as the program representation in our points-to analysis.

2.2.3 SSA Graphs

In a Static Single Assignment (SSA) graph, nodes are instructions and edges are data-flow (over local variables) connecting operations/statements. In this way, the relation between the definition and usage of variables (def-use relation) is modeled explicitly, where a variable is statically defined only once, i.e., only one instruction defines a value/variable. Nodes have ports, one for each argument of an operation and one for each result. Nodes have a fixed number of incoming edges, one for each port.

The value of a variable may be affected by different branches in the program.

Since the def-use relation is modeled explicitly, these values need to be merged when branches merge. This is done by introducing artificial merge operations

(23)

2.2. Program Representations

+

φ

<

call print() 1 10 0

+

“Result= ”

Control-flow Data-flow Constant Operation

Basic block Legend

int c= 0;

int i= 0;

while (i<10) { c= c + i;

i++;

}

print(“Result= ” + c);

Source code SSA graph

φ

Figure 2.3: SSA graph example.

represented by so-called φ-nodes, which have an arbitrary number of in-ports – one for each branch to merge. It merges these values into one result, which may have several uses and, hence, outgoing edges. This way, def-use relations are preserved.

SSA graphs, as presented in [9, 10], are primarily used as intermediate pro- gram representation in the analysis phase of compilers. Variants have been used for detecting program equivalence and inherent parallelism in imperative programs [9].

The example in Figure 2.3 shows the same source code as in Figure 2.2 and its SSA graph representation. The circles and ovals are constant values, solid boxes are operations, solid lines without arrows represent data-dependencies, solid arrows are control-flow. Data-dependencies have a direction – from out- ports to in-ports. The dotted rectangles are basic blocks, cf. Figure 2.2. The local variables c and i, initialized to 0, are represented by data dependency edges. The same constant value is reused in the graph. The while loop in the source code causes two data-flow loops in the SSA graph. Both statements within the while loop may get their in-values from either the two lines preceding the loop or from the statements within the loop. The two φ-nodes make sure that the values from the statements preceding the loop and the values from

(24)

within the loop are merged, and that the def-use relations are preserved. The basic block structure is required to capture φ and control-flow operations.

This is a suitable data structure for data-flow analysis. However, it contains information that particular analyses may abstract from.

2.2.4 Sparse SSA Graphs

A sparse SSA graph is an SSA graph, containing only node- and edge-types necessary to perform a certain analysis/transformation task. Nodes that do not directly contribute are removed as are edges from or to such nodes. This reduces both space and time complexity associated with the SSA graph structure, both regarding graph construction and node access. There is less information to store and the analysis algorithms need not consider as many nodes as in a complete SSA graph. However, all SSA properties are maintained and the graphs still contain enough information to complete the intended analysis/transformation task.

The SSA graphs used in this thesis are sparse SSA graphs called Points- to SSA [11], tailored to efficiently perform points-to analysis. There is one graph per method which can be seen as a semantic abstraction of that method.

All operations not directly relevant to reference computations are removed, e.g., operations and edges related to primitive types. The Points-to SSA is used in this thesis since it models the necessary features of the analyzed program, i.e., the necessary elements related to reference calculations, in a memory efficient way. At the same time, it is possible to construct efficient and precise algorithms using the data structure. The most important node types used are listed in Table 2.1. For a more complete presentation of Points-to SSA we refer to [12, 13].

2.3 Data-flow Analysis

The theory of data-flow analysis is divided into three parts: an introduction to monotone data-flow frameworks, a presentation of simulated execution, and a presentation of points-to analysis. The monotone data-flow framework is the theoretical basis for data-flow analysis. It provides the necessary elements to create an analysis and how to ensure that a fixed point for analysis values is reached. However, it does not provide any mechanisms that ensure fast compu- tation. Simulated execution is a fast algorithm for calculating analysis results.

It is used in the points-to analysis we use in this thesis. Points-to analysis is an analysis that calculates object reference information of object-oriented pro- gramming languages.

2.3.1 Monotone Data-Flow Framework

The monotone data-flow framework is a framework that solves data-flow prob- lems [6]. The analysis is performed on a graph representation of a program, where the nodes are statements and the edges represent the data-, and/or

(25)

2.3. Data-flow Analysis

SSA

Nodes Description Entry Method entry point Exit Method exit point

AllocC Creates new instance of class C

Loadf Loads a value from the field f of an object Storef Stores a value into the field f of an object

BagLoad Loads a value from an array (or another container) BagStore Stores a value to an array (or another container) φ Merges multiple input values into one output value MCallm Calls a.m(v1, . . . , vn) target method m in the

(sub-, super-)type of a

PCalltgts Calls a.m(v1, . . . , vn) in all targets m ∈ tgts in the (sub-, super-)type of a

SLoadf Loads a value from static field f SStoref Stores a value to static field f SBagLoad Loads value from a static array SBagStore Stores values to a static array

Table 2.1: Points-to SSA node types.

control-flow between the statements, e.g., a Basic Block graph or an SSA graph.

The graph has to be connected, otherwise there will be nodes that cannot be analyzed. The analysis uses a fix-point algorithm to find the least solution of the specific problem corresponding to a certain instantiation. An instantiation consists of five analysis elements: (1) a combination operator, (2) a control- or data-flow, (3) a set of starting nodes, (4) initial analysis information, and (5) a set of transfer functions. These will be explained individually in the remainder of this section.

The analysis starts in the start nodes (3) with specific initial analysis values (4) as input. Each of the start nodes contributes to the analysis result with the result of a transfer function (5) associated with that node’s type. The output analysis result of the start node has now changed and, hence, also the input values of the start node’s successor nodes. The direction of the analysis is de- fined by the control- or data-flow. It can be a forward analysis, propagating values along the flow, or a backward analysis, propagating values in the reverse direction. Now, these successor nodes’ transfer functions (5) calculate the con- tribution of these nodes on the analysis result. Each node has an analysis result associated with it. The analysis result can be attached to the out-port of the node, i.e., the out-edges. The values on the out-ports are propagated to the in-ports of the successor nodes. When a node’s in-port value changes, its trans- fer function is recalculated. Values are merged with the combination operator when multiple values are available as input to a node.

An instantiation of the framework needs a definition of the property space, i.e., which analysis results are possible, as well as a combination operator. The

(26)

combination operator is used to merge analysis results propagated on in-edges to a φ-node. Different analysis results may be propagated on each of the incoming edges and merged. The property space should, generally, be a complete lattice.

It is also possible that the property space is a partially ordered set that fulfills the ascending chain condition. Descriptions of these three concepts follow.

A Complete Lattice is a partial ordered set, with the restriction that each subset should have a least upper bound and a greatest lower bound. It should also have a least element ⊥ and a greatest element >.

The Least Upper Bound of two elements s, s0 of the partial ordered set S is the lowest element in S such that it is greater than each of the elements s, s0. The dual is called greatest lower bound.

The Ascending Chain Condition is given for a partially ordered set if every ascending chain of elements x0 ≤ x1 ≤ . . . eventually stabilize. The ascending chain stabilizes if there is an m such that am = an for all m < n. All partially ordered sets with finite size fulfill the ascending chain condition.

The analysis can also be performed as a backward analysis, instead of the standard forward analysis. The flow element of the instantiation defines the flow.

In a backward analysis the calculations transfer results against the edges in the control-flow graph; the transfer functions use the values on the nodes’ out-ports as input and transfer the result to their in-ports. The transfer functions need to be monotone to ensure that the analysis algorithm finishes. This means that the analysis result may only become larger in the lattice, i.e., more imprecise after a call to a transfer function. It is not possible to remove a previously added analysis result.

The described approach to solve data-flow problems only works on toy lan- guages, since it describes an intraprocedural analysis; it is not able to analyze the data-flow between procedures and functions. It is possible to make addi- tions and make the analysis interprocedural, i.e., acknowledge procedure and function calls in the analyzed language. When introducing procedure calls into the analysis, a new type of node is introduced, the call nodes. The procedures define start and exit nodes. The call node is connected to the called procedure’s start node and the procedure’s exit node back to the call node. There are two transfer functions for the call node and two transfer functions for the proce- dure. The call node’s transfer functions correspond to calling the procedure, and returning from the procedure, respectively. The procedure’s two transfer functions correspond to entering and exiting the procedure body, respectively.

This structure does not ensure that analysis results from one call node are only returned to that particular call node. Since the return node has many call nodes as successor nodes, these will be updated as well. Even though they are unrealizable paths, they still preserve the conservative property of the analysis. Context information reduces the number of unrealizable paths that are analyzed. A context can be formulated in many ways, and the simplest

(27)

2.3. Data-flow Analysis

is an encoding that enables only valid paths to be analyzed, e.g., by encoding from which call node the call came. Each context that is valid for an analyzed method is mapped to the analysis results for that method. This ensures that the analysis results are not mixed and that only valid paths are analyzed, which can only be approximated.

2.3.2 Simulated Execution

Simulated execution is an algorithm for performing data-flow analysis efficiently, it is introduced in [13]. The difference from previous approaches lies mainly in the way the analysis values are propagated between methods/procedures. This method simulates an execution of the analyzed program, in the sense that it follows the method calling sequence as it would occur when the program is ex- ecuted. The analysis of a method m is interrupted when a call to method n is reached. The analysis of method m continues when the analysis of method n is completed. The results of the analysis of method n are used when the analysis continues with method m. A key issue to solve for this approach to be suc- cessful is to find appropriate conditions for when to stop processing (recursive) calls. If the analysis is not interrupted it will iterate endlessly when there are recursive calls in the analyzed program. If an analysis of a method using specific input values does not provide any new analysis results, the method will not be analyzed using the specific input values again. This ensures that the analysis terminates, even for recursive calls.

Like classic data-flow analysis, the simulated execution iterates over loops to stabilize them before continuing with following parts of the program. Inner loops are stabilized before outer loops. A loop is stabilized when the analysis reaches a fixed point over the analysis values in the loop.

2.3.3 Points-to Analysis

Points-to analysis is a static program analysis that extracts reference informa- tion from a given input program. Points-to analysis targets object-oriented languages, but there are similar analyses, e.g., alias analysis, concerned with other programming paradigms, such as functional and imperative programming languages.

In an object-oriented language, objects are targets of method calls and field references. At runtime, a method call has a number of in-parameters that each holds exactly one object representing the target object and the method arguments, respectively. For static methods the target object is statically known and this needs not be calculated by the analysis. For each method call and field reference, points-to analysis finds a set of abstract objects that may be targets or passed as arguments.

(28)

Traditional Algorithms

Points-to analysis has existed for some time, with the origin in alias analysis for imperative languages, such as C. Here, we present two of the algorithms that are considered traditional for points-to analysis Analysis values typical for points-to analysis are discussed, as well.

The two algorithms that are considered traditional, Andersen’s and Steens- gaard’s, are often referred to as the baseline approaches in literature. Even though the two algorithms target programs written in the programming lan- guage C, their approaches can be adapted to other languages as well.

Andersen’s algorithm is a points-to analysis targeting the C programming language [14]. It is inter-procedural, flow-insensitive, and context-insensitive. It is constraint-based and includes two major steps: creating a constraint system and solving the constraint system. All statements in the language contribute to the constraint system. The analysis is a whole-program analysis on a static call graph, which is a graph with methods as nodes and calls between methods are edges. The analysis results consist of method summaries as well as program point results. A method summary is an abstraction and approximation of the analysis results of a method. Specific analysis results are quickly calculated from specific input values. Method summaries are used to speed up the analysis and save memory. When a method is called many times this approach reduces the quality of the results and the algorithm may separate different calling contexts instead of creating summaries. The analysis result is a points-to graph, where nodes model variables and abstract objects, i.e., heap-allocated memory, and edges model the relation points-to. A node may point to many nodes and may be pointed to by many nodes.

Steensgard’s algorithm is an almost linear solution to the points-to problem of the C programming language. It is a constraint-based algorithm that uses type inferences to solve the constraint system [15]. Most of the approaches taken in the algorithm are identical or similar to Andersen’s approach. The analysis result is a points-to graph, where nodes model variables and abstract objects and edges model the relation points-to. The difference to Andersen’s approach is that nodes may only have one out-edge and nodes may represent an arbitrary number of variables. This reduces the number of nodes and edges and enables the algorithm to perform in almost linear time and memory, while obviously sacrificing precision.

Analysis Values

The result of a points-to analysis is two-fold, a points-to graph and a points-to decorated program graph. The precision of the analysis result is influenced by the sensitivity of the analysis. A typical field-sensitive, object-sensitive points-to graph has objects and fields as nodes and the edges represent the has and refer- to relations. An object node has fields, and field nodes refer-to objects. The analysis values used in the points-to analysis, which decorate the program graph, are sets of abstract objects that may occur in different parts of the analyzed

(29)

2.4. Dimensions of Precision

program. When the analysis is finished there are analysis values on each ingoing edge and outgoing edge, or in the case of Points-to SSA representation, on each in-port and out-port.

2.4 Dimensions of Precision

It is expensive, both in time and space, for an analysis to be precise. In order to be efficient, an algorithm may have to sacrifice precision. The trade-off between analysis efficiency and the precision of its result is influenced by how detailed the analysis models programs and their execution. Here, we present four dimensions of precision, i.e., how analysis models programs and their execution.

The dimensions are object representation, context-sensitivity, field-sensitivity, and flow-sensitivity. Is is possible to trade accuracy for analysis time by choosing different levels in these dimensions. The points-to analysis used in this thesis uses the allocation name schema, and it is flow-sensitive and field-sensitive, but not completely conservative, since there is no handling of dynamic class-loading or native method calls.

2.4.1 Object Representation

Program statements that create objects during runtime are called syntactic cre- ation points. For instance, the Java statement new java.util.Vector(); cre- ates a runtime object of the type java.util.Vector. Other examples of con- structs which create objects are: dynamic class loading, object cloning, memory allocation and object de-serialization.

Static analysis reasons about the program without executing it and needs to have an abstraction of runtime objects. It is not possible to model each runtime object separately, because of the impossibility to decide (in the general case) how many times a certain statement is executed. Therefore, all runtime objects created are mapped to an abstract object. Different mappings can be used for defining abstract objects, referred to as name schema, and some of the existing name schema being as follows:

Class schema Objects of the same classes are mapped to abstract objects.

Allocation schema Objects created at the same syntactic creating points are mapped to abstract objects.

Exclude strings schema The allocation schema is used for all objects, except for objects instantiating string classes.1 The class schema is used for these objects.

Context-sensitive schema The allocation is context-sensitive, i.e., a com- bination of call context (cf. Section 2.4.2) and syntactic creation point defines an abstract object.

1In the Java programming language we include, for instance, java.lang.StringBuffer and java.lang.String.

(30)

Object-sensitive schema is a specific context-sensitive name schema using the object-sensitive context definition.

2.4.2 Inter/Intraprocedural Analysis and Context-sensitivity

In static analysis of imperative and object-oriented languages, there is a separa- tion between intra- and interprocedural analysis. It is necessary to specify how each method/procedure is analyzed separately, i.e., intraprocedural analysis, as well as how the analysis handles calls between methods/procedures. Per- forming intraprocedural analysis with high precision is not as hard and resource consuming as adding the interprocedural analysis aspect. The complexity grows when methods/procedures are connected through calls. Two basic ways of con- necting these method graphs to represent method calls are inlining and graph connecting; inlining creates new copies of the graphs and graph connecting cre- ates a connection between call nodes in the methods and target method graphs.

The inlining method results in an exponential explosion of method copies for non-recursive programs and does not terminate for recursive programs. The analysis is now reduced to an intraprocedural analysis, since the whole program can be seen as one huge method and is represented by a single graph. The graph connecting method also results in a single graph that can be analyzed using intraprocedural techniques but does not explode in space as the inlining method does. However, the analysis will be very imprecise, since it is not guar- anteed that the analysis results from one method execution only comes back to the caller. The analysis results of many calls are mixed, and this degrades the analysis precision. Other techniques used to get better precision, such as call contexts, are described below.

Call Context-sensitivities

Call contexts are used to overcome the shortcomings of both the inlining and the graph-based approaches. A call context is a static abstraction of the call stacks occurring when executing a method to distinguish calls to a specific method.

Abstraction can be based on, for instance, the k last call stack entries, or the arguments passed to the called method. In a context-sensitive inter-procedural analysis, all dynamic method calls are mapped to finitely many abstractions according to a so-called call-context schema. The mapping will influence the number of contexts and thus the space, time, and precision properties of the analysis. A call context schema that produces a large number of call contexts will theoretically have a higher precision, but takes more time to perform and requires more memory. Some of the different call context schemata that are defined in literature:

Insensitive No call context definition is used. All calls to the method m are merged, i.e., there is only one analysis abstraction for all calls to m.

(31)

2.4. Dimensions of Precision

CallString A call context is defined using the call history, i.e., the return address entry of the call stack. A finite number k of top stack frames are considered.

Object A call context c is defined for each abstract object o a certain method m is called on, i.e., c → (o, m).

This A call context c is defined for each set of abstract objects Om a certain method m is called on, i.e., c → ({o|o ∈ Om}, m).

ObjectArgs Same definition as in Object, but here all positions in the argu- ment list are used.

ThisArgs Same definition as in This, but here all positions in the argument list are used.

Call Context Graph

The call context graph is a directed graph G = (C, E), where C is the set of call contexts and E is the set of edges connecting call context nodes (ci, cj) such that ci contains a call to cj. A cycle occurs if there are recursive calls between call contexts in the program.

The context call graph has a starting node, which is the context that is called by the environment that executes the program. In Java, this is the call to the main() method done by the Java Virtual Machine (JVM). The call context graph is constructed during points-to analysis. When a new call context is reached during the analysis, a node with this call context associated is added to the call context graph. For each call context that is reached during the analysis, an edge between it and the call context that calls it is added to the call context graph. In the case of a polymorphic call, this results in an edge for each of the possible target call contexts.

Example 1 Figure 2.4 shows an example of a call context graph. The call context graph shows the result after an object-sensitive analysis, i.e., using the Object call context schema The objects are referred to by the source code line that created them, i.e., using the allocation name schema. The main method is static. An artificial static object is used for static calls. In Java, the constructors of a class are called when a new object is instantiated. The constructor method is named init in this example.

2.4.3 Field-sensitivity

An object-oriented program uses objects with fields., which may refer to other objects. These are the references calculated by points-to analysis. An analysis that models both objects and its fields is field-sensitive. In contrast, an analysis that does not model object-fields is known as a field-insensitive analysis. A field- insensitive analysis merges fields and does not separate accesses to the different fields of the same object. The field-sensitive analysis is a more precise model than the field-insensitive one, and it enables the analysis to be more precise.

(32)

1 c l a s s A {

2 s t a t i c B b f i e l d ;

3 Z m1 ( ) {

4 B b= new B ( ) ;

5 Z c= new C ( ) ;

6 Z d= new D ( ) ;

7 c a l l ( c , b ) ;

8 c a l l ( d , b ) ;

9 r e t u r n c ;

10 }

11 v o i d c a l l ( Z z , B b ) {

12 z . g ( b ) ; ,

13 }

14 }

15 c l a s s B {

16 L i s t l ;

17 B b ;

18 v o i d s e t L ( L i s t l 2 ) {

19 t h i s . l=l 2 ;

20 }

21 v o i d s e t B (B b2 ) {

22 t h i s . b=b2 ;

23 }

24 }

25 a b s t r a c t c l a s s Z {

26 v o i d g (B b ) ;

27 B getB ( ) ;

28 }

29 c l a s s C e x t e n d s Z {

30 v o i d g (B b ) {

31 L i s t l= new L i s t ( ) ;

32 B b2= new B ( ) ;

33 b . s e t L ( l ) ;

34 A . b f i e l d = b2 ;

35 }

36 B getB ( ) {

37 B b= new B ( ) ;

38 B b2= new B ( ) ;

39 b2 . s e t B ( b ) ;

40 r e t u r n b ;

41 }

42 }

43 c l a s s D e x t e n d s Z {

44 v o i d g (B b ) {

45 B b2= new B ( ) ;

46 b . s e t B ( b2 ) ;

47 }

48 B getB ( ) {

49 B b= new B ( ) ;

50 B b2= new B ( ) ;

51 b . s e t B ( b2 ) ;

52 r e t u r n b ;

53 }

54 }

55 p u b l i c s t a t i c v o i d main ( ) {

56 A a= new A ( ) ;

57 Z z= a . m1 ( ) ;

58 }

(<# >, <method>)obj

<type>

(null, main)A

(56, init)A (56, m1)A

(4, init)B (5, init)C (6, init)D

(56, call)A

(5, g)C (6, g)D

(31, init)List (32, init)B

(4, setL)B (4, setB)B

(45, init)B Legend

# - object created on line # method - called method

type - type of object

Figure 2.4: Call context graph example, Example 1.

(33)

2.4. Dimensions of Precision

2.4.4 Flow-sensitivity

A flow-sensitive analysis ensures that the data- and control-flow is preserved in the analysis. This means that operations are never influenced by operations that occur later in all executions of the program. The data-flow is dual, but instead of control-flow it models data-flow.

2.4.5 Conservative Analysis and Analysis Precision

To be conservative when analyzing a program, we need to follow and analyze all possible execution paths. For example, we cannot select only one branch in an if-statement, since we have to assume that any of the possible paths may happen. If a method call has several possible target methods, i.e., through method polymorphism, all these possible methods need to be analyzed in order for the analysis to stay conservative.

In a conservative analysis, the path followed during analysis over-approxi- mates all execution paths. The number of analyzed paths may be reduced by more precise points-to analysis. With higher precision, the number of abstract objects is reduced in certain program points, e.g., for polymorphic calls. This may mean fewer target methods, since the set of target methods depends on the set of target objects.

Some features of modern object-oriented languages like Java and C# hin- ders a conservative analysis. The existence of dynamic class-loading and native method calls causes problems when aiming for conservative analysis. Dynamic class-loading creates new objects from types specified by a string value. Deter- mining the value of a string is generally not possible. Thus, it is not possible to decide types of created objects. The intrinsic details of native methods are not known during the analysis, either. The name of a method, its return type and its argument signature are known. A call to the native method may in principle have any side-effect. This makes it impractical to keep an analysis conservative in the presence of dynamic class loading and calls to native methods.

The precision of the analysis result is a measure of how close the analysis is to the exact solution. Assume that the set G contains the analysis results, i.e., objects, of a correct and exact analysis and that A contains the analysis results of a particular analysis. Precision is defined as P = |G||A|. Conservative analyses ensures that the recall of the analysis is 100% at all times, i.e., all correct answers are part of the analysis result. To compare the precisions of two conservative analysis results with each other, it is only necessary to compare the sizes of the analysis results. A smaller result set contains fewer elements that are included because of imprecision in the analysis.

It is desirable that the results an analysis provides hold for all possible execu- tions of the analyzed program. An analysis is called conservative if the analysis result contains all analysis values that may be part of the exact result of a particular program execution for a particular input. The simplest conservative analysis would return all possible analysis values as the analysis result. Ob- viously, this is not very useful, since we have learned nothing from this, even

(34)

though it is correct. The challenge in static analysis is to produce a result that is precise but still conservative.

2.5 Client Analyses

Three client analyses using data-flow analysis (here points-to analysis) results are presented: reachability analysis, escape analysis, and side-effects analysis.

Reachability analysis and escape analysis are parallelized to get shorter analy- sis times (results are reported in Chapter 5). Escape analysis and side-effects analysis are studied in Chapter 6 to find commonalities and variation points for our static program analysis framework.

2.5.1 Reachability Analysis

Reachability analysis calculates the reachable call contexts of each node (call context) in the call context graph. The result is a mapping ci → {c | c ∈ C ∧ c ∈ Reachable(ci)}, where C is the set of call contexts in the analyzed program and Reachable(ci) denotes the reachable contexts of the call context ci. The associated set is the set of call contexts that may be called transitively from the call context ci.

Reachability analysis can be solved as a data-flow problem. Each call context node is associated with a set of call context nodes (called reachability set ), which are reachable from this node. Each reachability set is initialized with the node it is associated with, i.e., the mapping ci→ {ci}. The algorithm is a backwards data-flow analysis, i.e., the data propagation goes against the direction of the edges in the call context graph. The propagation starts from the call context leaf nodes and continues until a fixed point is reached. When two sets are merged, the set union operation is used. The fix-point iteration is needed if there are cycles in the graph, i.e., if there are any recursive calls in the program.

An improvement2of this algorithm that may save time and space is to merge the nodes that are on a cycle into one (virtual) node. The nodes that are on a cycle will have the same reachability; from one node it is possible to reach any of the other nodes in the cycle. We can calculate the cycles as the Strongly Connected Components (SCC) for the graph. Each SCC is then considered a virtual node that represents each cycle in the graph. This reduces the number of nodes that need to be processed and the context information needs only be associated with virtual nodes, instead of separate representations for each node.

The effect of this optimization on time and space is related to the number of nodes included in a cycle in the call context graph of the analyzed program.

There is a linear solution known that calculates the SCC for a graph [16].

Example 2 An example of reachability analysis results is shown in Figure 2.5.

The reachability analysis has been performed on the call context graph illus- trated in Figure 2.4. The reachability analysis result is a mapping from a context

2This is only possible if the graph is reducible.

(35)

2.5. Client Analyses

(56, call)A (56, call)A

(5, g)C (5, g)C (6, g)D (6, g)D

(31, init)List (32, init)B (4, setL)B

(4, setB)B(45, init)B Reachability analysis results

{ }

{

{ }

(5, g)C (6, g)D

(31, init)List (32, init)B

(4, setL)B

(4, setB)B(45, init)B

}

...

...

Figure 2.5: Reachability analysis example, Example 2.

ctx to a set of contexts that are reachable in the call context graph, starting in call context node ctx. A context is considered reachable from itself, i.e., it is included in its own reachability set. We can see in Figure 2.4 that from node (6, g) it is possible to reach the nodes (4, setB), (45, init).

2.5.2 Escape Analysis

Escape analysis is a static program analysis that finds abstract objects that escape from the methods of a program. We say that an object escapes from a method m if and only if it is created during the execution of m and is still accessible after the execution of m. Escape analysis has, for instance, been successfully applied to the stack-allocation of objects [17, 18, 19]: a non-escaping object o, which is – outside any loop – heap-allocated in a method m, can as well be allocated on the stack-frame of m avoiding the garbage collection of o.

Our escape analysis is standard, cf. [20, 17], with two exceptions. Firstly, our definition uses points-to analysis as a black box allowing the replacement of a context-insensitive points-to analysis with any of its context-sensitive variants.

Secondly, the generalization to a context-sensitive escape analysis is straight- forward: instead of finding the escaping objects of a method m in general, we determine them for each context ctx of m. The different context definitions from points-to analysis apply, cf. Section 2.4.2.

Formally, we define the set Escaping(cm) of abstract objects escaping from a context c of method m as specified in Figure 2.6. The set (Alloc, Alloc?) of abstract objects that are escape candidates in cm are all objects that are allocated in the method m (2.2) or in a method m0 of a context zm0 reachable from cm (2.6). The predicate ¬Alloc?(cm) refers to all abstract objects not allocated in the contexts reachable from cm. We refer to this set of abstract objects as the enter set. The allocation schema used is context-insensitive. C is the set of methods used by the analyzed application. Allocs(cm), Returns(m) and Calls(m) denote the set of Alloc-, Return- and Call -nodes in a specific method m, respectively. Objects assigned to any accessible field of an object o are returned by AssignedT o(o), while AssignedT o?(O) operates on a set of objects O. The set of accessibleField s of an object o includes all its public fields,

References

Related documents

In the field of English literature, critical theory/discussion/analysis — especially if more than one viewpoint is applied — provides the opportunity for students to

Academic &amp; cultural expression Barriers to parties Media self−censorship Party ban Vote buying Elections multiparty CSO repression Discussion for women Harassment of

A good representation scheme can help you save a lot of time if you work with a really big project, and want to know why something was done in a specific way, or if you are

Most importantly, we consider Juliet testcases as good benchmarks to test the tools because we found that it fulfills the criteria (seven desiderata) listed and explained in 3.5

As a whole, this thesis drives you through a systematic literature review to select best suitable way to perform sentiment analysis and then performing Twitter data sentiment

Measurement related to acoustic properties of Porous Material (in order to use as input in the VA One software) by Impedance Tube and based on a method presented by University

We have presented two client analyses, the escape analysis and the side-effects analysis. We have identified the commonalities and the variation points be- tween these two

As COTS tools are developed without a specific software application in mind, the identified tool will have to be configured to look for errors with regard to the code standard