DOKK Library

Transparent Object Proxies for JavaScript

Authors Andreas Schlegel Manuel Geffken Matthias Keil Peter Thiemann Sankha Narayan Guria

License CC-BY-3.0

Plaintext
Transparent Object Proxies for JavaScript
Matthias Keil1 , Sankha Narayan Guria2 , Andreas Schlegel1 ,
Manuel Geffken1 , and Peter Thiemann1

1       Institute for Computer Science
        University of Freiburg
        Freiburg, Germany
        {keilr,schlegea,geffken,thiemann}@informatik.uni-freiburg.de
2       Indian Institute of Technology Jodhpur
        Jodhpur, India
        sankha@iitj.ac.in



         Abstract
Proxies are the swiss army knives of object adaptation. They introduce a level of indirection to in-
tercept select operations on a target object and divert them as method calls to a handler. Proxies
have many uses like implementing access control, enforcing contracts, virtualizing resources.
    One important question in the design of a proxy API is whether a proxy object should inherit
the identity of its target. Apparently proxies should have their own identity for security-related
applications whereas other applications, in particular contract systems, require transparent prox-
ies that compare equal to their target objects.
    We examine the issue with transparency in various use cases for proxies, discuss different
approaches to obtain transparency, and propose two designs that require modest modifications
in the JavaScript engine and cannot be bypassed by the programmer.
    We implement our designs in the SpiderMonkey JavaScript interpreter and bytecode com-
piler. Our evaluation shows that these modifications of have no statistically significant impact
on the benchmark performance of the JavaScript engine. Furthermore, we demonstrate that con-
tract systems based on wrappers require transparent proxies to avoid interference with program
execution in realistic settings.

1998 ACM Subject Classification D.3.3 Language Constructs and Features

Keywords and phrases JavaScript, Proxies, Equality, Contracts

Digital Object Identifier 10.4230/LIPIcs.ECOOP.2015.149

Supplementary Material ECOOP Artifact Evaluation approved artifact available at
http://dx.doi.org/10.4230/DARTS.1.1.2


    1     Introduction
A proxy modifies the functionality of an underlying target object by introducing a level of
indirection that intercepts all operations on the target. As all problems in computer science
can be solved by another level of indirection1 , proxies may be called the Swiss army knives of
object adaptation. Indeed, proxies are widely used to perform resource management, to access
remote objects, to impose access control [20, 13], to implement contract checking [19, 10, 5],
to restrict the functionality of an object [19], to enhance the interface of an object [22], to


1
    A famous quote by David Wheeler.
             © Matthias Keil, Sankha Narayan Guria, Andreas Schlegel, Manuel Geffken,                                    tifact
                                                                                                               Ar
             and Peter Thiemann;                                                                                         t   * Complete
                                                                                                                                           *




                                                                                                                      en
                                                                                                      EC O C P *




                                                                                                                                            AECl Do
                                                                                                                                      *
                                                                                                               st




                                                                                                                                          We
                                                                                                             * onsi




             licensed under Creative Commons License CC-BY
                                                                                                          O




                                                                                                                                             l
                                                                                                                                                  *
                                                                                                                                          cu m
                                                                                                                se
                                                                                                             eu




29th European Conference on Object-Oriented Programming (ECOOP’15).
                                                                                                                                            Ev
                                                                                                                                              e




                                                                                                                      nt             R
                                                                                                                        ed
                                                                                                                           * Easy to
                                                                                                           *




                                                                                                                                  alu
                                                                                                                   at e d
Editor: John Tang Boyland; pp. 149–173
                    Leibniz International Proceedings in Informatics
                    Schloss Dagstuhl – Leibniz-Zentrum für Informatik, Dagstuhl Publishing, Germany
150       Transparent Object Proxies for JavaScript



      1    var target = { /∗ some object ∗/ };
      2    var handler = { /∗ empty handler ∗/ };
      3    var proxy = new Proxy (target, handler);
      4    proxy===target; // evaluates to false
              Listing 1 Comparing a proxy with its target. The methods of handler determine the behavior
          of proxy. If handler is empty, then proxy behaves exactly like target.



          implement dynamic effect systems, as well as for meta-level extension, behavioral reflection,
          security, and concurrency control [16, 2, 4].
              A proxy implementation provides an intercession API that enables the programmer to
          trap all operations on the target object (with few exceptions). Further, a program should
          not be able to distinguish a proxy from a non-proxy object so that putting a proxy in
          place of an object does not affect the outcome of the program (save for the new behavior
          introduced by the proxy). For that reason, the JavaScript Proxy API [20], a part of the
          current ECMAScript 6 draft standard, does not include a function that checks whether an
          object is a proxy, it does not provide traps for all operations on objects, and it restricts some
          traps to avoid breaking certain object invariants [21].
              However, the JavaScript Proxy API embodies a design decision that reveals the presence
          of proxies in some important use cases. This decision concerns object equality. The API
          description2 says: The double and triple equal (==, ===) operator is not trapped. p1 ===
           p2 if and only if p1 and p2 refer to the same proxy. The standard does not even mention
          proxies in the definition of object equality: If x and y are the same Object value, return true.
          [9, Section 7.2.13] In other words, proxies are opaque, which means that each proxy has its
          own identity, different from all other (proxy or non-proxy) objects. Given opaque proxies, an
          equality test can be used to distinguish a proxy from its target as demonstrated in Listing 1.
              Even though target and proxy behave identically, they are not considered equal. Thus, in
          a program that uses object equality, the introduction of a proxy along one execution path
          may change the meaning of the program without even invoking an operation on the proxy
          (which may behave differently from the same operation on the target).
              Equality for opaque proxies is straightforward to implement and works well under the
          assumption that proxies and their targets are never part of the same execution environment.
          For example, the revocable membrane pattern [20] enables to safely pass object references
          to untrusted code, control their operation on these objects, and revoke all access rights
          afterwards. This pattern is implemented using proxies and it partitions the execution
          environment into security realms so that the objects that live in the same realm are never
          in a proxy-target relationship. By this convention, the situation outlined in Listing 1 never
          arises inside a compartment.
              But the assumption that proxies never share their execution environment with their
          targets is not always appropriate. One prominent use case is the implementation of a
          contract system. A contract system provides a domain specific language to state very precise
          type-like assertions for values in an untyped language. Two examples for such systems are
          the contract framework of Racket [11, Chapter 7] and Contracts.js for JavaScript [5]. Both
          systems implement contracts on objects with specific wrapper objects, Racket’s chaperones
          or impersonators [19] and JavaScript proxies, respectively.


          2
              https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                      151



1    // contract wrapper implementation
2    function checkPredicate (pred) {
3       return {
4         set: function(target, prop, val) {
5            if (!pred (val)) { throw new ContractException(); };
6            target[prop] = val;
7         }
8       }
9    };
10   function assertContract(target, pred) {
11      return new Proxy (target, checkPredicate(pred));
12   }
13   // application code
14   function addBonus (acc1, acc2, amount) {
15      acc1.balance += amount;
16      if (acc1 !== acc2) { // test objects for equality
17        acc2.balance += amount;
18      }
19   }
20   var account = { balance: 10 };
21   var restricted = assertContract (account, function(x) {
22      return (x >= 0);
23   });
24   addBonus (account, account, 40); // raises account by 40
25   addBonus (restricted, account, 40); // raises account by 80
        Listing 2 Application with contract wrapper.




         Listing 2 contains a JavaScript implementation of a simple contract wrapper. The
     implementation uses JavaScript proxies, which are introduced in more detail in Section 2.
     The wrapper applies to a JavaScript object and it enforces that all property values written
     to the object fulfill a predicate pred. Otherwise, the wrapper raises an exception.
         The call assertContract (target, pred) returns a proxy for the target object with a handler
     created by the function call checkPredicate (pred). Whenever a property prop is set on the
     proxy, the method set of its handler is invoked with the target object, the property prop, and
     the new value as arguments. The handler throws an exception if the predicate pred is not
     fulfilled, otherwise it performs the set operation on the target.
        The application code contains an addBonus function that takes two accounts and a bonus
     amount to add. The intention is to give a bonus to each account once. Thus, if the two
     accounts are different, then the balance of the second account must be adjusted, too.
         The last couple of lines create an account and a restricted handle to the same account,
     where the restricted handle does not permit the account to overdraw. In line 24, a bonus
     of 40 is added to account and account. This bonus addition executes correctly because
     the equality test in line 16 yields false. However, performing the bonus addition with the
     restricted version and the standard account leads to adding a bonus of 80 to account because
     the test in line 16 yields true.
        This example shows that the introduction of a contract monitor like assertContract may
     change the semantics of a program even in cases where the contract is not exercised. But this


                                                                                                       ECOOP’15
