jpure: a modular purity system for java

Post on 02-Jan-2016

35 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

JPure: a Modular Purity System for Java. David J. Pearce Victoria University of Wellington New Zealand. Introduction. Definition: A method is considered pure if it does not assign (directly of indirectly) to any field or array cell that existed before it was called. - PowerPoint PPT Presentation

TRANSCRIPT

JPure: a Modular Purity System for Java

David J. PearceVictoria University of Wellington

New Zealand

Introduction

int sum(int[] items) {

int r = 0;

for(int v : items) { r += v; }

return r;

}

Definition: A method is considered pure if it does not assign (directly of indirectly) to any field or array cell that existed before it was called.

boolean isSorted(List<Integer> items) {

int last = Integer.MIN_VALUE;

for(Integer v : items) {

if(last > v) { return false; }

last = v;

}

return true;

}

Typical Previous Purity Systems

• Pointer Analysis feeds Purity Inference

– Pointer Analysis typically whole-program

– Inferred annotations cannot be checked easily• i.e. without regeneration pointer information

PointerAnalysis

?

Java Source Annotated Source

Annotated Bytecode

PurityInference

Modular Purity System

• Purity Inference:

– Generates annotations via interprocedural analysis

– Generated annotations are modularly checkable

• Purity Checker:

– Verifies annotations via intraprocedural analysis

– Integrates easily with Java Bytecode Verification

PurityInference

PurityChecker

Java Source Annotated Source

Annotated Bytecode

Java Compiler

Simple (Modular) Approach

• Pure Methods:

– Cannot contain field assignments

– Can only call methods marked @Pure

– Only pure methods can override pure methods

Parent

private int afield;

@Pure void method() { }

Child

@Pure void method() { }

Client

@Pure void f(Parent p) {

p.method();

}

Simple (Modular) Approach

• Pure Methods:

– Cannot contain field assignments

– Can only call methods marked @Pure

– Only pure methods can override pure methods

Parent

private int afield;

@Pure void method() { }

Child

@Pure void method() { }

Client

@Pure void f(Parent p) {

p.method();

}

Simple (Modular) Approach

• Pure Methods:

– Cannot contain field assignments

– Can only call methods marked @Pure

– Only pure methods can override pure methods

Parent

private int f;

@Pure void method(){f=1;}

Child

@Pure void method() { }

Client

@Pure void f(Parent p) {

p.method();

}

Simple (Modular) Approach

• Pure Methods:

– Cannot contain field assignments

– Can only call methods marked @Pure

– Only pure methods can override pure methods

Child

@Pure void method() { }

Client

@Pure void f(Parent p) {

p.method();

}

Simple (Modular) Approach

• Pure Methods:

– Cannot contain field assignments

– Can only call methods marked @Pure

– Only pure methods can override pure methods

Parent

private int afield;

@Pure void method() { }

Client

@Pure void f(Parent p) {

p.method();

}

Problems

public class AbstractStringBuilder {

private char[] data;

private int count; // number of items used

public AbstractStringBuilder append(String s) {

s.getChars(0, s.length(), data, count);

}}

@Pure String f(String x) { return x + “hello”; }

public class Test {

private List<String> items;

@Pure boolean has(String s) {

for(String i : items) {

if(s == i) { return true; }

}

return false;

} }

Problems

public class AbstractStringBuilder {

private char[] data;

private int count; // number of items used

public AbstractStringBuilder append(String s) {

s.getChars(0, s.length(), data, count);

}}

@Pure String f(String x) { return x + “hello”; }

public class Test {

private List<String> items;

@Pure boolean has(String s) {

for(String i : items) {

if(s == i) { return true; }

}

return false;

} }

Problems

public class AbstractStringBuilder {

private char[] data;

private int count; // number of items used

public AbstractStringBuilder append(String s) {

s.getChars(0, s.length(), data, count);

}}

@Pure String f(String x) { return x + “hello”; }

public class Test {

private List<String> items;

@Pure boolean has(String s) {

for(String i : items) {

if(s == i) { return true; }

}

return false;

} }

Problems

public class AbstractStringBuilder {

private char[] data;

private int count; // number of items used

public AbstractStringBuilder append(String s) {

s.getChars(0, s.length(), data, count);

}}

@Pure String f(String x) { return x + “hello”; }

public class Test {

private List<String> items;

@Pure boolean has(String s) {

for(String i : items) {

if(s == i) { return true; }

}

return false;

} }

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!Indicates iterator() returns “fresh” object

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!Indicates iterator() returns “fresh” object

Indicates next() only modifies “local” state

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!Indicates iterator() returns “fresh” object

Indicates next() only modifies “local” state

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!

@Pure boolean has(String s) {

Iterator tmp;

tmp = items.iterator();

while(tmp.hasNext()) {

i = tmp.next();

if(s == i) return true;

}

return false;

}

Indicates iterator() returns “fresh” object

Indicates next() only modifies “local” state

interface Collection {

@Fresh Object iterator();

}

