500 likes | 644 Views
Javari: Java Reference Immutability Language and Inference Tool. Jaime Quinonez November 14, 2006. Reference Immutability. @ReadOnly on a type specifies a reference that cannot be used to modify an object @ReadOnly can annotate any use of a type
E N D
Javari: Java Reference Immutability Language and Inference Tool Jaime Quinonez November 14, 2006
Reference Immutability • @ReadOnly on a type specifies a reference that cannot be used to modify an object • @ReadOnly can annotate any use of a type • For a type T, @ReadOnly T is a supertype of T • T can be used anywhere @ReadOnly T is expected • @ReadOnly T cannot be used where T is expected
Example mutable class • setTime() mutates object • getTime() does not mutate object public class Date { private long time; public Date(long t) { this.time = time; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } }
@ReadOnly receiver annotates method • getTime() does not mutate object • receiver of getTime() is @ReadOnly public class Date { private long time; public Date(long t) { this.time = time; } public long getTime() @ReadOnly { return time; } public void setTime(long time) { this.time = time; } }
Some fields not part of state • hashCode() does not modify abstract state • hashCode() modifies a field public class Date { private int hc; public int hashCode() @ReadOnly { if(hc == 0) { hc = … ; } return hc; } }
@Assignable excludes fields from abstract state • hc is not part of abstract state public class Date { private @Assignable int hc; public int hashCode() @ReadOnly { if(hc == 0) { hc = … ; } return hc; } }
@ReadOnly on generic types ArrayList<@ReadOnly Date> list; • listis a mutable ArrayList • Contains immutable references to Dates • The list can be mutated • list.get(0)- legal • list.clear()- legal • Elements in the list cannot be mutated • Return type of list.get() is @ReadOnly Date • list.get(0).getTime()- legal • list.get(0).setTime(2)- illegal • @ReadOnly Date d = list.get(0)- legal • Date d = list.get(0) - illegal
@ReadOnly doesn’t propagate to generics @ReadOnly ArrayList<Date> list; • listis an immutable ArrayList • Contains mutable references to Dates • The list cannot be mutated • list.get(0)- legal • list.clear()- illegal • Elements in the list can be mutated • Return type of list.get() is Date • list.get(0).getTime()- legal • list.get(0).setTime(2)- legal • @ReadOnly Date d = list.get(0)- legal • Date d = list.get(0) - legal
Need to add @ReadOnly annotations • Dateis an existing class • Date.getTime() andDate.setTime()are not annotated • Javari defaults method receiver to mutable unless@ReadOnlyis specified • It is tedious and error-prone for humans to infer these annotations • Need a tool to infer reference immutability in existing code
@ReadOnly methods do not mutate fields • Given unannotated class file: public class Date { private long time; public Date(long time) { this.time = time } public void setTime(long time) { this.time = time; } public long getTime() { return time; } }
@ReadOnly methods do not mutate fields • Modify class file to contain appropriate @ReadOnly annotations: public class Date { private long time; public Date(long time) { this.time = time } public void setTime(long time) { this.time = time; } public long getTime() @ReadOnly { return time; } }
Type Inference Algorithm • Flow-insensitive and context-insensitive • Generate a set of mutability constraints • Unguarded constraint states unconditional mutability • x = new Object(); • “x is mutable” • Guarded constraint states conditional mutability • y.x(); • “if x is mutable, then y is mutable” • Solve set of constraints to see what types have to be mutable • All other types can safely be declared immutable
Field reassignment mutates object • Create unguarded constraint public class Date { private long time; public Date(long time) { this.time = time } public void setTime(long time) { this.time = time; } public long getTime() { return time; } }
Field reassignment mutates object • Create unguarded constraint public class Date { private long time; public Date(long time) { this.time = time } public void setTime(long time) { this.time = time; } public long getTime() { return time; } } • Date.setTime().this is mutable
Calling methods on fields • Create guarded constraint based on field’s type public class Cell { private Date d; public long get() { return d.getTime(); } public void reset() { d.setTime(0); } }
Calling methods on fields • Create guarded constraint based on field’s type public class Cell { private Date d; public long get() { return d.getTime(); } public void reset() { d.setTime(0); } }
Calling methods on fields • Create guarded constraint based on field’s type public class Cell { private Date d; public long get() { return d.getTime(); } public void reset() { d.setTime(0) } } • Date.getTime().this mutable -> Cell.d mutable • Date.getTime().this mutable -> Cell.get().this mutable
Calling methods on fields • Create guarded constraint based on field’s type public class Cell { private Date d; public long get() { return d.getTime(); } public void reset() { d.setTime(0); } } • Date.getTime().this mutable -> Cell.d mutable • Date.getTime().this mutable -> Cell.get().this mutable
Calling methods on fields • Create guarded constraint based on field’s type public class Cell { private Date d; public long get() { return d.getTime(); } public void reset() { d.setTime(0); } } • Date.getTime().this mutable -> Cell.d mutable • Date.getTime().this mutable -> Cell.get().this mutable • Date.setTime().this mutable -> Cell.d mutable • Date.setTime().this mutable -> Cell.reset().this mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.get().thismutable • Date.setTime().thismutable -> Cell.dmutable • Date.setTime().thismutable -> Cell.reset().thismutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.get().thismutable • Date.setTime().thismutable -> Cell.dmutable • Date.setTime().thismutable -> Cell.reset().thismutable • New Constraints • Cell.dis mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.get().thismutable • Date.setTime().thismutable -> Cell.dmutable • Date.setTime().thismutable -> Cell.reset().thismutable • New Constraints • Cell.dis mutable • Cell.reset().thisis mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.get().thismutable • Date.setTime().thismutable -> Cell.dmutable • Date.setTime().thismutable -> Cell.reset().thismutable • New Constraints • Cell.reset().thisis mutable • Cell.dis mutable • Final unguarded constraints • Date.setTime().thisis mutable • Cell.reset().thisis mutable • Cell.dis mutable • All other types are@ReadOnly
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } }
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } }
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } } • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().this mutable -> Cell.getTime().this mutable
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } } • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().this mutable -> Cell.getTime().this mutable
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } } • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().this mutable -> Cell.getTime().this mutable • Cell.setDate().this is mutable • Cell.d mutable -> Cell.setDate().param:newDatemutable
Parameters assigned to fields • Field mutability guards parameter mutability public class Cell { private Date d; public long getTime() { return this.d.getTime(); } public void setDate(Date newDate) { this.d = newDate; } } • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().this mutable -> Cell.getTime().this mutable • Cell.setDate().this is mutable • Cell.d mutable -> Cell.setDate().param:newDatemutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.getTime().thismutable • Cell.setDate().thisis mutable • Cell.dmutable -> Cell.setDate().param:newDateis mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • Date.getTime().thismutable -> Cell.dmutable • Date.getTime().thismutable -> Cell.getTime().thismutable • Cell.setDate().thisis mutable • Cell.dmutable -> Cell.setDate().param:newDateis mutable • No guards can be satisfied • Finished with two unguarded constraints: • Date.setTime().this is mutable • Cell.setDate().this is mutable • All other types are@ReadOnly
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } }
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } } • Cell.set().this is mutable
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } } • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } } • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } } • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable
Generic types have same inference • Place constraints on upper and lower bound public class Cell<T extends Date super SubDate> { T t; public void set(T t) { this.t = t; } public void reset() { this.t.setTime(0); } } public class SubDate extends Date { public void setTime(long time) { this.time = time; } } • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable • Subtype can only be mutable if supertype is mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • SubDate.setTime().thisis mutable • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • SubDate.setTime().thisis mutable • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset() mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable • New Constraints • Cell.t is mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • SubDate.setTime().thisis mutable • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable • New Constraints • Cell.t is mutable • Cell.reset() is mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • SubDate.setTime().thisis mutable • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable • New Constraints • Cell.t is mutable • Cell.reset() is mutable • Cell.set().param:t is mutable
Javarifier propagates unguarded constraints • Constraints • Date.setTime().thisis mutable • SubDate.setTime().thisis mutable • Cell.set().this is mutable • Cell.t mutable-> Cell.set().param:t mutable • Date.setTime().this mutable-> Cell.t mutable • Date.setTime().this mutable-> Cell.reset().this mutable • SubDate.setTime().this mutable -> Date.setTime().this mutable • New Constraints • Cell.t is mutable • Cell.reset() is mutable • Cell.set().param:t is mutable • No references can be marked @ReadOnly
Case study of Javarifier on scene library • 6935 lines of code • 1008 annotatable slots • Hand-annotated by original author Matt McCutchen • 126 annotations disagree with Javarifier results • 5 differences due to bugs in Javarifier code • 121 differences due to programmer error • Type-checker would have caught most of the programmer's errors
Javarifier inserts @ReadOnly wherever legal • Mutable is more restricting than @ReadOnly on a receiver • If code that uses a method is type-safe when the method has a @Mutable receiver, it is type-safe with a @ReadOnly receiver • Two viewpoints • Programmers interpretation of Javari: @Mutable is legal • Javarifier: both @ReadOnly and @Mutable satisfy constraints, so use @ReadOnly • Javarifier finds mutations, propagates mutable restriction, then assigns everything else @ReadOnly
Some specifications cannot be inferred VivifyingMap<K,V> implements Map<K,V> • get(K k) returns the value that k is mapped to • If k isn’t mapped to a value, adds mapping from k to a new “empty” value, and returns that value • prune() removes all mappings from keys to “empty” values • Abstract state of a VivifyingMap is the set of mappings (possibly to empty values) • The result of keySet() is in the abstract state • Both get() and prune() modify abstract state
Abstract methods have no body to analyze public abstract class VivifyingMap<K,V> extends HashMap<K,V> { // Removes all mappings to empty values // Modifies receiver public void prune() { // for each key in map: if(checkEmpty(super.getValue(key))) { super.remove(key) } } // Returns whether the parameter is empty // Modifies receiver public abstract boolean checkEmpty(V v); }
Javarifier infers @ReadOnly on abstract methods public abstract class VivifyingMap<@ReadOnly K, @ReadOnly V> extends HashMap<@ReadOnly K, @ReadOnly V> { // prune() not @ReadOnly public void prune() { … } public abstract boolean checkEmpty(@ReadOnly V v) @ReadOnly; } • checkEmpty() annotated as@ReadOnly • No evidence that method modifies v • Violates programmer specification
Valid subtype can cause Javarifier conflict public class NamedMaps<K, V> extends VivifyingMap<String,VivifyingMap<K,V>> { public boolean checkEmpty(VivifyingMap<K,V> vm) { vm.prune(); return vm.keySet().isEmpty(); } } • checkEmpty() modifiesparameter • Calls prune() on parameter • Meets programmer specification of VivifyingMap.checkEmpty() • Javarifier does not annotate parameter as @ReadOnly
Javarifier can violate programmer’s specification (abstract) VivifyingMap.checkEmpty(V v) • Programmer: v modifiable • Javari: @ReadOnly v NamedMaps.checkEmpty(V v) • Programmer: v modifiable • Javari: v modifiable • Programmer of NamedMaps met programmer specification of VivifyingMap • In Javarifier’s specification, NamedMaps not a true subtype of VivifyingMap • Javarifier can’t infer programmer’s specification of abstract method without some implementation
Javarifier infers legal annotations • Javarifier is able to analyze code and infer reference immutability • Javarifier does not ensure consistency when classes are analyzed separately • Javarifier cannot truly capture programmer’s intent when this intent does not manifest itself in code • Sometimes formal specification comes from programmer intent and not necessarily code • Most errors in annotating a specification are simple errors that would be caught by a type-checker