ANNOTATIONS FOR (MORE) PRECISE POINTS-TO ANALYSIS Mike Barnett – Microsoft Research, USA Manuel Fähndrich – Microsoft Research, USA Diego Garbervetsky – DC. FCEyN. UBA, Argentina Francesco Logozzo – Microsoft Research, USA - IWACO’07 -
Feb 22, 2016
ANNOTATIONS FOR (MORE) PRECISE POINTS-TO
ANALYSIS
Mike Barnett – Microsoft Research, USA Manuel Fähndrich – Microsoft Research, USADiego Garbervetsky – DC. FCEyN. UBA, ArgentinaFrancesco Logozzo – Microsoft Research, USA
- IWACO’07 -
Original Motivation
Objective Have a points-to and effect analysis to reason
(among other things) about (weak) purity in .Net programs
Approach Try to use Salcianu-Rinard points-to analysis
Problems Their heap model neither support struct types nor
parameter passing by reference Relies in having a complete call graph for the app Very conservative in case of non-analyzable
methods
Motivating Example
It is difficult to predict the runtime type of src and iter
We cannot predict the effect of methods applied to iter on src …
List<int> Copy(IEnumerable<int> src) { List<int> l = new List(); foreach (int x in src) l.Add(x); return l;}
List<int> Copy(IEnumerable<int> src){ List<int> l = new List<int>(); IEnumerator<int> iter = src.GetEnumerator(); while (iter.MoveNext()){ int x = iter.get_Current(); l.Add(x); } return l;}
•May GetEnumerator modify src?•May MoveNext or get_Current indirectly modify src?
Is Copy (weakly) pure?
Our work
Interprocedural Points-to and read/ write Effects Analysis Based on Salcianu’s Poinst-to and Purity Analysis Support s some .NET features
Managed pointers, struct types Extended support s for non analyzable calls
A small annotation Language Represents points-to and effects information Leverages on some Spec# annotations (ownership)
Implementation in Spec# compiler Used to infer/verify method purity Reentrancy analysis Checking specifications admissibility in the Boogie
Methodology
Salcianu’s analysis reminderMain abstraction: Poinst-to graphs PTG=<I ,O,
L, E>Models the part of the heap accessed by the
methodvoid m1(A p1, A p2) { p2.g = p1.f;}
•Inside Nodes (objects allocated by m)•Load Nodes (placeholder for unknown objects read from outside the scope of m)•Parameter Nodes (represent the object/s passed as parameter)•Inside egdes: References created by m•Outside egdes: References read from outside the scope of m•W = set of write effects (n, field).
W: {P2.f1}
W: {a2.f1}
Salcianu’s analysis remindervoid m1(A p1, A p2) {
p2.g = p1.f;}
void m0(A a2) { a1 = new A(); b = new B(); m1(a1,a2);}
μ :: Node P(Node) relates every node n in the callee’s summary to a set of existing or fresh nodes in the caller (nodes(Pm ) nodes(Pcallee))Fixpoint calculation
1. Match argument with parameters2. Match reads from callee with writes of caller (outside egdes
disambiguation)
What happends is m1 is not analyzable?
First extension
A new set of nodes: address nodes
A new level of indirection Every variable or field
is represented by its address
For objects and primitive values an outgoing edge (labeled *) meaning “the contents of”
For struct types the outgoing edges are fields.
void m1(A p1, A p2) { p2.g = p1.f;}
Second extension
Annotations to improve the precision of the analysis of non-analyzable calls
Approach1. Add a few annotations to non-analyzable
methods (pt&e info)2. Compute PTG for every annotated method
We need to enrich the PTG model3. Treat every method as analyzable4. When method code is available check
against annotations
Why annotations?
Useful for interfaces and abstract methods There is no source code Virtual calls Documentation Impose restrictions over implementing classes
Useful for native code and 3rd party libraries Source not available
Useful for local reasoning in program analysis Used as summaries for method calls Assumed valid for inter-procedural analysis The callee will be eventually checked
Iterator sample revisited1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
[Pure] [Fresh] [Escapes(true)][GlobalAccess(false)] IEnumerable GetEnumerator();
bool MoveNext();
Iterator sample revisited
[Pure] [GlobalAccess(false)] [Escapes(true)] [Fresh]IEnumerable GetEnumerator();
bool MoveNext();
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
wNode = represents the set of objects reachable from the object/s represented by that node
Iterator sample revisited
[Pure] [GlobalAccess(false)] [Escapes(true)] [Fresh]IEnumerable GetEnumerator();
bool MoveNext();
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
Iterator sample revisited
[Pure] [GlobalAccess(false)] [Escapes(true)] [Fresh]IEnumerable GetEnumerator();[GlobalAccess(false)]bool MoveNext();
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
Iterator sample revisited
[Pure] [GlobalAccess(false)] [Escapes(true)] [Fresh]IEnumerable GetEnumerator();[GlobalAccess(false)]bool MoveNext();
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
GetEnumerator revisited
[Pure] [GlobalAccess(false)] [Escapes(true)][Captures(false)]IEnumerable GetEnumerator();[GlobalAccess(false)]bool MoveNext();
$ Fields: readonly reference using not owned fields
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
Iterator sample revisited
[Pure] [GlobalAccess(false)] [Escapes(true)][Captures(false)]IEnumerable GetEnumerator();[WriteConfined] bool MoveNext();
$ Fields: readonly reference using not owned fieldswC: Means all nodes reachable in its ownership domain
1: int result = 0;2: it= p1.GetEnumerator();3: while(it.MoveNext())4: result+=it.Current();
5: return result;
The Annotation Language Fresh: for out parameters and ret value
It is a newly created object Write: for parameters
The method may write objects reachable from the parameter WriteConfined: for parameters
The method may write objects reachable from the parameter but only within its ownership domain
Escape(bool): for parameters The method may create some link between objects reachable from
the parameter and other objects reachable from the return value or another parameter.
Capture(bool): for parameters The escaping parameter will be owned by some callee argument?
GlobalRead/GlobalWrite(bool): for methods Does the method read or write a global?
WriteConfined: for methods The method mutates only the objects owned by its parameters
Pure: for methods The method can not mutate the pre-estate unless allowed (out
parameters)
Conclusions
Extend an existing poinst-to analysis To support .NET memory model Improve precision for non analyzable calls
Using omega nodes and an small annotation language We find the annotation useful as documentation for
the methods. Leverages on some of existing Spec#
annotations Pure, Ownerships
Initial experiment are encouraging Interesting improvements in precision (purity,
aliasing)
Future Work
Integration with Spec# Generate and use modifies and read
clauses Improve precision:
Recompute $,? fields when more info becomes available
Use type information as annotations to reduce potential aliasing
Use omega nodes to “abstract” poinst-to graph (scalability)
Questions?
Additional Slides
About annotations Omega Nodes
Motivation, Definition, integration with annotations
Experiments PT Analysis step by step
About annotations
[Pure] annotation is not always enough information They still can impact the caller
A pure method can: Return a non fresh value ( a
global variable, a parameter) Make a parameter or a global
reachable from outside On the other hand…
A caller can be pure even if the callee is not.
[Pure] A m2(A p1, A p2){ return p2;}
void m(A p1, A p2) { A a; A b = m2(p1, p2); b.v = 20; c = new A(); m3(c);}
A m3(A p2){ p2.v = 0;}
PTG for non-analyzable calls Even using annotations we don’t
have total control of method behavior If we allow writing a parameter: how
deep is accessing its content? Salcianu’s analysis generates the
interprocedural mapping by matching operations on callee side with operation on caller side One to one traverse on both graphs
Some problems Suppose we want to model that an
unknown callee might potentially write any object reachable from a parameter p1.f1 = 0; p1.f1.f2 = 0; p1.f1.f2….fn = 0;
Attempt 1: Mark the effect directly over all nodes reachable from the caller Problem: When binding caller with callee, we may
not have enough context… M1: m2(a1);
The effect of the callee must persist the caller context
Attempt 2: Add to the callee PTG nodes/egdes corresponding with each potential access Problem: They may be infinite
Omega nodes Omega nodes:
Represent every object reachable from that node ? fields: Represent any field When computing μ (binding time)
1. If an omega node appears in μ(n) , then add all nodes reachable from n to μ(n)
2. If some of them is a Load Node convert it into an omega node
Annotations and Omega Nodes1. Generates a conservative PTG for
non analyzable calls using omega nodes for the parameters and “?” edges between them
2. Clean the PTG using information provided by annotations
1. Remove ? Egdes (or replace by $ edges)
2. Add omega confine nodes3. Add inside nodes (fresh returns)
Some experiments on Boogie
First extension - Language
We define a subset of IL like language including managed pointer support
Necessary for parameter passing by reference and for dealing with struct types
Assign a constant CopyAllocationStoreLoadMethod CallReturnIfAssign Null Load AddressLoad Heap AddressLoad Indirect
Store Indirect
a = ka = ba = new Ca.f = ba = b.fa0.m(a1,a2,...)returnif(b) goto ka = nulla = &ba = &(b.f)a = *b*a = b
a = &b
d = a.f1
First extension
A new set of nodes: address nodes
A new level of indirection Every variable or field
is represented by its address
For objects and primitive values an outgoing edge (labeled *) meaning “the contents of”
For struct types the outgoing edges are fields.
void m1(A p1, A p2) { p2.f1 = p1.f2;}
Dealing with struct types
Struct type has value semantics Treated as Values Impact in assignments Parameter passing
P&PEA intraproc
void m2(A a){ a = this; D d = new D(); a.f = d;}
P&PEA intraproc
void m2(A a){ a = this; D d = new D(); a.f = d;}
P&PEA intraproc
void m2(A a){ a = this; D d = new D(); a.f = d;}
P&PEA intraproc
void m2(A a){ a = this; D d = new D(); a.f = d;}
Write Effects: [PLN.this].f
• this.f• a.f
P&PEA intraproc
void m2(A a){ a = this; D d = new D(); a.f = d;}
Write Effects: [PLN.this].f
• this.f
Summary for m2