interface Iterator {

@Pure boolean hasNext();

@Local Object next();

}

class Test {

List<String> items;

@Pure boolean has(String s){

for(String i : items) {

if(s == i) return true;

}

return false;

} }

Introducing JPure!

@Pure boolean has(String s) {

Iterator tmp;

tmp = items.iterator();

while(tmp.hasNext()) {

i = tmp.next();

if(s == i) return true;

}

return false;

}

Indicates iterator() returns “fresh” object

Indicates next() only modifies “local” state

• Methods annotated @Fresh– Must return new objects– Or, values returned by methods marked @Fresh

• Methods annotated @Local– May update “local” state … – But otherwise must remain pure

class ArrayList implements Collection{

@Fresh Object iterator() { return new Iterator(data); }

static class Iterator {

Object[] data; int idx = 0;

@Pure boolean hasNext() { return idx < data.size(); }

@Local Object next() { return data[idx++]; }

}}

Iterator Implementation

• Methods annotated @Fresh– Must return new objects– Or, values returned by methods marked @Fresh

• Methods annotated @Local– May update “local” state … – But otherwise must remain pure

class ArrayList implements Collection{ …

@Fresh Object iterator() { return new Iterator(data); }

static class Iterator {

Object[] data; int idx = 0; …

@Pure boolean hasNext() { return idx < data.size(); }

@Local Object next() { return data[idx++]; }

}}

Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh.

class TList {

private int length;

private @Local Object[] data;

private Type type;

@Local public TList(Type t, int m) {

length = 0;

data = new Object[m];

type = t;

}

@Local public void copy(TList dst) {

length = dst.length;

type = dst.type;

data = new Object[dst.length];

for(int i=0;i!=length;++i) { data[i] = dst.data[i]; }

}}

TListdata

length,type

Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh.

class TList {

private int length;

private @Local Object[] data;

private Type type;

@Local public TList(Type t, int m) {

length = 0;

data = new Object[m];

type = t;

}

@Local public void copy(TList dst) {

length = dst.length;

type = dst.type;

data = new Object[dst.length];

for(int i=0;i!=length;++i) { data[i] = dst.data[i]; }

}}

TListdata

length,type

Required forInvariant 1

Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh.

class TList {

private int length;

private @Local Object[] data;

private Type type;

@Local public TList(Type t, int m) {

length = 0;

data = new Object[m];

type = t;

}

@Local public void copy(TList dst) {

length = dst.length;

type = dst.type;

data = new Object[dst.length];

for(int i=0;i!=length;++i) { data[i] = dst.data[i]; }

}}

TListdata

length,type

Required forInvariant 1

Safe underInvariant 2

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

LTHIS LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

LTHIS LDST

LTHIS LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

LTHIS LDST

LTHIS LDST

LTHIS LDST LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

LTHIS LDST

LTHIS LDST

LTHIS LDST

LTHIS LDST

LDST

LDST

Detailed Example@Local public void copy(TList dst) {

var tmp = dst.length;

this.length = tmp;

tmp = dst.type;

this.type = tmp;

tmp = new Object[dst.length];

this.data = tmp;

for(int i=0;i!=length;++i) {

tmp = dst.data[i];

this.data[i] = tmp;

} }

LTHIS LDST ?

tmpdstthis

LTHIS LDST

LTHIS LDST

LTHIS LDST ?

LTHIS LDST ?

LTHIS LDST

LTHIS LDST

LTHIS LDST

LTHIS LDST

LTHIS LDST

LDST

LDST

LDST

Checking vs Inference

• Purity Checker:

– Intraprocedural dataflow analysis

– Uses static information about called methods

– Checks fresh objects flow to @Fresh returns

– Checks assignments to @Local fields are fresh

– Checks assignments to other fields are in locality

– Checks annotations overridden correctly

• Purity Inference:

– Interprocedural dataflow analysis

– Uses static call graph

– Essentially works in opposite direction to checker

– E.g. if all returned values fresh -> method annotated @Fresh

Limitations

• Disappointment!– Object.equals() not inferred @Pure– Object.hashCode() not inferred @Pure

class Test {

private int hashCode;

public boolean equals(Object o) {

if(hashCode() == o.hashCode()) { … }

return false;

}

public int hashCode() {

if(hashCode == -1) { hashCode = …; }

return hashCode;

}}

Conclusion

• The JPure System

– Built around Modularly Checkable Annotations

– Interprocedural analysis infers annotations

– Intraprocedural analysis checks annotations• Could be incorporated in Java Bytecode Verifier

– Locality & freshness help uncover more purity• 41% on average for benchmarks (vs 25% for simple)

See http://www.ecs.vuw.ac.nz/~djp/jpure

Law of Locality

• Example:

– What if other aliased with this?

– Applying Law of Locality seems counter-intuitive

class Test {

private int field;

@Local void f(Test other){

this.field = 1;

}}

Law of Locality. When checking @Local annotations, one can safely assume parameters are not aliased (!)

top related