152   Transparent Object Proxies for JavaScript


      change in behavior violates a ground rule for monitoring: a monitor should never interfere
      with a program conforming to the monitored property. (In Section 3, we make a similar case
      for access restricting membranes.)
          While this particular example is constructed we demonstrate in Section 7 that such
      situations do occur in practice. Racket programmers have also run into this issue3 , as
      chaperones and impersonators behave opaquely with respect to Racket’s eq? operation.
          As a remedy, we propose alternative designs for transparent proxies that are better
      suited for use cases such as certain contract wrappers and access restricting membranes.
      We evaluate these designs with respect to usability. We further implement them in the
      Firefox SpiderMonkey JavaScript engine and evaluate the impact of transparent proxies on
      benchmark performance.

      Overview and contributions
      Section 2 introduces the JavaScript Proxy API, explains the membrane pattern, and sketches
      the implementation of a contract system based on proxies. Section 3 discusses different
      use cases of proxies and assesses them with respect to the requirements on proxy trans-
      parency. Section 4 contains an in-depth discussion of the programmer’s expectation from
      an equality operation and how it would be affected by the design of a proxy API. Section 5
      presents alternative designs to obtain transparency. We present two novel designs for
      transparent proxies that do not impede the implementation of advanced proxy
      management. In Section 6, we describe how we implement these two designs in the
      Firefox JavaScript engine (interpreter and baseline JIT compiler). In Section 7,
      we demonstrate that our modification to the JavaScript engine does not affect
      benchmark performance. Furthermore, we present evidence that the danger of
      interference for a contract implementation based on opaque proxies is real: it
      arises in instrumented benchmark programs, not just in artificially constructed examples.
      Section 8 discusses related work and Section 9 concludes.
          The technical report [12] accompanying this paper contains an appendix with a detailed
      descriptions of the algorithms used in our implementation. The implementation of the
      JavaScript engine with transparent proxies is available in a Github repository4 .

          2    Proxies, Membranes, and Contracts
      This section introduces the JavaScript Proxy API and presents two typical applications of
      proxies: membranes that regulate access to an object network and contracts that check
      assertions on the values manipulated by a program.

      2.1      Proxies
      A proxy is an object intended to be used in place of a target object. As the target object may
      also be a proxy, we call the unique non-proxy object that is transitively reachable through
      a chain of proxies the base target for each proxy in this chain. The behavior of a proxy is
      controlled by a handler object, which may modify the original behavior of the target object in
      many respects. A typical use case is to have the handler mediate access to the target object,
      but in JavaScript the handler has a full range of intercession facilities that we only touch on.


      3
          Personal communication with Robby Findler, February 2015.
      4
          https://github.com/sankha93/js-tproxy
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                         153



                                                       handler.get(target, ’x’, proxy);
                                          Handler
                                                       handler.set(target, ’x’, 1, proxy);


                                                                                     Meta-Level
                                                                                     Base-Level


                                   proxy.x;                                  target[’x’];
                      Proxy                                     Target
                                   proxy.y=1;                                target[’y’]=1;


        Figure 1 Example of a proxy operation.


        The JavaScript Proxy API [20] contains a proxy constructor that takes the designated
     target object and a handler object:
26   var target = { /∗ some properties ∗/};
27   var handler = { /∗ trap functions... ∗/ };
28   var proxy = new Proxy (target, handler);

         The handler object contains optional trap functions that are called when the corresponding
     operation is performed on the proxy. Operations like property read, property assignment,
     and function application are forwarded to the corresponding trap. The trap function may
     implement the operation arbitrarily, for example, by forwarding the operation to the target
     object. The latter is the default functionality if the trap is not specified.
         Performing an operation like property get or property set on the proxy object results in a
     meta-level call to the corresponding trap on the handler object. For example, the property
     get operation proxy.x invokes handler.get(target,’x’,proxy) and the property set operation
     proxy.y=1 invokes the trap handler.set(target,’y’,1,proxy), if these traps are present.
         Figure 1 illustrates this situation with a handler that forwards all operations to the target.
         However, a handler may redefine or extend the semantics of an operation arbitrarily. For
     example, a handler may forward a property access to its target object only if the property
     is not locally present. The following example demonstrates a handler that implements a
     copy-on-write policy for its target by intercepting all write operations and serving reads on
     them locally. Thus, reading target.a at the end may return a different value than 42.
29   function makeHandler() {
30     var local = {};
31     return {
32        get: function(target, name, receiver) {
33           return (name in local) ? local[name] : target[name];
34        },
35        set: function(target, name, value, receiver) {
36           return local[name]=value;
37        }
38     };
39   }
40   var child = new Proxy (target, makeHandler());
41   child.a = 42; // does not change target




                                                                                                          ECOOP’15
154        Transparent Object Proxies for JavaScript



                                                 P1                           T1

                                             x                            x
                                                      y                            y

                                        P2       z                   T2       z

                                                          P3                           T3


                Figure 2 Property access through membrane.


               Proxy and handler objects are based on the JavaScript Proxy API [20, 21], which is part
           of the JavaScript draft standard ES6. This API is implemented in Firefox since version 18.0
           and in Chrome V8 since version 3.5.
               JavaScript’s proxies are opaque: each proxy object has its own identity different from all
           other (proxy) objects. The proxy identity is observable with the JavaScript equality operators
           == and ===: When applied to two objects, both operators compare object identities.5
               The following example (which continues the preceding code fragment) illustrates this
           behavior. Comparing distinct proxies returns false even though the underlying target is the
           same. Similarly, the target object is different from any of its proxies.
      42   var proxy2 = new Proxy (target, handler);
      43   (proxy==proxy2); // false
      44   (proxy==target); // false
               We already mentioned in the introduction that equality cannot be trapped. While there
           are good reasons for this design decision [21], we mention in passing that it would be hard to
           design and implement an efficient equality trap because equality is a binary method.

           2.2      Membranes
           A membrane is a regulated communication channel between an object and the rest of the
           program. It ensures that all objects reachable from an object behind the membrane are also
           behind the membrane. Figure 2 shows a membrane (dashed line) around targets T1, T2,
           and T3 implemented by the wrapper objects P1, P2, and P3. Each property access through
           the wrapper (e.g., P1.x) returns a wrapper for T1.x, which is created on demand. After
           installing the membrane, no new direct references to target objects behind the membrane
           become available. This mechanism may be used to revoke all references to an object network
           at once or to enforce write protection on the objects behind the membrane [20, 17].
               An identity preserving membrane is a membrane that furthermore guarantees that no
           target object has more than one proxy. Thus, proxy identity outside the membrane reflects
           target object identity inside. That is, if T1.x.z===T1.y, then also P1.x.z===P1.y. Figure 2
           depicts such an identity preserving membrane.
               Both kinds of membrane may be implemented with the JavaScript Proxy API and a weak
           map that associates target objects with their proxies [20].


           5
               If one argument has a primitive type, == attempts to convert the other argument to the same primitive
               type, whereas === returns false if the types are different. If both arguments are objects, then both
               operators do the same.
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                     155


     2.3    Contracts

     Dynamically checked software contracts lie at the core of Meyer’s Design by Contract™
     methodology [15]. A contract specifies the interface of a software component by stating
     obligations and benefits for the component’s implementors and users. Contracts state
     invariants for objects as well as preconditions and postconditions for functions and methods.
     Contracts are particularly important for dynamically typed languages as these languages do
     not provide static guarantees beyond memory safety. For such languages, contract systems
     are indispensable tools to create maintainable software.
        A contract may specify a property of a value. In many cases, a simple assertion suffices,
     but many interesting properties of functions and objects cannot be checked immediately. For
     example, the contract Num→Num expresses that a function maps a number to a number. It
     can only be checked when the function is called: the caller must provide a number argument
     and then the result must be a number, too. Similarly, a check that a certain object property
     must always be assigned a number can only be checked when actually setting the property.
         We call such contracts on functions and objects delayed contracts, because their assertion
     never signals a contract violation immediately. The standard implementation of a delayed
     contract is by wrapping the function/object in a proxy. The proxy’s handler implements
     traps to mediate the use of the function/object and to assert its contract when the function
     is called or the object is read or written to.
         The following example sketches the implementation of a contract assertion for Num→Num.
     It installs a proxy where the handler supplies an apply trap that gets invoked when the proxy
     is called as a function. The arguments to apply are the target object, the this object, and an
     array containing the arguments.

