1 / 40

JPure: a Modular Purity System for Java

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.

Download Presentation

JPure: a Modular Purity System for Java

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. JPure: a Modular Purity System for Java David J. Pearce Victoria University of Wellington New Zealand

  2. 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. int sum(int[] items) { int r = 0; for(int v : items){ r += v; } return r; } booleanisSorted(List<Integer> items) { int last = Integer.MIN_VALUE; for(Integer v : items) { if(last > v) { return false; } last = v; } return true; }

  3. 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 Pointer Analysis Purity Inference Annotated Source Java Source ? Annotated Bytecode

  4. Modular Purity System Java Compiler Purity Inference Annotated Source • Purity Inference: • Generates annotations via interproceduralanalysis • Generated annotations are modularly checkable • Purity Checker: • Verifies annotations via intraprocedural analysis • Integrates easily with Java Bytecode Verification Java Source Purity Checker Annotated Bytecode

  5. Simple (Modular) Approach • Pure Methods: • Cannot contain field assignments • Can only call methods marked @Pure • Only pure methods can override pure methods Parent privateint afield; @Pure void method() { } Child @Pure void method() { } Client @Pure void f(Parent p) { p.method(); }

  6. Simple (Modular) Approach • Pure Methods: • Cannot contain field assignments • Can only call methods marked @Pure • Only pure methods can override pure methods Parent privateint afield; @Pure void method() { } Child @Pure void method() { } Client @Pure void f(Parent p) { p.method(); }

  7. Simple (Modular) Approach Parent privateint f; @Pure void method(){f=1;} • 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(); }

  8. Simple (Modular) Approach • Pure Methods: • Cannot contain field assignments • Can only call methods marked @Pure • Only pure methods can override pure methods Parent privateint afield; @Pure void method() { } Child @Pure void method() { } Client @Pure void f(Parent p) { p.method(); }

  9. Simple (Modular) Approach • Pure Methods: • Cannot contain field assignments • Can only call methods marked @Pure • Only pure methods can override pure methods Parent privateint afield; @Pure void method() { } Child @Pure void method() { } Client @Pure void f(Parent p) { p.method(); }

  10. Problems publicclass Test { private List<String> items; @Pure boolean has(String s) { for(String i : items) { if(s == i) { return true; } } return false; } } public classAbstractStringBuilder { 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”; }

  11. Problems publicclass Test { private List<String> items; @Pure boolean has(String s) { for(String i : items) { if(s == i) { return true; } } return false; } } public classAbstractStringBuilder { 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”; }

  12. Problems publicclass Test { private List<String> items; @Pure boolean has(String s) { for(String i : items) { if(s == i) { return true; } } return false; } } public classAbstractStringBuilder { 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”; }

  13. Problems publicclass Test { private List<String> items; @Pure boolean has(String s) { for(String i : items) { if(s == i) { return true; } } return false; } } public classAbstractStringBuilder { 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”; }

  14. 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; } }

  15. 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; } }

  16. 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; } } Indicates next() only modifies “local” state

  17. 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; } } Indicates next() only modifies “local” state

  18. 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; } } Indicates next() only modifies “local” state @Pure boolean has(String s) { Iterator tmp; tmp = items.iterator(); while(tmp.hasNext()) { i = tmp.next(); if(s == i) return true; } return false; }

  19. 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; } } Indicates next() only modifies “local” state @Pure boolean has(String s) { Iterator tmp; tmp = items.iterator(); while(tmp.hasNext()) { i = tmp.next(); if(s == i) return true; } return false; }

  20. 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++]; } }} • 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

  21. Iterator Implementation class ArrayList implements Collection{ … @Fresh Object iterator() { return new Iterator(data); } static class Iterator { Object[] data; intidx = 0; … @Pure booleanhasNext() { return idx < data.size(); } @Local Object next() { return data[idx++]; } }} • 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

  22. 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]; } }} TList data length,type Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh. Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

  23. 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]; } }} TList data length,type Required for Invariant 1 Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh. Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

  24. 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]; } }} TList data length,type Required for Invariant 1 Safe under Invariant 2 Locality Invariant 1 (Construction). When a new object is constructed its locality is always fresh. Locality Invariant 2 (Preservation). When the locality of a fresh object is modified, its locality must remain fresh.

  25. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ?

  26. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST 

  27. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST 

  28. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ?

  29. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ?

  30. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST 

  31. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST  LTHIS LDST 

  32. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST  LTHIS LDST  LTHIS LDST 

  33. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST  LTHIS LDST  LTHIS LDST  LDST LTHIS LDST

  34. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST  LTHIS LDST  LTHIS LDST  LDST LTHIS LDST LTHIS LDST LDST

  35. Detailed Example this dst tmp @Local public void copy(TListdst) { vartmp = 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 ? LTHIS LDST  LTHIS LDST  LTHIS LDST ? LTHIS LDST ? LTHIS LDST  LTHIS LDST  LTHIS LDST LDST LDST LTHIS LDST LTHIS LDST LDST

  36. 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

  37. Limitations 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; }} • Disappointment! • Object.equals()not inferred @Pure • Object.hashCode() not inferred @Pure

  38. 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

  39. Law of Locality Law of Locality. When checking @Local annotations, one can safely assume parameters are not aliased (!) • 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; }}

More Related