
Pointer Analysis for C Programs Through AST Traversal Marcio Buss∗ Stephen A. Edwards† Bin Yao Daniel Waddington Department of Computer Science Network Platforms Research Group Columbia University Bell Laboratories, Lucent Technologies New York, NY 10027 Holmdel, NJ 07733 {marcio,sedwards}@cs.columbia.edu {byao,dwaddington}@lucent.com Abstract dismantling the program into increasingly lower-level rep- resentations that deliberately discard most of the original We present a pointer analysis algorithm designed for structure of the source code to simplify its analysis. source-to-source transformations. Existing techniques for By contrast, source-to-source techniques strive to pre- pointer analysis apply a collection of inference rules to a serve everything about the structure of the original source dismantled intermediate form of the source program, mak- so that only minimal, necessary changes are made. As such, ing them difficult to apply to source-to-source tools that they typically manipulate abstract syntax trees that are little generally work on abstract syntax trees to preserve details more than a structured interpretation of the original program of the source program. text. Such trees are often manipulated directly through tree- Our pointer analysis algorithm operates directly on the or term-rewriting systems such as Stratego [15, 16]. abstract syntax tree of a C program and uses a form of stan- In this paper, we present an algorithm developed to per- dard dataflow analysis to compute the desired points-to in- form pointer analysis directly on abstract syntax trees. We formation. We have implemented our algorithm in a source- implemented our algorithm in a source-to-source tool called to-source translation framework and experimental results Proteus [17], which uses Stratego [15] as a back-end, and show that it is practical on real-world examples. find that it works well in practice. 2 Existing Pointer Analysis Techniques 1 Introduction Many techniques have been proposed for pointer analy- The role of pointer analysis in understanding C programs sis of C programs [1, 3, 4, 6, 10, 12, 14, 18]. They differ has been studied for years, being the subject of several PhD mainly in how they group related alias information. Fig- thesis and nearly a hundred research papers [9]. This type ure 1 shows a C fragment and the points-to sets computed of static analysis has been used in a variety of applications by four well-known flow-insensitive algorithms. such as live variable analysis for register allocation and Arrows in the figure represent pointer relationships be- constant propagation, checking for potential runtime errors tween the variables in the head and tail nodes: an arc from (e.g., null pointer dereferencing), static schedulers that need a to b means that variable a points-to variable b, or may to track resource allocation and usage, etc. Despite its appli- point-to that variable, depending on the specific algorithm. cability in several other areas, however, pointer analysis has been targeted primarily at compilation, be it software [9] or hardware [13]. In particular, the use of pointer analysis (and p=&x; p=&y; q=&z; p=q; x=&a; y=&b; z=&c; in fact, static analysis in general) for automated source code transformations remains little explored. Andersen [1] Steensgaard [15] Das [5] Heintze [8] We believe the main reason for this is the different pro- p q p q p q p q gram representations employed in source-to-source tools. x y z x,y,z x,y z x y z Historically, pointer analysis algorithms have been imple- mented in optimizing compilers, which typically proceed by a b c a,b,c a,b,c a b c ∗ supported in part by CNPq Brazilian Research Council, grant number Figure 1. Results of various flow-insensitive 200346/01-6 †supported by an NSF CAREER award, a grant from Intel corporation, pointer analysis algorithms. an award from the SRC, and by New York State’s NYSTAR program Some techniques encapusulate more than one variable in a of these properties depend on the order in which the state- single node, as seen in Steensgaard’s and Das’s approaches, ments of the program execute. As a result, the approach we in order to speed-up the computation. These methods trade adopt is flow-sensitive. precision for running time: variable x, for instance, points- to a, b and c on both techniques, although the code only 2.1 Analysis Accuracy assigns a’s address to x. Broadly, existing techniques can be classified as Another source of approximation commonly found in to- constraint-solving [5, 7, 8] or dataflow-based [6, 11, 12, 18]. day’s approaches is the adoption of the so-called non-visible Members of both groups usually define a minimal gram- variables [10], later renamed to invisible variables [6] or, al- ternatively, extended parameters [18]. When a function call mar for the source language that includes only basic op- 1 erators and statements. They then build templates used to takes place, a parameter p of pointer type might point to a match these statements. The templates are cast as inference variable v that is not in the scope of the called function. To rules [5, 7, 8] or dataflow equations [6, 11, 12, 18]. The al- keep track of such pointer relationships, special symbolic gorithms consist of iterative applications of inference rules names are created in the enclosing scope [6] [10] [18] and or dataflow equations on the statements of the program, dur- then manipulated in place of v whenever p is dereferenced. ing which pointer relationships are derived. This approach When the function call returns to the caller, the information assumes that the C program only contains allowed state- kept in the symbolic name is ’mapped’ back to v. For ex- ample, for a variable x with type int**, symbolic names ments. For instance, a=**b, with two levels of dereference 2 in the right-hand side, is commonly parsed 1 x and 2 x with types int* and int would be created [6] [18]. If an indirect reference, say *x, can lead to an out- = of-scope variable w, the corresponding symbolic name 1 x a is used to represent w. * There are some drawbacks with this approach: it adds * an overhead in the analysis due to this ’mapping’ and ’un- mapping’ of information, and it can become too approxi- b mate as the chain of function calls gets larger. The follow- Existing techniques generally require the preceding ing example shows how spurious aliases can be generated statement to be dismantled into two sub-expressions, each even though symbolic variable 1 a is not accessed within having at most one level of dereference: the called function. int *a, *b; Pointer Relationships = = int main() At P1: At P2: { Mapping: int c,d,e; d t a 1_a => c,d * * if (...) a 1_b => e a = &c; c else b t a = &d; b a e 1_a if (...) b = &c; b It is difficult to employ such an approach to source-to- else 1_b b = &e; source transformations because it is difficult to correlate the /* P1 */ f(); At P3: results calculated on the dismantled program with the origi- } d nal source. Furthermore, it introduces needless intermediate int f() a { /* P2 */ c variables, which can increase the analysis cost. int w; For source-to-source transformations, we want to per- b z = &w; e form the analysis close to the source level. It is particularly } /* P3 */ useful to directly analyze the ASTs and annotate them with the results of the analysis. Hence, we need to be able to Figure 2. Innacuracy due to invisible vari- handle arbitrary compositions of statements. ables. Precision is another issue in source-to-source transfor- mations: we want the most precise analysis practical be- In the example, 1 a stands for more than one program cause otherwise we may make unnecessary changes to the variable, namely c and d, due to the double assignment to code or, even worse, make incorrect changes. A flow- global variable a [6] [18]. When the statement b=&c insensitive analysis cannot, for example, determine that a pointer is initialized before it is used or that a pointer has 1Or global variable different values in different regions of the program. Both 2Process is applied recursively to all levels of pointer type. 2 is analyzed, local variable c has been already mapped to We assume the entire source code of the subject appli- 1 a, thus b is assumed to possibly point to such symbolic cation (multiple translation units, multiple files) is resolved variable. 1 b is then created to represent program variable into a large AST that resides in memory [17], so that we e, and the assignment b = &e induces pointer b to be also are able to jump from one procedure to another through tree associated with 1 b. When f returns, an additional relation- queries. The analysis starts off at the program’s main func- ship between program variables b and d has been created, tion, iterating through its statements. If a function call is even though only local variables were accessed within f .A encountered, its body is recursively analyzed taking into ac- pointer relationship to 1 a was generated because of a rela- count pointers being passed as parameters as well as global tionship to one of the variables 1 a stands for - not all the pointers. When the analysis reaches the end of the function, variables it represents3. it continues at the statement following the function call. The adoption of invisible variables, however, is of rel- Below, we give an overview of some aspects of the im- evant importance if one’s priority is the efficiency of the plementation.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages14 Page
-
File Size-