45   var handler = {
46      apply: function(target, thisArg, argsArray) {
47        if (typeof argsArray[0] !== ’number’) { throw new Exception (); }
48        var result = target.apply(thisArg, argsArray);
49        if (typeof result !== ’number’) { throw new Exception (); }
50        return result;
51      }
52   };
53   var addOne = function (x) { return x+1; };
54   var addOneNN = new Proxy (addOne, handler);

        Thus, the monitored function addOneNN is implemented by a proxy with a suitable
     handler. The contract systems Contract.js [5] and TreatJS [14] are both implemented in this
     way. As JavaScript does not support “proxification” (i.e., the transformation of an arbitrary
     object into a proxy), it is conceivable that addOne, the original object, and addOneNN, the
     proxy, are both accessible in the same execution environment.



      3     Opacity vs. Transparency for Proxies

     In this section, we question whether JavaScript proxies need be opaque by considering
     various use cases for proxies and evaluating whether they could be served equally well with
     transparent proxies, where equality is defined as equality of base targets.



                                                                                                      ECOOP’15
156   Transparent Object Proxies for JavaScript


      3.1      Use Case: Object Extension
      A common use case of proxies is to extend or redefine the semantics of particular operations
      on objects. For example, a handler may throw an exception instead of returning undefined,
      it may redirect different operations to different targets (for example to store changes locally
      or to implement placeholders), it may log or trace operations, or it may notify observers.
          In this case, using the proxy may lead to a completely different outcome than using the
      target object. Thus, proxy and target object should not be confused.

      3.2      Use Case: Access Control
      Revocable references are the motivating use case for membranes [20, 17]. Instead of passing
      a target object to an untrusted piece of code, the idea is to pass its proxy wrapped in a
      protecting membrane. Once the host application deems that the untrusted code has finished
      its job, it revokes the reference which detaches the proxy from its target. The membrane
      extends this detachment recursively to all objects reachable from the original target.
          Opaque proxies are suitable for implementing membranes as well as their identity pre-
      serving variant. However, transparent proxies would work just as well, because the host
      application only sees original objects whereas the untrusted code only sees proxies. Further-
      more, the implementation of revocable references and membranes ensures that there is at most
      one proxy for each original object. If an execution environment is compartmentalized like
      this, then each compartment has a consistent view with unique object (or proxy) references,
      regardless whether proxies are opaque or transparent. In fact, with transparent proxies, a
      membrane is always identity preserving, the weak map only improves the space efficiency.

      3.3      Use Case: Contracts
      Proxies implement contracts in Racket [19] and in JavaScript [5, 13, 14]. During maintenance,
      a programmer may add contracts to a program as understanding improves. To systematically
      investigate a program in this way, the addition of a new contract must not change a program
      execution that respects the contract already. In this scenario, the program executes in a
      mix of original objects and proxy objects. Furthermore, there may be more than one proxy
      (implementing different contracts) for the same target. If introducing proxies affected the
      object identity, then some equality comparisons on objects (eq? in Racket and ==, !=, ===,
      or !== in JavaScript) would flip their outcome, thus changing the semantics.
          Our experimental evaluation (Section 7.2) considers a typical program understanding
      and maintenance scenario where a programmer inserts assertions/contracts to document and
      validate his/her understanding of the program. We find that mixed (proxy vs. non-proxy)
      object comparisons occur in realistic programs.
          Similar incidents where observed when maintaining Racket’s preferences framework.
      Registering a callback with the framework wrapped the callback in a contract before storing
      it in an eq?-based weak hash table. Because there were no further references to the wrapper,
      the weak table released it on the next garbage collection. Thus, the callback disappeared
      mysteriously, leading to unwanted behavior6 . This problem is evidence that such mixes occur
      in real situations and also in a system where contracts are aligned with module boundaries.
          Thus, we see strong evidence that unintended mixing is hard to avoid even in a well-
      designed system. If a module has a higher-order interface, then a function passed as a


      6
          Personal communication with Robby Findler, February 2015.
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                          157


     parameter may capture an un-proxied version of an object that is also passed as a regular
     parameter. For example, let T be some delayed contract and consider the module interface
55   // foo : (T → boolean, T) → boolean
     so that foo carries a wrapper that asserts this contract. The function accepts two parameters,
     the first of which is a function. An external caller may use foo as follows:
56   var x = { /∗ some object ∗/ };
57   var f = function (y) { return x===y; };
58   foo(f, x);
     The call to foo wraps f and x in the respective contract wrappers for T → boolean and T.
     Unfortunately, wrapping the function f does not affect the free variable x in its environment.
        Now consider the following contract-abiding implementation of foo:
59   function foo (f, y) { return f (y); }
     Inside of foo, y is wrapped in the T contract and applying f (wrapped in T → boolean) may
     wrap it one more time in a T. Thus, in the body of f (line 57), x is unwrapped and y is the
     same object wrapped at least once in T. Thus, assuming opaque proxies, x===y yields false,
     which is different from the result before installing the contract on foo (line 55).
         Thus, the existing implementations of higher-order contracts for JavaScript are prone
     to interfere with the semantics. The situation is similar for Racket. Racket’s chaperones
     and impersonators are opaque because they may be distinguished from their target and
     from one another using eq?. This choice is legitimized by pointing out that the preferred
     equality test in Racket is the equal? function that compares two values for structural
     equality, a non-trivial functionality that is not available out-of-the-box in JavaScript (cf. [1]).
     Clearly, chaperones and impersonators are transparent with respect to equal?. The paper
     on chaperones and impersonators [19] further acknowledges that “wrappings do affect the
     identity of objects, as compared with JavaScript’s === or Racket’s eq? comparisons”, but
     remarks that Racket programmers rely much less on object comparison than JavaScript
     programmers. Indeed, the paper’s formalization includes equal?, but not eq?.

     3.4    Assessment
     Neither the opaque nor a transparent proxy implementation can be labeled as right or wrong
     without further qualification. Each is appropriate for particular applications and may lead
     to undesirable behavior in other applications.
         Opaqueness is required for a proxy that changes the behavior of its target significantly.
     This case corresponds to the use case for impersonators [19].
         Transparent proxies can safely be used to implement revocable membranes as well as for
     other applications that guarantee compartmentalized execution (where proxies and targets
     never meet). Their use simplifies the implementation of the identity preserving membrane
     because the weak map from targets to their proxies may be elided. However, the price for
     this elision is increased space usage for multiple wrappers for the same target. It must be
     weighed against the time taken to administer a weak target-to-proxy mapping.
         Transparency is required for proxies that implement contract wrappers to avoid the
     interference with the normal program execution pointed out in the introduction and in
     Section 3.3. To avoid such interference with opaque proxies, a programmer would have to
     guarantee that contracts are only ever applied to unique object references or that references
     to the target object do not escape. Such a guarantee may be established by only applying



                                                                                                           ECOOP’15
