Authors Peter Thiemann, Manuel Geffken, Andreas Schlegel, Sankha Narayan Guria, Matthias Keil,
License CC-BY-3.0
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
* Well Docume v
en
EC O C P *
A EC E
*
st
* onsi
licensed under Creative Commons License CC-BY
O
*
se
eu
29th European Conference on Object-Oriented Programming (ECOOP’15).
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