Transparent proxies for java futures

Polyvios Pratikakis, Jaime Spacco, Michael Hicks
2004 Proceedings of the 19th annual ACM SIGPLAN Conference on Object-oriented programming, systems, languages, and applications - OOPSLA '04  
A proxy object is a surrogate or placeholder that controls access to another target object. Proxies can be used to support distributed programming, lazy or parallel evaluation, access control, and other simple forms of behavioral reflection [19] . However, wrapper proxies (like futures or suspensions for yet-to-be-computed results) can require significant code changes to be used in statically-typed languages, while proxies more generally can inadvertently violate assumptions of transparency,
more » ... ulting in subtle bugs. To solve these problems, we have designed and implemented a simple framework for proxy programming, which employs a static analysis based on qualifier inference [16] , but with additional novelties. Code for using wrapper proxies is automatically introduced via a classfile-to-classfile transformation, and potential violations of transparency are signaled to the programmer. We have formalized our analysis and proven it sound. Our framework has a variety of applications, including support for asynchronous method calls returning futures. Experimental results demonstrate the benefits of our framework: programmers are relieved of managing and/or checking proxy usage, analysis times are reasonably fast, and overheads introduced by added dynamic checks are negligible, and performance improvements can be significant. For example, changing two lines in a simple RMI-based peerto-peer application and then using our framework resulted in a large performance gain. with JSR 166 futures, which is timely given JSR 166's impending inclusion in the Java standard. • A future is an example of a wrapper proxy, in which a container wraps an underlying object. We have also implemented support for lazy evaluation in Java using suspensions, which are wrapper proxies that encapsulate lazy computations. Section 5.3 describes how we profitably used futures and suspensions together in an RMI-based peer-to-peer application: changing two lines resulted in a large performance gain. • Interface proxies share an interface with their target object, as specified by the proxy design pattern [19] . As with wrapper proxies, incorrect usage of these proxies could violate transparency. Section 5.4 describes how we used our framework to discover possible transparency violations arising from the introduction of interface proxies in large programs. Our static analysis is based on qualifier inference [16] , but improves on it in two ways. First, we support dynamic coercions, needed to claim futures and other wrapper proxies. Second, we use a simple form of flow-sensitivity to avoid coercing the same expression more than once. While our framework was developed for proxy programming, these advances apply to qualifier systems in general. As described in Section 3.7, they enable a number of new or improved applications, including tracking security-sensitive data in a program [41] , and supporting non-null types [13]. Contributions This paper describes the design, theory, implementation, and evaluation of a framework for proxy programming. We make the following contributions: • We formalize the problem of transparent proxy programming as one of qualifier inference, extending existing algorithms to support dynamic coercions and a form of flow-sensitivity. We have formalized our analysis as an extension of Featherweight Java (FJ) [25] , and proven it sound (Section 3). We are the first to consider qualifier inference in an object-oriented setting, and our approach enables new or improved applications of qualifier systems (Section 3.7). • We present a design and implementation of asynchronous and lazy method invocations for Java. Because handling of futures is automatic, our approach is substantially easier to use than approaches that require manipulating futures manually [32, 28, 37] . At the same time, performance can be easily tuned by the programmer. Our approach combines nicely with other Java features, like RMI and the JSR 166 concurrency libraries. (Section 4.) • We present a performance evaluation of our implementation on a variety of applications, including uses of futures and suspensions, and checking for transparency B.1 Progress Lemma B.1 (Progress) Given that (S, e 0 ) : T , then either e 0 is a variable x, or else (S, e 0 ) → (S , e 0 ) for some S and e 0 , unless the program reaches an erroneous state due to an impossible cast. Proof The proof is by induction on the syntax of expressions that satisfy (S, e 0 ) : T can take a step using a transition rule. For every non-value expression, we prove that it either can • take a reduction step directly (base cases), or • given that, if a sub-expression typechecks, then it can reduce, the whole expression can reduce (induction step). Case analysis of the possible forms of expression e 0 : Case e 0 ≡ (x): In this case the lemma is true by definition, the expression is a value. Case e 0 ≡ e. f : From Γ e 0 : T and [Field] we get Γ e : nonproxy N; Γ . Also fields(C) =Tf . Case e 0 ≡ x. f : From S : Γ and Γ x : nonproxy N; Γ we deduce S(x) = (nonproxy, new C(y)) for a {C} C ≤ N. So, e 0 reduces by [TransField]. Case e 0 ≡ e. f : From Γ e : nonproxy N; Γ and the induction hypothesis, e 0 reduces by [C-CongruenceE]. Case e 0 ≡ (e 1 .m(ē)): From Γ e 0 : T and [Invoke] we get Γ e 1 : nonproxy N; Γ . Case e 0 ≡ (x.m(ȳ)): From Γ x : nonproxy N; Γ , and S : Γ we have S(x) = (nonproxy, new C(ȳ)) for some {C} C ≤ N. Moreover by definition, from mtype(m,C) =T → U we get that mbody(m,C) = (z, e m ). So, e 0 can reduce by [TransInvoke]. Case e 0 ≡ (e 1 .m(ē)): From Γ e 1 : nonproxy N; Γ and the induction hypothesis, e 1 → e 1 and the whole expression reduces by [C-CongruenceE]. Case e 0 ≡ (x.m(ē)): From Γ e 0 : T and [Invoke] we get Γ ē :T , so by induction hypothesis,ē →ē and the whole expression reduces by [C-CongruenceBarE]. Case e 0 ≡ (new C(ē)): Case e 0 ≡ (new C(x)): Reduces by [TransAnnot]. Case e 0 ≡ (new C(ē)): From [New] and Γ e 0 : T we get Γ ē :S; Γ . So, by the induction hypothesis e 0 can reduce by [C-CongruenceBarE]. Case e 0 ≡ ((C)e): Case e 0 ≡ ((N)x): From [DCast], [UCast] or [SCast] we have that Γ x : nonproxy M, therefore from S : Γ we have that S(x) = (nonproxy, new D(x)), for a D that satisfies {D} D ≤ M. So, if {D} D ≤ N then e 0 reduces by [TransCast], otherwise we have an erroneous stuck program. Case e 0 ≡ ((C)e ): From [DCast], [UCast] or [SCast] we have Γ e : T , so by induction hypothesis, e 0 reduces by [C-CongruenceE]. Case e 0 ≡ (let x = e 1 in e 2 ): Case e 0 ≡ (let x = y in e 2 ): The term reduces by [TransLet]. Case e 0 ≡ (let x = e 1 in e 2 ): Given Γ e 0 : T we have from [Let] Γ e 1 : T ; Γ 1 . From induction hypothesis, we get that e 1 → e 1 , therefore e 0 reduces by [C-CongruenceE]. Case e 0 ≡ (makeproxy e ): Case e 0 ≡ makeproxy x: e 0 typechecks, so from [MakeProxy] we get Γ x : nonproxy N; Γ . From S : Γ we get that S(x) = (nonproxy, new C(ȳ)), for some C such that {C} C ≤ N. So, we can reduce by [TransProxy]. Case e 0 ≡ makeproxy e : From Γ e 0 : T and [MakeProxyCheck] we get that Γ e : T , therefore from the induction hypothesis, it reduces by [C-CongruenceE]. Case e 0 ≡ (coerce e ): Case e 0 ≡ (coerce x): From Γ e 0 : T and [CoerceVarCheck] we get that Γ can be written as Γ[x → Q N] such that: Γ [x → Q N] x : Q N; Γ [x → Q N]. From this and S : Γ we get that S can be written as S {x → (Q, new C(ȳ))} for some C such that {C} C ≤ N. So, e 0 reduces by [TransCoerce]. Case e 0 ≡ (coerce e ): From Γ e 0 : T and [CoerceExpCheck] we get that Γ e : Q N; Γ . Therefore, by induction hypothesis, e 0 reduces by [C-CongruenceE]. Case e 0 ≡ (if e instanceof N then e else e) Case e 0 ≡ (if x instanceof N then e 2 else e 3 ) From Γ e 0 : T and [If] we get that Γ x : nonproxy N 1 . From this and S : Γ we get that S(x) = (nonproxy, new C(ȳ)) where {C} C ≤ N 1 . So, e 0 reduces by [TransIfTrue] or [TransIfFalse]. Case e 0 ≡ (if e 1 instanceof N then e 2 else e 3 ) From Γ e 0 : T and [If] we get that Γ e 1 : nonproxy N 1 ; Γ . Therefore, by induction hypothesis, e 0 reduces by [C-CongruenceE]. B.2 Preservation Lemma B.2 (Preservation) Given that (S, e 0 ) : T , and that (S, e 0 ) → (S , e 0 ), then (S , e 0 ) : U such that U ≤ T . Proof Induction proof: • The lemma either holds for the program (S, e 0 ) directly, or • if the lemma holds for all non-value sub-expressions of e 0 , then it holds for (S, e 0 ). Case analysis of the possible forms of program (S, e 0 ): Case (S, e 0 ) ≡ (S, x): In this case the program cannot take an evaluation step, therefore by definition the lemma is true. Case (S, e 0 ) ≡ (S, e. f ): From Γ e 0 : T ; Γ and [Field] we get Γ e : nonproxy N; Γ . Also fields(C) =Tf .
doi:10.1145/1028976.1028994 dblp:conf/oopsla/PratikakisSH04 fatcat:jlse37f5vbct7gguvlpidv4q5u