158   Transparent Object Proxies for JavaScript


      contracts to objects as they are created or by static analysis. However, the former is an
      unrealistic assumption and the latter severely limits the freedom that developers want to
      obtain by using contracts in a dynamically typed language: rather than submitting to a type
      system, they must submit to a uniqueness or an escape analysis.
          A similar case can be made for further wrapper-based programming patterns. For example,
      a wrapper that records all operations performed on an object reference can be very helpful
      while debugging. Clearly, such a wrapper must be indistinguishable from its target object.
          It is also clear that the behavior of equality is not something that should be left to the
      whim of the programmer. For example, equality on objects should be an equivalence relation,
      which means that the equality operations == and === must not be trapped [21].
          Thus, the current state of affairs in JavaScript is fully justified, but it is not well suited
      to implement contract systems. To obtain some insight into a proxy design suitable for
      implementing contracts, we first explore the important issue of equality and then consider
      some designs and assess the suitability with respect to the use cases outlined in this section.


          4   Invariants for Equality
      What does a programmer expect from an equality function on objects? Let’s consult the
      language references for Racket, Java, and JavaScript for cues.
            Racket inherits its hierarchy of equality operators from Scheme: equal?, eqv?, and
      eq?. The Scheme report [18] specifies them as follows: “An equivalence predicate is the
      computational analogue of a mathematical equivalence relation; it is symmetric, reflexive,
      and transitive. Of the equivalence predicates described in this section, eq? is the finest or
      most discriminating, equal? is the coarsest, and eqv? is slightly less discriminating than
      eq?.” The intuition is that equal? implements structural equality, eq? implements pointer
      equality, and eqv? is a compromise between the expensive structural equality7 and pointer
      equality, which may distinguish different boxings of the same number.
            There are further differences between eq? and equal?. The function eq? is guaranteed
      to run in O(1), whereas there is no known implementation of equal? that runs in less than
      linear time. Furthermore, equal? is not stable in the sense that (equal? x y) may hold at
      some point during execution, but this equality may be destroyed by subsequent assignments
      in the program. In contracts, (eq? x y) does not change as the program proceeds.
            Java has an equals method in java.lang.Object. The documentation of this class
      reads:8 “The equals method implements an equivalence relation on non-null object references:
      . . . ” It also asks that repeated invocations with the same arguments behave consistently in
      that they always return the same answer. And finally: “The equals method for class Object
      implements the most discriminating possible equivalence relation on objects; that is, for any
      non-null reference values x and y, this method returns true if and only if x and y refer to
      the same object (x == y has the value true).” About the == operator, the JLS says:9 “The
      equality operators are commutative . . . ” and then “the result of == is true if the operand
      values are both null or both refer to the same object or array; otherwise, the result is false.”
      Regarding stability and execution time, the == operator is stable and runs in O(1). The
      equals methods is recommended to be implemented in a stable way, but this restriction is
      not enforced. No bounds on the execution time are prescribed and none can be given.


      7
          It is supposed to work on cyclic structures, too. [1]
      8
          http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
      9
          http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html in 15.21
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                        159


         The ECMAScript specification [8, Section 11.9] does not mention algebraic properties of
     the equality operation, but rather contains pseudocode for the abstract equality comparison
     algorithm, which underlies the == operator. This algorithm implements an operation, which
     is symmetric, but neither reflexive nor transitive: It is not reflexive because NaN==NaN
     is false and it is not transitive because new String("foo")=="foo" and "foo"==new String
     ("foo"), but new String("foo")==new String("foo") is false due to unfortunate interaction
     with type conversions. Restricted to object arguments the algorithm says: “Return true if
     x and y refer to the same object. Otherwise, return false.” The strict equality comparison
     algorithm (in Section 11.9.6), which specifies the === operator, is not reflexive because of
     NaN, but appears to be symmetric and transitive. Thus, it is at least a partial equivalence
     relation. Restricted to objects, both equalities are equivalences if that is implied by sameness.
         JavaScript’s == operator is not stable: Consider a comparison between an object and a
     string and then change the toString method of the object. However, === is stable because it
     does not involve type conversion. Restricted to object arguments, both equalities are stable.
         The least common denominator for an object equality appears to be that equality must
     be a stable equivalence relation. Often, object equality is explained by alluding to
     the “sameness” of objects, which is left to further interpretation. In particular, the JLS
     explicitly mentions that == is overly discerning for String objects and none of the language
     specifications addresses proxy objects.
         Some proponents of opaque proxies ask that equality should be an observational equiva-
     lence. That is, for any conditional of the form

60    if (a === b) Statement

     (where a and b are variables) it should be possible to substitute b for a (but respecting lexical
     binding rules) in Statement without changing the semantics. However, in the presence of
     JavaScript’s with (head){ ... body ... } statement inside Statement further qualifications are
     needed. As with extends the lexical environment by placing head on top of the scope chain
     while executing body, a property of head may shadow any variables in scope including a.
     Thus, the substitution must not extend to the body of a with statement.
         If the above conditional appears in the body of a with (head){ ... body ... } statement,
     then essentially all bets are off. The object head may define a or b via a getter function
     or via a proxy handler, which may be impure and return different results on each access.
     Alternatively, a and b may be changed by simple assignment to head.
         Furthermore, the Statement may contain a call to eval. As this call is executed in the
     lexical environment of its call site, its execution may perform an assignment to a or b. As
     the call to eval may be performed indirectly by storing eval in a different variable or object
     property, essentially every function or method call may assign to a or b.
         While ES5 strict mode abolishes the with statement and severely restricts the use of eval,
     a call to eval may still assign to variables in scope at the point of the call. For example, the
     following function f42 always returns 42 because the eval assigns to an existing local variable.

1         function f42(x) {
2           "use strict";
3           eval("x = 42’’);
4           return x;
5     }

         Thus, arriving at a practically useful definition of observational equivalence in JavaScript
     is quite intricate; perhaps too intricate to be a useful reasoning tool for programmers.



                                                                                                         ECOOP’15
160        Transparent Object Proxies for JavaScript


           Moreover, we are not aware of a language definition that requires its equality operator to be
           an observational equivalence.
               What is the take-home message from this discussion with respect to the question whether
           a proxy implementation should be opaque or transparent with respect to equality? We believe
           that most programming patterns using equality only expect a stable equivalence relation,
           which is easy to implement for transparent proxies, as we show in Section 5.3. However,
           disregarding the problems with the with statement and eval, a weak version of observational
           equivalence is certainly desirable: For any conditional of the form
      61   if (a === b) Statement

           if we substitute b for a in Statement, then either the semantics of Statement does not change
           or Statement raises an exception.
               However, a transparent proxy without further restrictions would not even fulfill weak
           observational equivalence. As a compromise, the behavioral change of a transparent proxy
           could be restricted to implement a projection, a point that we come back to in Section 5.4.


            5     Design Alternatives for Proxy Equality
           In this section, we explore some alternatives for designing proxies and equalities and discuss
           their suitability for the use cases outlined in Section 3.


           5.1    Program Rewriting
           One way to obtain transparent proxies with an implementation of opaque proxies is to replace
           all occurrences of ==, !=, ===, and !== with proxy-aware equality functions. These
           functions can be implemented in JavaScript using a weak map from proxies to their target.
           The proxy constructor would be extended to maintain this map. It would be possible to
           treat some proxies as transparent and the rest as opaque.
               This approach preserves the existing behavior and retains the possibility to distinguish
           proxies from target objects in library code implementing proxy abstractions. A macro system
           like SweetJS [6] may be used to implement such a transformation elegantly, alas, SweetJS is
           currently implemented as an offline transformation and would need to be extended to deal
           with eval and dynamic loading.


           5.2    Additional Equality Operators
           Another approach would be to reinterpret the JavaScript equality operators == and ===
           as proxy-transparent and introduce new variants, :==: and :===:, say, for their opaque
           cousins (i.e., the current implementations of == and ===). The former operators are used
           in application code whereas the implementation of proxy abstractions would make use of the
           opaque operators where needed.
               No code transformation is required with this approach. However, it is not clear how to
           ensure that application code does not use the opaque operators. It is not even clear if it
           should not use them. While proxy abstractions can be implemented, the distinction between
           application and library seems too brittle.
               Given both operations, application code can test if two objects are in a proxy relation
           with the same target:
      62   ((x === y) != (x :===: y)); // true, iff x is in a proxy relation to y
     M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                         161



1     function GetIdentityObject(obj) {
2       while(isProxy(obj) && isTransparentProxy(obj)) {
3         obj = getProxyTargetObject(obj);
4       }
5       return obj;
6     }
        Listing 3 Pseudo code for GetIdentityObject.



        Furthermore, the implementation would either have to use macros, which gives rise to
     the problems discussed in Section 5.1, or it would have to be implemented in the JavaScript
     engine, where it requires changes starting in the parser. This point makes it unlikely that
     such a proposal would be adopted by the community. Moreover, a proliferation of equality
     operations is confusing for developers as there are already three different kinds of equality in
     JavaScript: besides equality and strict equality, there is a third equality that fixes reflexivity
     when comparing NaN values.

     5.3    Transparent Proxies in the VM
     We already discussed that trapping the equality operation is not appropriate. As an
     alternative, we implement transparent proxies as an extension of a JavaScript VM (cf.
     Section 6) and provide different constructors for transparent and for opaque proxies. This
     extension provides a different kind of proxy object on which equality comparison behaves
     differently. Before testing reference identity as the last step in a comparison of two objects,
     the equality comparison calls a new internal function GetIdentityObject (see Listing 3) that
     computes the base target of an object. For a non-proxy object, the function returns its
     argument. For a proxy object, GetIdentityObject checks whether the proxy is transparent.
     If that check fails, then GetIdentityObject returns the reference to the current proxy object.
     Otherwise, it iteratively performs the same checks on the proxy’s target. For consistency,
     the GetIdentityObject method also needs to be called in other computations that depend on
     object identity. One example is the WeakMap abstraction of the ES6 draft standard.
         This design enables both scenarios described in Sections 3.2 and 3.3. It also guarantees
     that equality (on objects) is an equivalence relation.
         Transparent proxies need special attention because there are abstractions that require
     to test whether a reference is a (transparent) proxy. For example, the implementation of
     access permissions contracts [13] extracts the current permission from a proxy to construct a
     new proxy with an updated permission. This introspection improves the efficiency of the
     implementation; its absence would lead to long, inefficient chains of proxy objects.
         Thus, for implementing proxy abstractions it is useful to be able to break the transparency.
     We propose to use secret tokens for this purpose. A token (just an object in JavaScript)
     stands for a transferable right to perform a particular operation. We attach the token
     object to a proxy by making it an extra argument to the constructor of transparent proxies,
     TransparentProxy, say. Being a standard object, the token can be hidden in the scope of the
     function that wraps objects.
63    var wrap = (function() {
64      var token = {};
65      return function(target, handler) {
66        return new TransparentProxy(target, handler, token);



                                                                                                          ECOOP’15
162        Transparent Object Proxies for JavaScript


      67     };
      68     // further operations on wrappers
      69   })();
               Later on, the token can be used to make the transparent proxy visible for equality
           operations. To this end, we need an equality operation Object.equals that takes the token as
           a third (optional) parameter. The following snippet demonstrates this operation in action.
      70   var token = {};
      71   var target = { ... };
      72   var proxyA = new TransparentProxy (target, handlerA, token);
      73   var proxyB = new TransparentProxy (target, handlerB, token);
      74   target == proxyA; // true
      75   proxyA == proxyB; // true
      76   Object.equals(target, proxyA, token); // false: token reveals proxy identity
      77   Object.equals(proxyA, proxyB, token); // false
      78   Object.equals(target, proxyA, { /∗ some other object ∗/ }); // true
      79   Object.equals(proxyA, proxyB, { /∗ some other object ∗/ }); // true
               Weak maps and other internal data structures that depend on object equality may be
           extended with tokens in the same way.
               From different point of view, the tokens assign transparent proxies to distinct realms. Thus,
           instead of passing tokens one could use object capabilities to create proxies in a particular
           realm and to create an equality function that only reveals proxies for that realms10 . A realm
           constructor may be implemented in JavaScript on top of our token-based implementation.
      80   TransparentProxy.createProxyConstructor = function() {
      81     var token = {};
      82     var equals = function (x, y) {
      83       return Object.equals(x, y, token);
      84     }
      85     var Constructor = function(target, handler) {
      86       return new TransparentProxy(target, handler, token);
      87     }
      88     return {Constructor:Constructor, equals:equals};
      89   }
           The realm constructor returns a new transparency realm represented by an object that
           consists of a fresh constructor for transparent proxies (named Constructor) and an equals
           function revealing proxies of that realm.
      90   var realm = TransparentProxy.createProxyConstructor();
      91   var proxy1 == realm.Constructor(target, handler);
      92   var proxy2 == realm.Constructor(target, handler);
           The proxies proxy1 and proxy2 are transparent with respect to equality unless someone uses
           the realm.equals method.
      93   proxy1 === proxy2; // true
      94   Object.equals(proxy1, proxy2); // true
      95   realm.equals(proxy1, proxy2); // false


           10
                We would like to thank the anonymous ECOOP 2015 reviewer who suggested this elegant solution.
      M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                      163


      Here, === and Object.equals return true and do not reveal proxies. However, the realm
      .equals function is a capability that represents the right to reveal proxies of that realm.
      Realm-aware weak maps and other internal data structures can be created in same way.

      5.4    Observer Proxies
      To implement contracts, transparent proxies need not be fully general. It would be sufficient
      if transparent proxies would be restricted to Observers that implement a projection: they
      would either return a value identical to the value that would be returned from the target
      object (this should also include the same side effects) or that restricts the behavior of the
      target object. An Observer can cause a program to fail more often, but otherwise it would
      behave in the same way as if no observer were present.
          A similar feature is provided by Racket’s chaperones [19]. A chaperone is a proxy that
      either returns an identical value, returns a chaperone of this value, or throws an exception.
      This restricted kind of proxy is shown to be sufficient to implement a contract system. (Recall
      that chaperones are not transparent.)
          The following code snippet sketches the implementation of an Observer proxy in JavaScript
      that mimics Racket’s chaperone proxy. The example considers the get trap only, but other
      traps can be implemented in the same way.
 96   function Observer(target, handler) {
 97      var sbx = new Sandbox(/∗ some parameters ∗/);
 98      var controller = {
 99         /∗ further traps omitted ∗/
100         get: function(target, name, receiver) {
101           var result = (trap=handler[’get’]) ? sbx.call(trap, target, name, receiver) :
                   undefined;
102           var raw = target[name];
103           return (result===raw) ? result : raw;
104         }
105      };
106      return new TransparentProxy(target, controller);
107   }
108   var target = { /∗ some object ∗/ };
109   var handler = {
110      get:function(target, name, receiver) {
111         return target[name];
112      }
113   };
114   var observed = Observer(target, handler);
          The constructor starts with instantiating a sandbox (line 97). The sandbox is drawn from
      another contract system for JavaScript, TreatJS [14], that uses membranes and decompilation
      to implement access restrictions. Functions execute inside the sandbox without interfering
      with the normal execution.
          The implementation distinguishes between a user specified handler (handler) whose traps
      are evaluated in the sandbox to guarantee noninterference and the proxy handler (controller)
      which is used to implement the behavior of the observer. The controller’s get trap first checks
      the presence of a get trap in the user handler, before it evaluates this trap in the sandbox.
      Next, it performs a normal property access on the target value. This step is required to



                                                                                                        ECOOP’15
164   Transparent Object Proxies for JavaScript


      produce the same side effects and to get a reference value to compare the results. Finally,
      the reference value is compared with the result from calling the trap. Line 103 makes only
      sense when using transparent proxies. The user specific trap can return an observer of the
      reference value.
          Indeed, the implementation is only correct if one can ensure that result is either the raw
      value or a transparent proxy generated by an Observer. A user specific handler can simply
      elude the observers behavior by returning a transparent proxy of the same target but with a
      different handler object. Correctness can be guaranteed by either hiding transparent proxies
      from the user level or by using the sandbox to restrict resources access be the handler’s trap.

      5.5    Recommendation
      There is likely no single semantics for object identity that fits the programmers expectation
      in all possible contexts. A proxy that changes the behavior of its target object significantly
      needs its own identity and thus needs to be implemented opaquely.
          A contract proxy that only restricts the behavior of the original object, we propose to
      use a transparent observer proxy with the design explained in Section 5.3. For these proxies,
      === (and friends) will be forwarded to the target objects, recursively. An observer proxy
      (see Section 5.4) limits the possible change of behavior analogously to chaperones. Technically,
      observer proxy weakly simulate the original objects.


       6     Implementation
      We implemented two prototype extensions of the SpiderMonkey JavaScript engine, one
      according to the design of Section 5.3 and another which extends the proxy handler by an
      isTransparent trap that regulates the proxy’s transparency. The first prototype implements
      a new global object TransparentProxy that implements the constructor for transparent proxy
      objects.
          Proxies created with new Proxy in the second prototype are generally opaque, unless
      they implement an isTransparent trap and this trap returns false. Proxies created with
      new TransparentProxy are generally transparent unless they are compared with Object.equal
      using the correct token. These choices guarantee full backwards compatibility.
          The implementation is rather tedious because many things have to be implemented three
      times as SpiderMonkey consists of an interpreter, the baseline JIT compiler (JaegerMonkey),
      and the IonMonkey compiler. Support for transparent proxies has to be added at each level
      because SpiderMonkey switches at run time from interpreter to baseline compiler and then
      to IonMonkey after a sufficient number of loop iterations. Specifically, the documentation
      says: “All JavaScript functions start out executing in the interpreter [. . . which] collects
      type information for use by the JITs. When a function gets somewhat hot, it gets compiled
      with JaegerMonkey. [. . . ] When it gets really hot, it is recompiled with IonMonkey.” If type
      information changes, execution falls back all the way to the interpreter.

      6.1    JavaScript Interpreter
      To cater for transparent proxies, the interpreter had to be changed in a few places.
      1. The internal classes Proxy and BaseProxyHandler had to be extended to support the
         new isTransparent trap.
      2. The comparison operators had to be modified to recognize transparent proxies and obtain
         their base target for comparison,
M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                          165


3. All internal data structures which are connected to the identity of objects (in particular,
   the map, weak map, and set abstractions of the upcoming JavaScript standard) had to
   be modified.

6.1.1     JavaScript’s Equality Comparison
JavaScript provides two types of comparison operators. The strict equality comparison (e.g.
===) returns false if the operands have different types. The equality comparison (e.g. ==)
applies type conversion if the operands have different types; then it essentially performs
a strict comparison on the converted values. When comparing two objects x and y, both
comparisons behave identically [8, Section 11.9.3]:
        1.f. “Return true if x and y refer to the same object.”
    Thus, this test for sameness of two objects is only one place where the algorithms for
equality comparison and strict equality comparison have to be changed. Our implementation
replaces the test (case 1.f. in equality comparison [11.9.3], case 7. in strict equality comparison
[11.9.6]) as follows.
1. Let lhs be the result of calling GetIdentityObject on x.
2. Let rhs be the result of calling GetIdentityObject on y.
3. Return true if lhs and rhs refer to the same object. Otherwise, return false.

6.1.2     Getting the Identity Object
When comparing two objects or when adding an object to a map, transparent proxies do
not use their own identity. To get the right identity for the object the operation first checks
the transparency of the proxy object and transitively obtains its target object until either
an opaque proxy or a native object is reached (cf. Listing 3 for the pseudocode). All object
comparisons refer to this internal method.
   The actual implementation is slightly more involved, in particular the implementation
that supports the isTransparent trap (its existence needs to be checked, it needs to be called,
and its results needs to be interpreted). Technical details may be checked in the source code
which is available in a github repository.

6.1.3     Maps, Sets, and other Data Structures
After modifying the comparison operators the internal data structures Map, Set, and
WeakMap, which depend on object equality, have to be adjusted to handle transparent
proxies. If target == proxy evaluates to true then map.has(target)==map.has(proxy) should
also evaluate to true.
    When adding a new key-value pair to any Map, WeakMap, or Set, the operation first
determines if the key is an object of type Proxy. If it is a Proxy, then the GetIdentityObject
internal method is used to determine the identity object; hashing takes place with respect to
this identity object, but the original key is stored in the collection along with its value. A
subsequent lookup of the identity object or any proxy with the same identity object returns
the same stored value. The implementation of the for...in loop or calling .keys() or .entries()
on a map returns the originally added object as key value.
    The example below demonstrates the behavior just described on an empty map object map.
We first create one transparent and one opaque proxy for the same target. The operation
map.set(target, A) creates a new map entry, whereas the second one map.set(proxy1, B);
updates this entry. The third operation map.set(proxy2, C); creates a new entry, again.



                                                                                                      ECOOP’15
166         Transparent Object Proxies for JavaScript


      115    var target = {};
      116    var proxy1 = new TransparentProxy(target, {});
      117    var proxy2 = new Proxy(target, {});
      118    map.set(target, A); // map = [target 7→ A]
      119    map.set(proxy1, B); // map = [target 7→ B]
      120    map.set(proxy2, C); // map = [target 7→ B, proxy2 7→ C]


            6.2    Object.equals
            Transparent proxies created with TransparentProxy are generally indistinguishable from their
            base target object and from another transparent proxy object of the same target. However,
            Object.equals can be employed to make them distinguishable for algorithms that implement
            advanced proxy manipulation. To this end, the constructor stores its token argument in a
            slot of each proxy.
                When Object.equals is called with arguments obj1, obj2 and an optional argument secret
            the following steps are taken:
                If secret is not present, then return the value of obj1 === obj2.
                If one of obj1 or obj2 is a transparent proxy where the token slot matches the secret, then
                return true if obj1 is the same object as obj2 (and false, otherwise).
                Otherwise return the value of the transparent comparison obj1 === obj2.

            6.3    JavaScript Baseline Compiler
            The SpiderMonkey Baseline Compiler is the first tier of the JIT compiler. It produces native
            code for JavaScript through stub method calls and optimized inline caches (ICs) for some
            operations. To adapt for changes to the equality (both strict and non-strict) comparison
            operation, the fallback stub code was modified to do a call to the VM and test for equality
            between the two objects in exactly the same manner as in the interpreter.
                For the isTransparent trap implementation we stop emitting the optimized ICs for object-
            object comparison, and instead use the fallback IC to do a VM call. This will invoke the
            isTransparent trap for the proxy and then compare with the identity object if it evaluates to
            true or it performs the standard object-object comparison.
                For the TransparentProxy implementation, we stop emitting the optimized IC for any
            object-object comparison that involves comparison of TransparentProxy object. We use the
            fallback stub to do a VM call and do a comparison with the identity object, if it involves a
            TransparentProxy. Any other kind of comparison operations are left unaffected and still take
            place through the optimized stubs.

            6.4    IonMonkey Compiler
            The IonMonkey optimizing compiler is the final tier of the SpiderMonkey JIT compiler.
            This has been left unmodified, but we give an outline for a future implementation so that
            generated native code by IonMonkey can support transparent proxy comparisons.
                For the isTransparent trap implementation, it will need to load the object into the register
            and test if the object’s class is a Proxy or not. If it is not a proxy, then normal fast path for
            object-object comparison code can be generated. Otherwise execution should stop, do a VM
            call to the isTransparent trap in the handler object of the proxy, and store the return value
            in a register. If the value is false then proceed with emitting the usual object comparison
            operation code, otherwise load the identity object of the proxy (i.e., the result of calling
M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                    167


GetIdentityObject on the proxy) and replace that value in the equality operation’s operand
register. Then we can proceed with emitting of code for a standard object comparison.
    For the TransparentProxy implementation, the code generation for the equality operation
would be simpler, as there is no handler trap to be called. We’d have to load the object
into the register and test if the object’s class is a TransparentProxy or not. If it is not a
TransparentProxy, then normal fast path for object-object comparison code can be generated.
Otherwise load the result of calling GetIdentityObject on the proxy and replace that value in
the equality operation’s operand register. Then we can proceed with emitting of code for a
standard object comparison.


6.5    Getting the Source Code
The implementation of both modified engines is available on the Web11 . The branch
isTransparent-trap12 contains the isTransparent handler trap version and branch global-
tproxy-object 13 contains the implementation of the TransparentProxy object. A README
file in each of the respective branches contains the build instructions.


 7     Evaluation

This section reports our experiences with applying our modified engines to JavaScript
benchmark programs to answer the following research questions:
RQ1 Does the introduction of transparent proxies affect the performance of non-proxy code?
RQ2 Does a contract implementation based on opaque proxies affect the meaning of realistic
   programs?


7.1    Performance Test
To answer RQ1, we evaluate the impact of our implementations of transparent proxies on
programs that do not make use of proxies at all. These programs may be affected by our
modifications to the equality comparison algorithms and to the set and map abstraction.
    To this end we used benchmark programs from the Google Octane 2.0 Benchmark Suite14 .
This suite comprises 17 programs that range from performance tests to real-world web
applications (Figure 3), that is, from an OS kernel simulation to a portable PDF viewer.
Each program focuses on a special purpose, for example, function and method calls, arithmetic
and bit operations, array manipulation, JavaScript parsing and compilation.
    Octane reports its results in terms of a score. The Octane FAQ15 explains the score as
follows: “In a nutshell: bigger is better. Octane measures the time a test takes to complete
and then assigns a score that is inversely proportional to the run time.” The constants in
this computation are chosen so that the current overall score (i.e., the geometric mean of
the individual scores) matches the overall score from earlier releases of Octane and new
benchmarks are integrated by choosing the constants so that the geometric mean remains
the same. The rationale is to maintain comparability.


11
   https://github.com/sankha93/js-tproxy/
12
   https://github.com/sankha93/js-tproxy/tree/isTransparent-trap
13
   https://github.com/sankha93/js-tproxy/tree/global-tproxy-object
14
   https://developers.google.com/octane/
15
   https://developers.google.com/octane/faq




                                                                                                ECOOP’15
168   Transparent Object Proxies for JavaScript


        Benchmark                   Origin                  Trap                Transparent
                                No-Ion No-JIT          No-Ion No-JIT           No-Ion No-JIT
        Richards                    505       64.8          502       63.3         509       64.3
        DeltaBlue                   453       82.5          466       80.2         466       79.6
        Crypto                      817        111          825        113         793        109
        RayTrace                    462        182          455        173         462        174
        EarleyBoyer                 909        275          938        271         913        270
        RegExp                      853        371          842        362         871        365
        Splay                       802        409          792        398         857        409
        SplayLatency               1172       1336         1222       1307        1231       1338
        NavierStokes                841        155          836        156         834        148
        pdf.js                     2759        704         2764        697        2793        691
        Mandreel                    691       82.5          711       82.4         688       78.5
        MandreelLatency            3803        526         3829        514        3829        503
        Gameboy Emulator           4275        556         4250        535        4382        540
        Code loading               9063       9439         9124       9318        9114       9502
        Box2DWeb                   1726        289         1750        278        1736        282
        zlib                      28981      29052        29097      29074       28909      29108
        TypeScript                 3708       1241         3715       1210        3666       1203
        Total Score                1594         456        1604        447        1610         445

          Figure 3 Scores for the Google Octane 2.0 Benchmark Suite (bigger is better). Column Origin
      gives the baseline scores for the unmodified engine. Column Trap shows the score values of the
      engine that implements the transparency trap and column Transparent contains the scores for
      running the engine containing the transparent proxies. The sub-column No-Ion (no IonMonkey)
      lists the scores with the baseline compiler enabled, but with IonMonkey disabled. Sub-column
      No-JIT (no just in-time compilation) shows the scores of the interpreter without any kind of just
      in-time compilation.


      7.1.1    The Testing Procedure
      All benchmarks were run on a machine with two AMD Opteron processors with 2.20 GHz and
      64 GB memory. All measurements reported in this paper were obtained with SpiderMonkey
      JavaScript-C24.2.0.
          To evaluate our implementation we run the benchmark program in different settings to
      separate the impact caused by the interpreter and the baseline compiler. Recall that no
      modifications were done to IonMonkey. Enabling IonMonkey in this state would lead to
      meaningless results from executing a mixture of proxy-aware code and proxy-oblivious code.
      Therefore, the IonMonkey compiler remains disabled.

      7.1.2    Results
      Figure 3 contains the score values of all benchmark programs in different configurations
      explained by the figure’s caption. The examples show the run-time impact of our modified
      engines vs. an unmodified engine. All scores were taken from a deterministic run, which
      requires a predefined number of iterations, and by using a warm-up run.
          Comparing the total scores of the interpreter (column No-JIT), the Trap version is
      1.97% slower than the unmodified engine and the Transparent version is 2.41% slower than
      the unmodified engine. However, when comparing the total scores of the baseline compiler
      (column No-Ion) we see that the Trap version is 0.62% faster than the unmodified engine
      and that the Transparent version is 1.00% faster than the unmodified engine.
M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                     169


    At this point we have to mention that both differences are smaller than the standard
deviation of the mean total scores produced by an unmodified engine. When measuring five
runs with the same configuration we found a standard deviation of 2.68 score points for the
interpreter and a standard deviation of 23.44 points for the baseline compiler.
    The numbers clearly show that both implementations do not have a statistically relevant
impact on the execution time of non-proxy code. This result is not surprising because the
overwhelming majority of equality comparisons have at least one non-object operand. As
our modification only applies when both operands are objects it is only exercised rarely (cf.
Section 6) and hence its performance impact is not measurable.


7.2     An Analysis of Object Comparisons
In this section, we answer RQ2, by considering how many object-object comparisons occur
during a normal program execution and how many of these may fail when objects were
wrapped by contracts implemented using opaque proxies. To this end, we count object
comparisons involving JavaScript Proxies and give a classification that covers different types
of object comparisons whose result might be influenced by the transparency of the involved
proxy objects.


7.2.1    The Testing Procedure
For this experiment, we instrumented the JavaScript engine with a monitor to count and
classify object-object comparisons. Our subject programs are again taken from the Google
Octane 2.0 Benchmark Suite. Our wrapper model is a simple contract system which applies
a dynamic type check (cf. Section 2.3) to the arguments of selected functions.
    To prepare for the experiment, a source-to-source translation generates for each function
that occurs in a program a new variant of the program, where exactly this function is
replaced by a function that wraps its arguments with a transparent proxy. The proxy’s
handler implements a membrane. It forwards the operation to the target and wraps its
return value in another transparent proxy. We rely on a weak map to avoid creating chains
of nested proxies.
    We applied this translation to the benchmark programs and executed each variant in our
new engine, counting and classifying each object comparison. As we used transparent proxies,
normal execution of the programs is not influenced. Because each function is wrapped
individually, we can accurately detect the effect of each single placement of a contract.


7.2.2    Results
First we introduce the types of object comparisons that must be distinguished. We only
consider comparisons between two objects where at least one of them is a proxy object,
because these are the only comparisons that may be affected if the proxy is opaque.

Type-I All comparisons between a proxy object and another object, which is either a native
   object or a proxy object from another membrane. Comparisons of this type always return
   false when using opaque proxies. With transparent proxies the result is either true or
   false, depending on the proxy’s target object.
Type-Ia The subset of Type-I, where the underlying target objects differ. Opaque and
   transparent proxies yield the same outcome, false, but for different reasons.



                                                                                                 ECOOP’15
170   Transparent Object Proxies for JavaScript


          Table 1 Number of comparisons involving object proxies. Column Total contains the total
      number of comparisons. Column Type-I lists the comparisons of Type-I, divided in the two
      categories Type-Ia and Type-Ib. Column Type-II shows the number of Type-II comparisons,
      divided in the categories Type-IIa and Type-IIb.

                Benchmark                       Type-I                  Type-II
                                  Total    Type-Ia Type-Ib        Type-IIa Type-IIb
                DeltaBlue        144126       29228        1411       33789        79698
                RayTrace        1075606           0           0      722703       352903
                EarleyBoyer       87211        8651        6303       53389        18868
                TypeScript       801436      599894      151297       20500        29745



      Type-Ib The subset of Type-I, where the underlying target objects are the same. A
         comparison of this type yields false when using opaque proxies, whereas transparent
         proxies yield true.
      Type-II All comparisons between two proxy objects from the same membrane. If using an
         identity preserving membrane, a target object is only wrapped once. In this case the
         result will be true if and only if they refer to the same target object, independent of the
         transparency of the proxies involved. Without an identity preserving membrane, the use
         of opaque proxies yields false, whereas the result with transparent proxies depends on
         the proxy’s target object.
      Type-IIa The subset of Type-II, where the target objects differ. Opaque and transparent
         proxies yield the same outcome, false, but for different reasons.
      Type-IIb The subset of Type-II, where both proxies refer to the same target object. A
         comparison of this type yields false when using opaque proxies without an identity
         preserving membrane, whereas transparent proxies or an identity preserving membrane
         yield true.

          In this setting we count the comparison between two proxy objects from different mem-
      branes in category Type-I, because different contracts implement different membranes and
      the mechanism that preserves the identity does not work when using different membranes.
          Clearly, the Type-Ib comparisons are the bad guys as they may flip. They are closely
      followed by Type-IIb comparisons, although they are avoidable if identity preserving
      membranes are used throughout.
          Table 1 summarizes the number of comparisons between native objects and proxy objects
      and among proxy objects. Comparisons between two primitive values (e.g., a boolean, a
      number, a string, null, or undefined), comparisons between a primitive value and an object
      (proxy or native object), and comparisons between two native objects are omitted from the
      results because the result of the operation is not influenced by the transparency of proxy
      objects.
          Benchmark programs not listed in this table do not contain comparisons with a proxy
      object: any function in the unlisted programs may be wrapped using any kind of membrane
      without affecting its meaning. However, they still contain comparisons between native objects
      and primitive values (usually the test ptr === null).
          The numbers in the table cover all comparison operators, namely equal (==), not equal
      (!=), strict equal (===), and strict not equal (!==). The meaning of “the result is true” is
      generalized to the sense that equal and strict equal will return true, not equal and strict not
      equal will return false.
M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                      171


    What we see in Table 1 is that there are three benchmarks with a non-negligible number
of bad Type-Ib comparisons, although the majority of object comparisons is not affected.
The numbers also indicate that there are many more Type-IIb comparisons, so that any
use of non-identity preserving membranes should be strongly discouraged.


7.3    Summary and Threats to Validity
The evaluation shows that the implementation of a dynamic contract system based on opaque
proxies, whose monitoring replaces function arguments by proxy objects, definitely influences
the program execution (which answers RQ2), which in turn leads to program errors.
   The reason for the comparatively small number of flipped comparisons is due to the
careful handling of object comparisons in JavaScript. Results from previous unpublished
experiments show that approximately 6% of all comparisons involve two objects. The vast
majority of comparisons either check an object against null or undefined, or compare primitive
values.


 8     Related Work

The JavaScript proxy API [20, 21] enables a developer to enhance the functionality of objects
easily. The implementation of proxies opens up the means to fully interpose all operations
on objects including functions calls on function objects.
     JavaScript proxies have been used for Disney’s JavaScript contract system, contracts.js
[5], to enforce access permission contracts [13], as well as for other dynamic effects systems,
meta-level extension, behavioral reflection, security, or concurrency control [16, 2, 4].
     Proxy-based implementations avoid the shortcomings of static implementations and
offline code transformations. In JavaScript, static approaches are often lacking because of
the dynamicity of the language. Proxies guarantee full interposition and handle the full
JavaScript language, including the with-statement, eval, and arbitrary dynamic code loading
techniques.
     The ideal contract system should not interfere with the normal execution of code as long
as the application code does not violate any contract. The application should run as if no
contracts were present [7].
     Object equality becomes an issue for non-interference when contracts are implemented
by some kind of wrapper. The problem arises if an equality test between wrapper and
target or between different wrappers for the same target returns false instead of true. This
issue is known from other work involving wrappers for implementing object extensions and
multimethods [22, 3]
     The PLT group examines various designs for low-level mechanisms for implementing
contracts and related abstractions [19]. They propose two kinds of proxies, chaperones and
impersonators, that differ, for example, in the degree of freedom for modifying the underlying
object. They experience similar problems with noninterference as we report in Section 3.3
when using the eq? operator which is similar to JavaScript’s strict equality operator ===
and roughly implements pointer equality on objects. Racket’s chaperones and impersonators
are not transparent with respect to this operator. However, the preferred equality operation
in Racket, equal?, implements structural equality which is indifferent to proxy transparency.
In contrast, JavaScript provides no built-in operation to test for structural equality, so that
developers need to build on pointer equality or roll their own structural equality.



                                                                                                  ECOOP’15
172   Transparent Object Proxies for JavaScript


       9     Conclusion
      Neither the transparent nor the opaque implementation of proxies is appropriate for all
      use cases. We discuss several amendments and propose two flexible solutions that enable
      applications requiring transparency as well as opacity. Both solutions are implemented as
      extensions of the SpiderMonkey JavaScript VM. This approach ensures full and transparent
      operation with all JavaScript programs. Hence, we can evaluate the solution on real-world
      JavaScript programs.
         A significant number of object comparisons would fail when mixing opaque proxies and
      their target objects. This situation can arise when gradually adding contracts to a program
      during debugging. Identity preserving membranes decrease this number, but they are not
      able to guarantee full noninterference.
         We also measured the run-time impact of an implementation supporting transparent
      proxies on the execution time of a realistic program mix. The results show that the
      modification to equality required to support transparent proxies has no statistically significant
      impact on the execution time.


           References
       1   Michael D. Adams and R. Kent Dybvig. Efficient nondestructive equality checking for trees
           and graphs. In Peter Thiemann, editor, Proceedings International Conference on Functional
           Programming 2008, pages 179–188, Victoria, BC, Canada, September 2008. ACM Press,
           New York.
       2   Thomas H. Austin, Tim Disney, and Cormac Flanagan. Virtual values for language ex-
           tension. In Cristina Videira Lopes and Kathleen Fisher, editors, OOPSLA, pages 921–938,
           Portland, OR, USA, 2011. ACM.
       3   Gerald Baumgartner, Martin Jansche, and Konstantin Läufer. Half & half: Multiple dis-
           patch and retroactive abstraction for Java. Technical Report OSU-CISRC-5/01-TR08, Re-
           vised 3/02, Ohio State University, March 2002.
       4   Gilad Bracha and David Ungar. Mirrors: Design principles for meta-level facilities of object-
           oriented programming languages. In John M. Vlissides and Douglas C. Schmidt, editors,
           OOPSLA, pages 331–344. ACM, 2004.
       5   Tim Disney. contracts.js. https://github.com/disnet/contracts.js, April 2013.
       6   Tim Disney, Nathan Faubion, David Herman, and Cormac Flanagan. Sweeten your
           JavaScript: Hygienic macros for ES5. In Andrew P. Black and Laurence Tratt, editors,
           DLS, pages 35–44, Portland, OR, USA, October 2014. ACM.
       7   Tim Disney, Cormac Flanagan, and Jay McCarthy. Temporal higher-order contracts. In
           Olivier Danvy, editor, Proceedings International Conference on Functional Programming
           2011, pages 176–188, Tokyo, Japan, September 2011. ACM Press, New York.
       8   ECMAScript Language Specification, December 2009. ECMA International, ECMA-262,
           5th edition.
       9   ECMAScript Language Specification.              http://wiki.ecmascript.org/lib/exe/
           fetch.php?id=harmony:specification_drafts&cache=cache&media=harmony:
           ecma-262_6th_edition_final_draft_-04-14-15.pdf, April 2015.               ECMA Interna-
           tional, ECMA-262, 6th edition (draft).
      10   Robert Bruce Findler and Matthias Felleisen. Contracts for higher-order functions. In
           Simon Peyton-Jones, editor, Proceedings International Conference on Functional Program-
           ming 2002, pages 48–59, Pittsburgh, PA, USA, October 2002. ACM Press, New York.
      11   Matthew Flatt, Robert Bruce Findler, and PLT. The Racket Guide, v.6.0 edition, March
           2014. http://docs.racket-lang.org/guide/index.html.
M. Keil, S. N. Guria, A. Schlegel, M. Geffken, and P. Thiemann                                      173


12   Matthias Keil, Sankha Narayan Guria, Andreas Schlegel, Manuel Geffken, and Peter Thie-
     mann. Transparent object proxies for JavaScript. Technical report, Institute for Computer
     Science, University of Freiburg, 2015.
13   Matthias Keil and Peter Thiemann. Efficient dynamic access analysis using JavaScript
     proxies. In Proceedings of the 9th Symposium on Dynamic Languages, DLS’13, pages 49–
     60, New York, NY, USA, 2013. ACM.
14   Matthias Keil and Peter Thiemann. TreatJS: Higher-order contracts for JavaScript. http:
     //proglang.informatik.uni-freiburg.de/treatjs/, 2014.
15   Bertrand Meyer. Object-Oriented Software Construction. Prentice-Hall, 1988.
16   Mark S. Miller, Tom Van Cutsem, and Bill Tulloh. Distributed electronic rights in
     JavaScript. In Matthias Felleisen and Philippa Gardner, editors, ESOP, volume 7792 of
     Lecture Notes in Computer Science, pages 1–20, Rome, Italy, March 2013. Springer.
17   Mark Samuel Miller. Robust Composition: Towards a Unified Approach to Access Control
     and Concurrency Control. PhD thesis, Johns Hopkins University, Baltimore, MD, USA,
     2006. AAI3245526.
18   Michael Sperber, R. Kent Dybvig, Matthew Flatt, and Anton van Straaten, editors. Re-
     vised[6] Report on the Algorithmic Language Scheme. Cambridge University Press, 2010.
19   T. Stephen Strickland, Sam Tobin-Hochstadt, Robert Bruce Findler, and Matthew Flatt.
     Chaperones and impersonators: Run-time support for reasonable interposition. In Gary T.
     Leavens and Matthew B. Dwyer, editors, OOPSLA, pages 943–962. ACM, 2012.
20   Tom Van Cutsem and Mark S. Miller. Proxies: Design principles for robust object-oriented
     intercession APIs. In William D. Clinger, editor, DLS, pages 59–72. ACM, 2010.
21   Tom Van Cutsem and Mark S. Miller. Trustworthy proxies – virtualizing objects with invari-
     ants. In Giuseppe Castagna, editor, ECOOP, volume 7920 of Lecture Notes in Computer
     Science, pages 154–178, Montpellier, France, July 2013. Springer.
22   Alessandro Warth, Milan Stanojevic, and Todd Millstein. Statically scoped object adap-
     tation with expanders. In Proceedings of the 21th ACM SIGPLAN Conference on Object
     Oriented Programming, Systems, Languages, and Applications, pages 37–56, Portland, OR,
     USA, 2006. ACM Press, New York.




                                                                                                  ECOOP’15