Catch-all proposal based on proxies

# Tom Van Cutsem (16 years ago)

Dear all,

Over the past few weeks, MarkM and myself have been working on a proposal for catch-alls for ES-Harmony based on proxies. I just uploaded a strawman proposal to the wiki:

strawman:proxies

Any feedback is more than welcome. Note that the proposal is quite lengthy, which is mostly due to the fact that it also includes a detailed semantic specification of proxies (in ES5-spec-style) in addition to just its API description. If you're not interested in the specification details, you can skip the section named "Semantics".

# Mike Samuel (16 years ago)

Under "This avoids questions like:", does the proposal also avoid 5. Can property trapping be defined on host objects

"The Type of a Proxy" Why does the type change upon fixing? Is it an error to pass a value v s.t. (typeof v !== 'function') to Proxy.createFunction? Specifically RegExps?

"Equality:" Would defining a Proxy.proxies(proxy, obj) that returns true iff obj was the argument to Proxy.create{,Function} that created proxy allow a useful level of equality checking? That would allow limited checking by code that already possesses a handle to the underlying object without potentially leaking an underlying object to a bad proxy.

"[[DefaultValue]] (hint)" "[[Class]]" The rule for "[[Class]]" means that it's possible to create proxy an array in every way but that the isArray test used by common libraries cannot be spoofed: '[object Array]' !== Object.prototype.toString.call(myArrayProxy) What is the proper behavior here?

2009/12/7 Tom Van Cutsem <tomvc at google.com>:

# Tom Van Cutsem (16 years ago)

Somebody asked me what the main differences are w.r.t. the existing catch-all proposal:

The main similarity: like the existing catch-all proposal, our proxy proposal separates the traps from the object being trapped (i.e. both propose the use of a separate "handler" object).

The main difference: the proxy proposal only enables catch-alls for special, empty "proxy" objects. In the existing catch-all proposal, there is still the notion of "activating" or "deactivating" catch-alls on existing objects, which leads to issues such as whether or not the catch-all should trigger on existing properties. Also, from a security POV this has the bad property that any object that has a reference to an object can all of a sudden decide to take control over the behavior of that object. Our proxy proposal does not allow this.

The other main difference is that the proxy proposal details how proxies interact with the new ES5 concepts of "frozen", "sealed" and "non-extensible" objects, and with a host of other language features (e.g. instanceof, ===, typeof, the Object.* introspection methods, ...)

# Tom Van Cutsem (16 years ago)

On Mon, Dec 7, 2009 at 4:44 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

Under "This avoids questions like:", does the proposal also avoid 5. Can property trapping be defined on host objects

Yes, it does avoid this issue. One cannot trap properties of host objects. But one can define a proxy that redirects property access to a host object.

"The Type of a Proxy"

Why does the type change upon fixing? Is it an error to pass a value v s.t. (typeof v !== 'function') to Proxy.createFunction? Specifically RegExps?

The "applyTrap" and "constructTrap" arguments to Proxy.createFunction should be callable, so anything with an internal [[Call]] method will do.

"Equality:" Would defining a Proxy.proxies(proxy, obj) that returns true iff obj was the argument to Proxy.create{,Function} that created proxy allow a useful level of equality checking? That would allow limited checking by code that already possesses a handle to the underlying object without potentially leaking an underlying object to a bad proxy.

For this proposal it does not make sense to ask whether a proxy proxies a particular object 'obj'. However, it does make sense to ask something similar like "Proxy.isHandledBy(proxy, handler)", which would return true iff 'handler' equals the proxy's internal handler attribute. Such a method cannot be defined by user code, since given a proxy it's impossible to get a reference to its handler. If there's a good use case for providing such a function, it could be added.

"[[DefaultValue]] (hint)" "[[Class]]" The rule for "[[Class]]" means that it's possible to create proxy an array in every way but that the isArray test used by common libraries cannot be spoofed: '[object Array]' !== Object.prototype.toString.call(myArrayProxy) What is the proper behavior here?

This is a good point. Our current proposal does not allow proxies to influence the value of their [[Class]] attribute, so the above "isArray" test would fail for a proxy trying to emulate an array.

# Mike Samuel (16 years ago)

2009/12/7 Tom Van Cutsem <tomvc at google.com>:

On Mon, Dec 7, 2009 at 4:44 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

Under "This avoids questions like:", does the proposal also avoid   5. Can property trapping be defined on host objects

Yes, it does avoid this issue. One cannot trap properties of host objects. But one can define a proxy that redirects property access to a host object.

How much of a strength is the host-object agnosticism of this approach? How much work it would be on people's favorite interpreters to get host objects working with an imaginary alternate proposal that added catchAlls o an object after creation, i.e. Object.defineCatchAll(myHostObject, ...)

"The Type of a Proxy" Why does the type change upon fixing? Is it an error to pass a value v s.t. (typeof v !== 'function') to Proxy.createFunction?   Specifically RegExps?

The "applyTrap" and "constructTrap" arguments to Proxy.createFunction should be callable, so anything with an internal [[Call]] method will do.

Ah ok, I missed the applyTrap on first read and got confused by the Invoke vs Get+Call stuff into thinking that apply was handled as a method of undefined.

"Equality:" Would defining a Proxy.proxies(proxy, obj) that returns true iff obj was the argument to Proxy.create{,Function} that created proxy allow a useful level of equality checking?  That would allow limited checking by code that already possesses a handle to the underlying object without potentially leaking an underlying object to a bad proxy.

For this proposal it does not make sense to ask whether a proxy proxies a particular object 'obj'. However, it does make sense to ask something similar like "Proxy.isHandledBy(proxy, handler)", which would return true iff 'handler' equals the proxy's internal handler attribute. Such a method cannot be defined by user code, since given a proxy it's impossible to get a reference to its handler. If there's a good use case for providing such a function, it could be added.

I can't think of any compelling use cases of the top of my head.

"[[DefaultValue]] (hint)" "[[Class]]" The rule for "[[Class]]" means that it's possible to create proxy an array in every way but that the isArray test used by common libraries cannot be spoofed:     '[object Array]' !== Object.prototype.toString.call(myArrayProxy) What is the proper behavior here?

This is a good point. Our current proposal does not allow proxies to influence the value of their [[Class]] attribute, so the above "isArray" test would fail for a proxy trying to emulate an array.

I don't think this is a defect. But I think one side-effect of proxying and/or iterators/generators would be a sudden profusion of array like objects so I started a separate thread to discuss whether it's worth weighing in on what is array-like and what is not.

# Mike Wilson (16 years ago)

I certainly see the logic in your proposal, but I am uncertain whether the obj-proxy split is what the world wants, and what kind of up-take it would get among script authors? My impression is that the success of existing catch-all mechanisms is partly due to their perceived simplicity. The proposed design adds extra verbocity and handling for authors, needing to coordinate two different objects (of different type) instead of one that has been augmented, and to be careful about who has what reference.

A somewhat related example of the "world's choice" is how third-party byte-code patching techniques have become the default choice in the Java community, instead of the standard Java proxies (java.lang.reflect.Proxy), which is a similar construct to the one proposed.

Or are you seeing openings for syntactic sugar up the road, with classes etc?

Best Mike Wilson

Tom Van Cutsem wrote:

Dear all,

Over the past few weeks, MarkM and myself have been working on a proposal for catch-alls for ES-Harmony based on proxies. I just uploaded a strawman proposal to the wiki:

strawman:proxies

Any feedback is more than welcome. Note that the proposal is quite lengthy, which is mostly due to the fact that it also includes a detailed semantic specification of proxies (in ES5-spec-style) in addition to just its API description. If you're not interested in the specification details, you can skip the section named "Semantics".

# Tom Van Cutsem (16 years ago)

On Tue, Dec 8, 2009 at 10:35 AM, Mike Wilson <mikewse at hotmail.com> wrote:

I certainly see the logic in your proposal, but I am uncertain whether the obj-proxy split is what the world wants, and what kind of up-take it would get among script authors? My impression is that the success of existing catch-all mechanisms is partly due to their perceived simplicity. The proposed design adds extra verbocity and handling for authors, needing to coordinate two different objects (of different type) instead of one that has been augmented, and to be careful about who has what reference.

It is true that the proxy API is slightly more complex than, for instance, the noSuchMethod trap in Spidermonkey. I cannot predict the up-take of such an API, but it is worth mentioning that a simple noSuchMethod-like API can be built on top of the proposal, in a library, without losing the benefits of stratification. One possibility is to define a method named "Object.createHandled", similar to ES5's "Object.create" method, but which allows an additional noSuchMethod trap to be specified:

Object.createHandled = function(super, objDesc, noSuchMethod) { var handler = { get: function(rcvr, p) { return function() { var args = [].slice.call(arguments, 0); return noSuchMethod.call(this, p, args); }; } }; var p = Proxy.create(handler, super); return Object.create(p, objDesc); };

Given a convenience function Object.getOwnProperties(obj), one could write:

Object.createHandled(Parent, Object.getOwnProperties({ ... }), function (id, args) { ... });

This comes close to the noSuchMethod-style of trapping missing methods, without giving up on stratification (the 'noSuchMethod' trap itself is still clearly separated from the object itself). The fact that an object literal is passed as an argument to 'Object.getOwnProperties' is part of the idiom. It ensures that the rest of the application only refers to the 'trapped' object, and there is no need for the programmer to explicitly distinguish the 'trapped' object from the 'non-trapped' object.

A somewhat related example of the "world's choice" is how third-party byte-code patching techniques have become the default choice in the Java community, instead of the standard Java proxies (java.lang.reflect.Proxy), which is a similar construct to the one proposed.

I cannot speak authoritatively about Java proxies, but one cause of this evolution might be that Java Proxies only work for interface types. Hence, anyone that wants to build a general wrapping/trapping mechanism that works on any Java class is forced to abandon the Proxy abstraction and look for other means to accomplish that goal.

Or are you seeing openings for syntactic sugar up the road, with classes

etc?

Syntactic sugar is always a possibility, but with the introduction of ES5's Object.* meta-methods, I think many abstractions can be built on top of proxies as plain library abstractions.

, Tom

# Mike Wilson (16 years ago)

Tom Van Cutsem wrote:

On Tue, Dec 8, 2009 at 10:35 AM, Mike Wilson <mikewse at hotmail.com> wrote:

A somewhat related example of the "world's choice" is how third-party byte-code patching techniques have become the default choice in the Java community, instead of the standard Java proxies (java.lang.reflect.Proxy), which is a similar construct to the one proposed.

I cannot speak authoritatively about Java proxies, but one cause of this evolution might be that Java Proxies only work for interface types.

That certainly accounts for part of the loss in up-take, but not all I'd say.

Or are you seeing openings for syntactic sugar up the road, with classes etc?

Syntactic sugar is always a possibility, but with the introduction of ES5's Object.* meta-methods, I think many abstractions can be built on top of proxies as plain library abstractions.

I'm guessing that a common use-case is trapping calls to all objects delegating to a certain prototype, or all instances of a certain class. Do you have any thoughts on how this may be optimized? When having 10000 objects to trap, it seems more efficient to put the catch-all on a single augmentation point instead of creating and configuring 10000 proxies?

Best Mike

# Mark Miller (16 years ago)

On Tue, Dec 8, 2009 at 2:12 PM, Mike Wilson <mikewse at hotmail.com> wrote:

I'm guessing that a common use-case is trapping calls to all objects delegating to a certain prototype, or all instances of a certain class.

It's not the use case that's motivating us to work on catchalls, but it may be an important use case. Let's assume it is.

Do you have any thoughts on how this may be optimized? When having 10000 objects to trap, it seems more efficient to put the catch-all on a single augmentation point instead of creating and configuring 10000 proxies?

Have these 10000 objects inherit from a single proxy. The trapping get/put calls on the handler provide the "receiver" argument, which is the object on which the failed property access was attempted.

# Mike Wilson (16 years ago)

Mark Miller wrote:

On Tue, Dec 8, 2009 at 2:12 PM, Mike Wilson wrote:

I'm guessing that a common use-case is trapping calls to all objects delegating to a certain prototype, or all instances of a certain class.

It's not the use case that's motivating us to work on catchalls, but it may be an important use case. Let's assume it is.

Looking at the Use Cases section in your proposal it seems most of your suggested use cases would be interesting to apply on an object prototype/class level when targeting many objects, to avoid having to recreate the catch-all for every object and also possibly adding dynamically generated methods in a common place (like in Ruby/ActiveRecord). Hearing that you are not certain that this is indeed an important use case, or maybe use case property, I wonder if I am missing something? I have full respect for that you have thought this through, so maybe there is something in the language or in other Harmony plans that make this less interesting?

Do you have any thoughts on how this may be optimized? When having 10000 objects to trap, it seems more efficient to put the catch-all on a single augmentation point instead of creating and configuring 10000 proxies?

Have these 10000 objects inherit from a single proxy. The trapping get/put calls on the handler provide the "receiver" argument, which is the object on which the failed property access was attempted.

This sounds interesting. It would be great if you could show a code example? A good "demo" case could be mimicing the single point catch-all and dynamic .find_by_* method generation for all User objects in the ActiveRecord examples: api.rubyonrails.org/classes/ActiveRecord/Base.html (section "Dynamic attribute-based finders")

Best Mike

# Brendan Eich (16 years ago)

On Dec 7, 2009, at 4:11 PM, Tom Van Cutsem wrote:

Dear all,

Over the past few weeks, MarkM and myself have been working on a
proposal for catch-alls for ES-Harmony based on proxies. I just
uploaded a strawman proposal to the wiki:

strawman:proxies

Hi Tom, great to see this proposal. I took the liberty of making a few
small edits; hope they're ok. I like the stratification and the ab- initio nature of the design -- the last seems to me to be a crucial
improvement over past proposals, which may help overcome the "climbing
the meta ladder" objection.

Some initial comments, pruned to avoid restating others' comments:

  1. This proposal obligates the catch-all implementor to delegate to
    any prototype object in has and get, to include unshadowed prototype
    properties in enumerate, to shadow if p in receiver.[[Prototype]] in
    put, and to do nothing for delete proxy.p if !proxy.hasOwnProperty(p).

In general, handler writers have to implement standard prototype-based
delegation if it is desired. This is probably the right thing, but I
wonder if you considered the alternative where prototype delegation is
handled "by the spec" or "by the runtime" and the proxy is considered
"flat"?

  1. The fix handler returning undefined instead of throwing explicitly
    to reject a freeze, etc., attempt is a bit implicit. Falling off the
    end of the function due to a forgetten or bungled return will do this.
    Ok, let's say the programmer will test and fix the bug.

But more significant: could there be a useful default denoted by
returning undefined or falling off the end of the fix function? An
alternative interpretation would be an empty frozen object. This has
symmetry with undefined passed (or no actual argument supplied) to
Object.create. It's a minor comment for sure.

  1. Mozilla's wrappers (proxies, membranes), which we pioneered for
    security purposes (e.g. for DOM inspectors where privileged JS is
    interacting with web content objects) and which have been copied in
    other browsers (at least WebKit), implement === by unwrapping, so two
    wrappers for the same object are === with that object, and with each
    other.

The proxies proposal does not have an unwrapped object, although
super? is similar. Later in the proposal, you write "meta-level code
will ‘see’ the proxy rather than the object it represents." This
sounds more like wrappers as we use them -- there is always a wrapped
object and its proxy or wrapper.

The alternative of not trapping === is a leaky abstraction that
inevitably breaks some programmers' expectations. Our early wrappers
did not hook ===, but eventually we settled on the unwrap-before-===
behavior based on testing.

This is a use-case I wanted to bring to your attention (Mike Samuel
raised it in his reply by suggesting a Proxy.proxies predicate; his
[[Class]] question also gets to the broader issue of transparency vs.
leaky proxy abstractions). Our wrapper experience suggests allowing
=== to be hooked in a constrained way, for certain kinds of proxies.
It could be that this use-case can't be served by a standardized,
general proxy/catch-all proposal, and must be done under the hood and
outside of the ES spec.

  1. The [[Get]] versus [[Invoke]] rationale: indeed performance is a
    concern, but existing engines also specialize callee-computation
    distinctly from get-value, in order to optimize away Reference types.
    The ES specs so far do not, instead using the internal Reference type
    to delay GetValue so as to bind |this| to the Reference base when
    computing a callee and its receiver as part of evaluating a call
    expression.

I think it is an open question whether a future spec, especially one
using a definitional interpreter, will stick to References. If we end
up making the distinction that all practical implementations already
make, between get-as-part-of-callee-computation and all other get- value "gets", then I don't think this rationale is so strong.

In general over-coupling to ES5 may not help either a new Harmony-era
proposal to "get in", or to be as complete or expressive as it should
be. So a rationale based on choices or limitations of ES1-5 seems weak
to me.

Thanks again for this proposal,

# Tom Van Cutsem (16 years ago)

On Wed, Dec 9, 2009 at 1:02 AM, Mike Wilson <mikewse at hotmail.com> wrote:

Looking at the Use Cases section in your proposal it seems most of your suggested use cases would be interesting to apply on an object prototype/class level when targeting many objects, to avoid having to recreate the catch-all for every object and also possibly adding dynamically generated methods in a common place (like in Ruby/ActiveRecord). Hearing that you are not certain that this is indeed an important use case, or maybe use case property, I wonder if I am missing something? I have full respect for that you have thought this through, so maybe there is something in the language or in other Harmony plans that make this less interesting?

Ok, we should probably have made clear the distinction between our motivating use cases and other possible use cases. Our motivating use case for the proxy proposal is near-complete object virtualization, that is: the ability to emulate as much aspects of an object's behavior as practically possible (I'm not claiming our current proposal achieves full virtualization, but it comes close, given the legacy compatibility and security constraints).

In this light, the "doesNotUnderstand" trap provides a strict subset of what you can do with full object virtualization. So, while you can definitely use the proposed proxies to build a Ruby/ActiveRecord-style framework in Javascript (see below), this use case was not our main motivation. A motivating example would be using proxies to virtualize an existing Javascript API (e.g. to enforce access control to the DOM, or to provide a legacy interface adaptor to a new API).

This sounds interesting. It would be great if you could show a code example? A good "demo" case could be mimicing the single point catch-all and dynamic .find_by_* method generation for all User objects in the ActiveRecord examples: api.rubyonrails.org/classes/ActiveRecord/Base.html (section "Dynamic attribute-based finders")

Here's the essence of one possible solution:

var attributeBasedFinder = Proxy.create({ get: function(rcvr, p) { var res = p.match(/^find_by_(.*)$/); if (res) { return generate_find_by_query(res[1], rcvr.name); } } });

function makeTable(tname) { return Object.create(attributeBasedFinder, { name: { value: tname } }); };

# Mike Samuel (16 years ago)

2009/12/9 Tom Van Cutsem <tomvc at google.com>:

On Wed, Dec 9, 2009 at 1:02 AM, Mike Wilson <mikewse at hotmail.com> wrote:

Looking at the Use Cases section in your proposal it seems most of your suggested use cases would be interesting to apply on an object prototype/class level when targeting many objects, to avoid having to recreate the catch-all for every object and also possibly adding dynamically generated methods in a common place (like in Ruby/ActiveRecord). Hearing that you are not certain that this is indeed an important use case, or maybe use case property, I wonder if I am missing something? I have full respect for that you have thought this through, so maybe there is something in the language or in other Harmony plans that make this less interesting?

Ok, we should probably have made clear the distinction between our motivating use cases and other possible use cases. Our motivating use case for the proxy proposal is near-complete object virtualization, that is: the ability to emulate as much aspects of an object's behavior as practically possible (I'm not claiming our current proposal achieves full virtualization, but it comes close, given the legacy compatibility and security constraints).

In this light, the "doesNotUnderstand" trap provides a strict subset of what you can do with full object virtualization. So, while you can definitely use the proposed proxies to build a Ruby/ActiveRecord-style framework in Javascript (see below), this use case was not our main motivation. A motivating example would be using proxies to virtualize an existing Javascript API (e.g. to enforce access control to the DOM, or to provide a legacy interface adaptor to a new API).

I.e. this allows implementing Mozilla style wrappers in user-space. With the exception that mozilla's wrappers support ===, but by combining this with a weak reference table mapping wrapped objects to wrappers, you could preserve EQ as far as a piece of code that never gets a handle to a wrapped DOM node is concerned.

# Mark S. Miller (16 years ago)

On Wed, Dec 9, 2009 at 2:06 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

I.e. this allows implementing Mozilla style wrappers in user-space. With the exception that mozilla's wrappers support ===, but by combining this with a weak reference table mapping wrapped objects to wrappers, you could preserve EQ as far as a piece of code that never gets a handle to a wrapped DOM node is concerned.

As shown by Tom Van Cutsem and I at strawman:proxies#an_identity-preserving_membrane.

# Mark S. Miller (16 years ago)

On Wed, Dec 9, 2009 at 6:21 PM, Mark S. Miller <erights at google.com> wrote:

On Wed, Dec 9, 2009 at 2:06 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

I.e. this allows implementing Mozilla style wrappers in user-space. With the exception that mozilla's wrappers support ===, but by combining this with a weak reference table mapping wrapped objects to wrappers, you could preserve EQ as far as a piece of code that never gets a handle to a wrapped DOM node is concerned.

As shown by Tom Van Cutsem and I at strawman:proxies#an_identity-preserving_membrane.

I have now split that example in a simpler expository example membrane at strawman:proxies#a_simple_membrane

that does not preserve identity, but that should be read first, and an elaboration of our identity preserving membrane, still at strawman:proxies#an_identity-preserving_membrane

with a bug fixed and with both wrapping and unwrapping logic that makes it into a practical membrane.

# Mark S. Miller (16 years ago)

On Wed, Dec 9, 2009 at 11:02 AM, Brendan Eich <brendan at mozilla.com> wrote:

On Dec 7, 2009, at 4:11 PM, Tom Van Cutsem wrote:

Dear all,

Over the past few weeks, MarkM and myself have been working on a proposal for catch-alls for ES-Harmony based on proxies. I just uploaded a strawman proposal to the wiki:

strawman:proxies

Hi Tom, great to see this proposal. I took the liberty of making a few small edits; hope they're ok. I like the stratification and the ab-initio nature of the design -- the last seems to me to be a crucial improvement over past proposals, which may help overcome the "climbing the meta ladder" objection.

Some initial comments, pruned to avoid restating others' comments:

  1. This proposal obligates the catch-all implementor to delegate to any prototype object in has and get, to include unshadowed prototype properties in enumerate, to shadow if p in receiver.[[Prototype]] in put, and to do nothing for delete proxy.p if !proxy.hasOwnProperty(p).

In general, handler writers have to implement standard prototype-based delegation if it is desired. This is probably the right thing, but I wonder if you considered the alternative where prototype delegation is handled "by the spec" or "by the runtime" and the proxy is considered "flat"?

We did think about it, but it seemed needlessly less flexible. If such flat-and-delegate handling is desired, an abstraction can be built on top of ours that emulates it as a convenience. The reverse emulation seems difficult at best.

  1. The fix handler returning undefined instead of throwing explicitly to reject a freeze, etc., attempt is a bit implicit. Falling off the end of the function due to a forgetten or bungled return will do this. Ok, let's say the programmer will test and fix the bug.

But more significant: could there be a useful default denoted by returning undefined or falling off the end of the fix function? An alternative interpretation would be an empty frozen object. This has symmetry with undefined passed (or no actual argument supplied) to Object.create. It's a minor comment for sure.

Since the undefined may be the result of a bug as you say, it seems worse

for the bug to silently result in fixing the proxy into an empty frozen object. We think the current "noisy" behavior better supports defensive programming.

  1. Mozilla's wrappers (proxies, membranes), which we pioneered for security purposes (e.g. for DOM inspectors where privileged JS is interacting with web content objects) and which have been copied in other browsers (at least WebKit), implement === by unwrapping, so two wrappers for the same object are === with that object, and with each other.

In answer, <

strawman:proxies#an_identity-preserving_membrane>

preserves === correspondence on each side of a membrane. Now that we have a concrete catchall proposal adequate to build membranes, we'd like to restart our discussions with Mozilla (JetPack, etc) about whether you could rebuild some of your C++ membranes in JS code using these primitives. We should follow up offlist.

The proxies proposal does not have an unwrapped object, although super? is similar. Later in the proposal, you write "meta-level code will ‘see’ the proxy rather than the object it represents." This sounds more like wrappers as we use them -- there is always a wrapped object and its proxy or wrapper.

The alternative of not trapping === is a leaky abstraction that inevitably breaks some programmers' expectations. Our early wrappers did not hook ===, but eventually we settled on the unwrap-before-=== behavior based on testing.

This is a use-case I wanted to bring to your attention (Mike Samuel raised it in his reply by suggesting a Proxy.proxies predicate; his [[Class]] question also gets to the broader issue of transparency vs. leaky proxy abstractions). Our wrapper experience suggests allowing === to be hooked in a constrained way, for certain kinds of proxies. It could be that this use-case can't be served by a standardized, general proxy/catch-all proposal, and must be done under the hood and outside of the ES spec.

To avoid some "climbing meta ladder" issues, we purposely distinguish between what we consider base-level operations, such as x.foo, and meta-level operations, such as Object.getOwnProperty(x, 'foo'). We attempt to be as fully transparent (leak free) as reasonably possible at virtualizing base level operations. We attempt to be fully non-transparent (leak like a firehose) to meta-level operations. Some of our classification may seem weird: Object.prototype.toString() is meta-level. It can be used to reveal that an object is a trapping proxy. Object.getOwnPropertyNames() is meta-level. Object.keys() is base level.

The properties of === that we feel need to be preserved:

  1. "x === y" does not cause any user code to run.
  2. "x === y" neither gives x access to y nor vice versa.
  3. "typeof x !== 'number' && x === y" mean that x is operationally identical to y in all observable ways. Substituting the value for x with the value of y cannot change the meaning of a computation.
  4. "x === y" implies that the Grant Matcher < erights.org/elib/equality/grant-matcher> may safely send the money

to either x or y.

A wrapper is not identical to the object it is wrapping, or there wouldn't be any point in wrapping it. Thus, they can't be ===. Two independent wrappers on the same object may behave differently, depending on their definers. Thus they can't be ===. Except for two proxies with identical parts, such as two object proxies with identical handlers and supers. However, as shown by our example membrane, one can just use an Ephemeron table to avoid creating semantically identical duplicate proxies, preserving === without magic.

  1. The [[Get]] versus [[Invoke]] rationale: indeed performance is a concern, but existing engines also specialize callee-computation distinctly from get-value, in order to optimize away Reference types. The ES specs so far do not, instead using the internal Reference type to delay GetValue so as to bind |this| to the Reference base when computing a callee and its receiver as part of evaluating a call expression.

I think it is an open question whether a future spec, especially one using a definitional interpreter, will stick to References. If we end up making the distinction that all practical implementations already make, between get-as-part-of-callee-computation and all other get-value "gets", then I don't think this rationale is so strong.

In general over-coupling to ES5 may not help either a new Harmony-era proposal to "get in", or to be as complete or expressive as it should be. So a rationale based on choices or limitations of ES1-5 seems weak to me.

+100. I would love to see the concept of References disappear, and to see the ([[Get]], [[Call]]) pairs in the spec that really mean "call this as a method" be rewritten as [[Invoke]]s. In that case, I would enthusiastically agree that this catchall proposal should be upgraded with an invoke() trap. Note how this would make our membrane code simpler and more efficient. Rather than a get() trap at the choke point that creates and returns a function, we'd simply have an invoke() trap whose body is that function's body.

I'd like to understand better how we could get rid of References.

# Mike Samuel (16 years ago)

On the climbing the meta, I'd like to understand how this might interact with other proposals.

get - already can execute arbitrary code due to getters set - already can execute arbitrary code due to setters in - cannot for non host objects delete - cannot for non host objects enumerate - cannot for non host objects hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies? This seems bad since a for (in) loop that filters out non-own properties would be O(n**2) on top of the loop body.

If a future version of ES includes some kind of generator/iterator scheme, then "in" and "enumerate" cease to be proxy specific. But iterators could be implemented as proxies if instead of providing (has, keys, enumerate) we provide (prop, keyProducer) where prop(property) -> one of (undefined, OWN, INHERITED), and keyProducer

returns a function, that each time it's called returns a string property name or undefined to signal no-more. Of course, trying to freeze an object that returns a key name multiple times is hard to define, but returning an array has the same problem.

If there is a lazy key mechanism then iterators can be implemented as proxies function iterator(producer) { var pending, produced = false; function fetch() { if (produced) { return; } try { pending = producer(); produced = true; } catch (e) { if (e !== NO_MORE_ELEMENTS) { throw e; } } } return Proxy.create({ get: function (property) { if ('next' === property) { fetch(); var result = pending; pending, produced = void 0, false; } }, has: function (property) { return 'next' === property && (fetch(), produced); }, keyProducer: function () { return function () { return fetch(), produced ? 'next' : void 0; } }); } This doesn't solve generators, since the pausing semantics of yield can't be easily implemented on top of proxies.

2009/12/9 Mark S. Miller <erights at google.com>:

# Mike Samuel (16 years ago)

2009/12/9 Mike Samuel <mikesamuel at gmail.com>:

On the climbing the meta, I'd like to understand how this might interact with other proposals.

get - already can execute arbitrary code due to getters set - already can execute arbitrary code due to setters in - cannot for non host objects delete - cannot for non host objects enumerate - cannot for non host objects hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies?  This seems bad since a for (in) loop that filters out non-own properties would be O(n**2) on top of the loop body.

If a future version of ES includes some kind of generator/iterator scheme, then "in" and "enumerate" cease to be proxy specific.  But iterators could be implemented as proxies if instead of providing (has, keys, enumerate) we provide (prop, keyProducer) where prop(property) -> one of (undefined, OWN, INHERITED), and keyProducer returns a function, that each time it's called returns a string property name or undefined to signal no-more.  Of course, trying to freeze an object that returns a key name multiple times is hard to define, but returning an array has the same problem.

If there is a lazy key mechanism then iterators can be implemented as proxies    function iterator(producer) {      var pending, produced = false;      function fetch() {        if (produced) { return; }        try {          pending = producer(); produced = true;        } catch (e) {          if (e !== NO_MORE_ELEMENTS) { throw e; }        }      }      return Proxy.create({          get: function (property) {            if ('next' === property) {              fetch();              var result = pending;              pending, produced = void 0, false;            }          },         has: function (property) {           return 'next' === property && (fetch(), produced);         },         keyProducer: function () {           return function () { return fetch(), produced ? 'next' : void 0;         }       });    } This doesn't solve generators, since the pausing semantics of yield can't be easily implemented on top of proxies.

Actually, a getter that can delete its own next property is all you need for iterators.

function iterator(producer) { var pending, produced; fetch(); if (!produced) { return {}; } var it = { get next() { var result = pending; try { pending = producer(); produced = true; } catch (e) { if (e !== STOP_ITERATION) { throw e; } delete it.next; } return result; } }; return it; }

for (var it = iterator(x), item; 'next' in it;) { item = it.next; ... }

# Mike Samuel (16 years ago)

Actually, I think if iterators are desired, proxies may be a good way. Code below shows a problem where having an object delete it's own property means hard choices have to be made over when to throw an exception. Proxies are fundamentally lazier and so dodge this issue:

var myIterator = iterator(function () { var i = 0; return function () { if (i === 10) throw STOP_ITERATION; return i++; }; }());

while ('next' in myIterator) { alert(myIterator.next); }

var STOP_ITERATION = {};

function iterator(producer) { var produced, pending; function fetch() { if (produced) { return; } try { pending = producer(); } catch (e) { if (e !== STOP_ITERATION) { throw e; } return; } produced = true; } fetch(); if (!produced) { return {}; } var it = { get next() { var result = pending; pending = void 0; produced = false; try { fetch(); // throws too early? } finally { if (!produced) { delete it.next; } // throws if frozen losing result } return result; } }; return it; }

2009/12/10 Mike Samuel <mikesamuel at gmail.com>:

# Brendan Eich (16 years ago)

On Dec 10, 2009, at 9:57 AM, Mike Samuel wrote:

Actually, I think if iterators are desired, proxies may be a good way.

We have implemented iterators and generators since 2006 in JS1.7
(Firefox 2), and they do not involve proxies or property mutation
including deletion. Those are costly features and I don't see why they
are necessary for an iteration protocol.

var examp1 = {a:1, b:2, c:3}; var examp2 = [4, 5, 6];

function keyIterator() { let keys = Object.keys(this); let i = 0; return { next: function () { if (i == keys.length) throw StopIteration; return keys[i++]; } }; }

function indexIterator() { let self = this; let i = 0; return { next: function () { if (i == self.length) throw StopIteration; return i++; } }; }

Object.defineIterator(examp1, keyIterator); Object.defineIterator(examp2, indexIterator);

for (let i in examp1) print(i + " (type " + typeof i + ")");

for (let i in examp2) print(i + " (type " + typeof i + ")");


example output:

a (type string) b (type string) c (type string) 0 (type number) 1 (type number) 2 (type number)


Notes:

  1. After Python, the meta-level handler is a function that returns an
    iterator for its receiver object. Yeah, that means |this| -- but it's
    just a convention. The handler could take an explicit obj parameter
    instead.

  2. After Python, an iterator is an object with a next() method
    returning the next value in the iteration, or throwing StopIteration
    to end the iteration.

  3. The StopIteration object is a well-known singleton (like Math). In
    our implementation each frame has one but any will do to terminate
    iteration (they all have the same [[Class]]).

/be

P.S. Here are some shims from JS1.[78] in SpiderMonkey to ES5 plus
defineIterator:

Object.defineProperty = function (obj, name, desc) { return obj.defineProperty(name, desc.value, !desc.configurable, ! desc.writable, !desc.enumerable); };

Object.defineIterator = function (obj, iter) { Object.defineProperty(obj, 'iterator', {value: iter}); };

# Mike Samuel (16 years ago)

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 9:57 AM, Mike Samuel wrote:

Actually, I think if iterators are desired, proxies may be a good way.

We have implemented iterators and generators since 2006 in JS1.7 (Firefox 2), and they do not involve proxies or property mutation including deletion. Those are costly features and I don't see why they are necessary for an iteration protocol.

var examp1 = {a:1, b:2, c:3}; var examp2 = [4, 5, 6];

function keyIterator() {    let keys = Object.keys(this);

This will fail for infinite iterators.

# Brendan Eich (16 years ago)

On Dec 10, 2009, at 11:10 AM, Mike Samuel wrote:

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 9:57 AM, Mike Samuel wrote:

Actually, I think if iterators are desired, proxies may be a good
way.

We have implemented iterators and generators since 2006 in JS1.7
(Firefox 2), and they do not involve proxies or property mutation including
deletion. Those are costly features and I don't see why they are necessary
for an iteration protocol.

var examp1 = {a:1, b:2, c:3}; var examp2 = [4, 5, 6];

function keyIterator() { let keys = Object.keys(this);

This will fail for infinite iterators.

So? That has nothing to do with the issue of using proxies for
iterators. First, this example uses an object, and objects do not have
infinitely many keys. Second, iterators as we've implemented them can
compute next values without bound if you code them to do so -- the
next function is free to do whatever it takes.

I'm not sure why you responded only to this line.

/be

(Wow, overcite :-P)

# Mike Samuel (16 years ago)

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 11:10 AM, Mike Samuel wrote:

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 9:57 AM, Mike Samuel wrote:

Actually, I think if iterators are desired, proxies may be a good way.

We have implemented iterators and generators since 2006 in JS1.7 (Firefox 2), and they do not involve proxies or property mutation including deletion. Those are costly features and I don't see why they are necessary for an iteration protocol.

var examp1 = {a:1, b:2, c:3}; var examp2 = [4, 5, 6];

function keyIterator() {   let keys = Object.keys(this);

This will fail for infinite iterators.

So? That has nothing to do with the issue of using proxies for iterators. First, this example uses an object, and objects do not have infinitely many keys. Second, iterators as we've implemented them can compute next values without bound if you code them to do so -- the next function is free to do whatever it takes.

Sorry. I should've paid more attention to the second example which is an infinite iterator.

I was assuming iterators would work without clients explicitly trapping STOP_ITERATION, which is what proxies would provide. But I suppose if we're doing proxies, we can also do the syntactic sugar to do away with that in new looping constructs.

Proxy based iterators work well with existing loop constructs though while ('next' in iterator) doSomething(iterator.next);

This works because I used a different definition of iterator. In my example, the producer has almost exactly the same contract as the next method of your iterator, differing only in the value thrown, and the proxy served to convert it to an iterator. The iterator then an object such that

  • there is no next property iff the iterator is exhausted !('next' in iterator)
  • the next value in the series can be retrieved by reading the next property which advances the iterator
  • optionally, setting or deleting the next property mutates the last element returned on the underlying collection
# Brendan Eich (16 years ago)

On Dec 10, 2009, at 11:31 AM, Mike Samuel wrote:

I was assuming iterators would work without clients explicitly trapping STOP_ITERATION, which is what proxies would provide. But I suppose if we're doing proxies, we can also do the syntactic sugar to do away with that in new looping constructs.

We do indeed have for-in loops, comprehensions, and generator
expressions all automatically catching StopIteration in SpiderMonkey
and Rhino. There is hardly ever a reason to write a try/catch -- just
as in Python.

Proxy based iterators work well with existing loop constructs though while ('next' in iterator) doSomething(iterator.next);

There are lots of convenient ways to express iteration, but one that
mandates proxies is inherently heavier and harder to optimize than I
think we (implementors or in some cases users) want.

This works because I used a different definition of iterator. In my example, the producer has almost exactly the same contract as the next method of your iterator, differing only in the value thrown, and the proxy served to convert it to an iterator. The iterator then an object such that

  • there is no next property iff the iterator is exhausted !('next' in iterator)
  • the next value in the series can be retrieved by reading the next property which advances the iterator
  • optionally, setting or deleting the next property mutates the last element returned on the underlying collection

The last element, or the next element?

Proxies can propagate effects to other objects, for sure, but this is
not only expensive, it's also hard to analyze. An iteration protocol
should be more functional (pure).

We chose to borrow from Python first in the spirit of programming
language design by borrowing from older languages, second to reuse
developer brainprint.

But the functional (with light OO dusting on top, which could be
removed as noted -- getting rid of |this|) flavor of the iteration
protocol in SpiderMonkey and Rhino JS1.7+ is winning both for
implementors and users, in our experience.

To criticize our JS1.7/1.8 experience a bit:

A. The obvious problem is that we hang the meta-level handler off the
base-level object, as Python does with its iter. But
Object.defineIterator seems like the fix for this bug.

B. Creating an object with a next method instead of a closure may be
one object too many. The object need not carry any state that's not in
the next method's environment (as in the examples I showed).

But the object does provide an identity separate from next, in which
send, throw, and close methods are bound for generators. And the
object allows state representation optimizations other than closure- based ones.

C. Generators, being flat (one frame of activation saved) yet heap- escaping, don't always compose nicely. This has spawned PEP 380 (www.python.org/dev/peps/pep-0380 ).

Generators are nevertheless quite convenient according to reports from
our users, and pretty easy to implement in any implementation that
compiles into bytecode or something more sophisticated, instead of
walking ASTs to evaluate code.

It's important not to oversell generators as solving concurrency
issues or being "coroutines" -- they are best thought of as the
simplest way to write an iteration protocol handler (or more casually,
"an iterator"). Here are the example iterators recast as generators:

function keyIterator() { let keys = Object.keys(this); for (let i = 0; i < keys.length; i++) yield keys[i]; }

function indexIterator() { for (let i = 0; i < this.length; i++) yield i; }

When called, a generator returns an iterator, so these are factories.
The functional version had to return objects with next methods:

function keyIterator() { let keys = Object.keys(this); let i = 0; return { next: function () { if (i == keys.length) throw StopIteration; return keys[i++]; } }; }

function indexIterator() { let self = this; let i = 0; return { next: function () { if (i == self.length) throw StopIteration; return i++; } }; }

This use-case is where generators shine.

# Mike Wilson (16 years ago)

Thanks for the example! Reading up on your "transparent chains" example, it seems this will also work fine for delegation in "class hierarchies" with multiple proxy points (User inherits from Person and both classes are proxied, etc).

Just a couple of minor comments on other parts:

Why does the handler use "put" for the setter trap instead of "set" like in previous catchall proposals? (It seems get/set would also provide more symmetry with syntax in Object.defineProperty.)

[really minor nit-pick:] The proposal text sometimes uses the variable "p" for property names, sometimes for a proxy object. It might aid new readers if these were renamed "prop" and "proxy", respectively. A similar issue exists in the handler API listing where the same proxy object is referred to as both "proxy" and "receiver".

Tom Van Cutsem wrote:

Here's the essence of one possible solution:

var attributeBasedFinder = Proxy.create({ ...

Best Mike

# Mike Samuel (16 years ago)

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 11:31 AM, Mike Samuel wrote:

I was assuming iterators would work without clients explicitly trapping STOP_ITERATION, which is what proxies would provide.   But I suppose if we're doing proxies, we can also do the syntactic sugar to do away with that in new looping constructs.

We do indeed have for-in loops, comprehensions, and generator expressions all automatically catching StopIteration in SpiderMonkey and Rhino. There is hardly ever a reason to write a try/catch -- just as in Python.

Proxy based iterators work well with existing loop constructs though   while ('next' in iterator) doSomething(iterator.next);

There are lots of convenient ways to express iteration, but one that mandates proxies is inherently heavier and harder to optimize than I think we (implementors or in some cases users) want.

This works because I used a different definition of iterator.  In my example, the producer has almost exactly the same contract as the next method of your iterator, differing only in the value thrown, and the proxy served to convert it to an iterator. The iterator then an object such that  - there is no next property iff the iterator is exhausted !('next' in iterator)  - the next value in the series can be retrieved by reading the next property which advances the iterator  - optionally, setting or deleting the next property mutates the last element returned on the underlying collection

The last element, or the next element?

Previous as in java Iterators.

Proxies can propagate effects to other objects, for sure, but this is not only expensive, it's also hard to analyze. An iteration protocol should be more functional (pure).

We chose to borrow from Python first in the spirit of programming language design by borrowing from older languages, second to reuse developer brainprint.

Fair enough.

But the functional (with light OO dusting on top, which could be removed as noted -- getting rid of |this|) flavor of the iteration protocol in SpiderMonkey and Rhino JS1.7+ is winning both for implementors and users, in our experience.

To criticize our JS1.7/1.8 experience a bit:

A. The obvious problem is that we hang the meta-level handler off the base-level object, as Python does with its iter. But Object.defineIterator seems like the fix for this bug.

B. Creating an object with a next method instead of a closure may be one object too many. The object need not carry any state that's not in the next method's environment (as in the examples I showed).

But the object does provide an identity separate from next, in which send, throw, and close methods are bound for generators. And the object allows state representation optimizations other than closure-based ones.

It would extend to set/delete as in java though. Java has basically ignored set/delete on iterators in the syntactic sugar around Iterables.

C. Generators, being flat (one frame of activation saved) yet heap-escaping, don't always compose nicely. This has spawned PEP 380 (www.python.org/dev/peps/pep-0380).

Generators compose in some ways though the syntax can be awkward: def zip(a, b): for a_el in a: yield (a_el, b.next()) try: b.next() except StopIteration: pass except: raise AssertionError('%r not exhausted', b)

The part that does the work is straight-forward, and then the corner case checking is, as usual, the most verbose.

Generators are nevertheless quite convenient according to reports from our users, and pretty easy to implement in any implementation that compiles into bytecode or something more sophisticated, instead of walking ASTs to evaluate code.

Cool.

It's important not to oversell generators as solving concurrency issues or being "coroutines" -- they are best thought of as the simplest way to write an iteration protocol handler (or more casually, "an iterator"). Here are the example iterators recast as generators:

function keyIterator() {    let keys = Object.keys(this);    for (let i = 0; i < keys.length; i++)        yield keys[i]; }

function indexIterator() {    for (let i = 0; i < this.length; i++)        yield i; }

When called, a generator returns an iterator, so these are factories. The functional version had to return objects with next methods:

function keyIterator() {    let keys = Object.keys(this);    let i = 0;    return {        next: function () {            if (i == keys.length)                throw StopIteration;            return keys[i++];        }    }; }

function indexIterator() {    let self = this;    let i = 0;    return {        next: function () {            if (i == self.length)                throw StopIteration;            return i++;        }    }; }

This use-case is where generators shine.

I think your arguments against using proxies to implement iterators are strong, so maybe keys/enumerate should be considered tentative so that they can be informed by any separate work on iterators.

Again, separating out the concept of own-ness from key enumeration might make that easier.

# Mike Samuel (16 years ago)

2009/12/10 Mike Samuel <mikesamuel at gmail.com>:

2009/12/10 Brendan Eich <brendan at mozilla.com>:

On Dec 10, 2009, at 11:31 AM, Mike Samuel wrote:

I was assuming iterators would work without clients explicitly trapping STOP_ITERATION, which is what proxies would provide.   But I suppose if we're doing proxies, we can also do the syntactic sugar to do away with that in new looping constructs.

We do indeed have for-in loops, comprehensions, and generator expressions all automatically catching StopIteration in SpiderMonkey and Rhino. There is hardly ever a reason to write a try/catch -- just as in Python.

Proxy based iterators work well with existing loop constructs though   while ('next' in iterator) doSomething(iterator.next);

There are lots of convenient ways to express iteration, but one that mandates proxies is inherently heavier and harder to optimize than I think we (implementors or in some cases users) want.

This works because I used a different definition of iterator.  In my example, the producer has almost exactly the same contract as the next method of your iterator, differing only in the value thrown, and the proxy served to convert it to an iterator. The iterator then an object such that  - there is no next property iff the iterator is exhausted !('next' in iterator)  - the next value in the series can be retrieved by reading the next property which advances the iterator  - optionally, setting or deleting the next property mutates the last element returned on the underlying collection

The last element, or the next element?

Previous as in java Iterators.

Proxies can propagate effects to other objects, for sure, but this is not only expensive, it's also hard to analyze. An iteration protocol should be more functional (pure).

We chose to borrow from Python first in the spirit of programming language design by borrowing from older languages, second to reuse developer brainprint.

Fair enough.

But the functional (with light OO dusting on top, which could be removed as noted -- getting rid of |this|) flavor of the iteration protocol in SpiderMonkey and Rhino JS1.7+ is winning both for implementors and users, in our experience.

To criticize our JS1.7/1.8 experience a bit:

A. The obvious problem is that we hang the meta-level handler off the base-level object, as Python does with its iter. But Object.defineIterator seems like the fix for this bug.

B. Creating an object with a next method instead of a closure may be one object too many. The object need not carry any state that's not in the next method's environment (as in the examples I showed).

But the object does provide an identity separate from next, in which send, throw, and close methods are bound for generators. And the object allows state representation optimizations other than closure-based ones.

It would extend to set/delete as in java though. Java has basically ignored set/delete on iterators in the syntactic sugar around Iterables.

C. Generators, being flat (one frame of activation saved) yet heap-escaping, don't always compose nicely. This has spawned PEP 380 (www.python.org/dev/peps/pep-0380).

Generators compose in some ways though the syntax can be awkward: def zip(a, b):  for a_el in a:    yield (a_el, b.next())  try:    b.next()  except StopIteration:    pass  except:    raise AssertionError('%r not exhausted', b)

Sorry, zip should probably just read def zip(a, b): for a_el in a: yield (a_el, b.next()) b.next()

# Brendan Eich (16 years ago)

On Dec 9, 2009, at 9:48 PM, Mark S. Miller wrote:

In general, handler writers have to implement standard prototype- based delegation if it is desired. This is probably the right thing,
but I wonder if you considered the alternative where prototype
delegation is handled "by the spec" or "by the runtime" and the
proxy is considered "flat"?

We did think about it, but it seemed needlessly less flexible. If
such flat-and-delegate handling is desired, an abstraction can be
built on top of ours that emulates it as a convenience. The reverse
emulation seems difficult at best.

Agreed, good point.

Since the undefined may be the result of a bug as you say, it seems
worse for the bug to silently result in fixing the proxy into an
empty frozen object. We think the current "noisy" behavior better
supports defensive programming.

Of course, the fix handler could throw and that would propagate too
but it's more concise to return undefined (or fall off the end? Still
a bit too implicit for my taste, but never mind). Agreed.

In answer, <strawman:proxies#an_identity-preserving_membrane

preserves === correspondence on each side of a membrane. Now that
we have a concrete catchall proposal adequate to build membranes,
we'd like to restart our discussions with Mozilla (JetPack, etc)
about whether you could rebuild some of your C++ membranes in JS
code using these primitives. We should follow up offlist.

We should, but I wanted to note that we're not rushing to self-host
our wrappers. They are part of the C++ TCB and take advantage of
certain privileges inherent there that would have to be reflected into
JS along with proxy support.

We may get there (my long term goal is to self-host much of what's now
native in our DOM code, and shrink the TCB) but efficiency as well as
complexity in pulling on a "need to reflect APIs X, Y and Z from C++
land" ball of string put the priority of this work lower than you seem
to want.

To avoid some "climbing meta ladder" issues, we purposely
distinguish between what we consider base-level operations, such as
x.foo, and meta-level operations, such as Object.getOwnProperty(x,
'foo'). We attempt to be as fully transparent (leak free) as
reasonably possible at virtualizing base level operations. We
attempt to be fully non-transparent (leak like a firehose) to meta- level operations. Some of our classification may seem weird:
Object.prototype.toString() is meta-level. It can be used to reveal
that an object is a trapping proxy. Object.getOwnPropertyNames() is
meta-level. Object.keys() is base level.

You're right, some of this is weird :-P. Any detailed rationale?

As noted, we've interposed wrappers where they are not expected and
their effects should not be observable to the wrapper-using code
except by preventing security exploits. So they want to be
transparent, even for such ES5 precursors as defineGetter,
lookupGetter, etc.

This is an issue, since at least live.com "Ajax-style" JS uses these
SpiderMonkey extensions (which other browsers emulated) to extend the
more-standard DOMs in non-IE browsers to look like IE's DOM, and we
sometimes interpose a wrapper.

So I'm not sure our use cases want any non-transparency on a meta- level basis. Blake might be able to say more.

The properties of === that we feel need to be preserved:

  1. "x === y" does not cause any user code to run.
  2. "x === y" neither gives x access to y nor vice versa.
  3. "typeof x !== 'number' && x === y" mean that x is operationally
    identical to y in all observable ways. Substituting the value for x
    with the value of y cannot change the meaning of a computation.
  4. "x === y" implies that the Grant Matcher <erights.org/elib/equality/grant-matcher

may safely send the money to either x or y.

A wrapper is not identical to the object it is wrapping, or there
wouldn't be any point in wrapping it. Thus, they can't be ===. Two independent wrappers on the same object may behave differently,
depending on their definers. Thus they can't be ===. Except for two proxies with identical parts, such as two object
proxies with identical handlers and supers. However, as shown by our
example membrane, one can just use an Ephemeron table to avoid
creating semantically identical duplicate proxies, preserving ===
without magic.

This is probably a good long-term solution for our wrappers, as we
already have C++-only weak tables and the like to track and reuse
wrappers.

  1. The [[Get]] versus [[Invoke]] rationale: indeed performance is a
    concern, but existing engines also specialize callee-computation
    distinctly from get-value, in order to optimize away Reference
    types. The ES specs so far do not, instead using the internal
    Reference type to delay GetValue so as to bind |this| to the
    Reference base when computing a callee and its receiver as part of
    evaluating a call expression.

I think it is an open question whether a future spec, especially
one using a definitional interpreter, will stick to References. If
we end up making the distinction that all practical implementations
already make, between get-as-part-of-callee-computation and all
other get-value "gets", then I don't think this rationale is so
strong.

In general over-coupling to ES5 may not help either a new Harmony- era proposal to "get in", or to be as complete or expressive as it
should be. So a rationale based on choices or limitations of ES1-5
seems weak to me.

+100. I would love to see the concept of References disappear, and to see
the ([[Get]], [[Call]]) pairs in the spec that really mean "call
this as a method" be rewritten as [[Invoke]]s. In that case, I would
enthusiastically agree that this catchall proposal should be
upgraded with an invoke() trap. Note how this would make our
membrane code simpler and more efficient. Rather than a get() trap
at the choke point that creates and returns a function, we'd simply
have an invoke() trap whose body is that function's body.

One issue: ES specifies order of evaluation in detail, so array[index+ +].method(--j, ++k) really does evaluate left to right.

If invoking 'method' from the object at array[index] involves a
handler, the handler should be able to witness index++ having
happened, but not yet --j and --k. This is because without an invoke
handler, but with a get handler in array[index], the getter will run
in the left-to-right order.

So when would [[Invoke]] run?

I'd like to understand better how we could get rid of References.

That's easy. Consider a toy grammar:

E ::= M '=' E | T

T ::= T '+' M | M

M ::= M '.' id | '(' E ')' | id

The ES specs always make Reference types for M.id and id, do GetValue
on any the result of evaluating any expression not on the left of '=',
and do PutValue on the left of '='.

The alternative is to write the semantics "bottom-up" style (yacc,
bison, etc.) and in terms of ASTs or equivalent intermediate forms
built during reduction. Then the semantic action associated with M '='
E can crack M apart into its base (M if M.id else null) and
propertyName (id) parts, with early error throwing for invalid left- hand sides (E in parentheses where E does not reduce to id or M.id).

This is what WebKit/JavaScriptCore/parser/Grammar.y does. It fits our
formal bottom-up grammar requirement. It does require ASTs to delay
evaluation, but (see the AST discussion) those have other uses ;-).

# Brendan Eich (16 years ago)

On Dec 10, 2009, at 12:51 PM, Mike Wilson wrote:

[really minor nit-pick:] The proposal text sometimes uses the
variable "p" for property names, sometimes for a proxy object. It
might aid new readers if these were renamed "prop" and "proxy",
respectively. A similar issue exists in the handler API listing
where the same proxy object is referred to as both "proxy" and
"receiver".

Good nit. To add to it, using "name" instead of "prop" is even better,
for different first letter with "proxy". 'P' is for proxy...

# Brendan Eich (16 years ago)

On Dec 10, 2009, at 12:56 PM, Mike Samuel wrote:

Generators compose in some ways though the syntax can be awkward: def zip(a, b): for a_el in a: yield (a_el, b.next()) try: b.next() except StopIteration: pass except: raise AssertionError('%r not exhausted', b)

Sorry, zip should probably just read def zip(a, b): for a_el in a: yield (a_el, b.next()) b.next()

Right -- it really is quite rare to have to catch StopIteration. If
you feel the need, something else may be going on that needs
addressing at a higher level.

# Tom Van Cutsem (16 years ago)

On Thu, Dec 10, 2009 at 12:51 PM, Mike Wilson <mikewse at hotmail.com> wrote:

Why does the handler use "put" for the setter trap instead of "set" like in previous catchall proposals? (It seems get/set would also provide more symmetry with syntax in Object.defineProperty.)

Yes, "set" is indeed more consistent. We changed it.

[really minor nit-pick:] The proposal text sometimes uses the variable "p" for property names, sometimes for a proxy object. It might aid new readers if these were renamed "prop" and "proxy", respectively. A similar issue exists in the handler API listing where the same proxy object is referred to as both "proxy" and "receiver".

We modified the proposal page, including Brendan's suggestion of using "name" instead of "p".

# Tom Van Cutsem (16 years ago)

On Thu, Dec 10, 2009 at 1:21 PM, Brendan Eich <brendan at mozilla.com> wrote:

To avoid some "climbing meta ladder" issues, we purposely distinguish

between what we consider base-level operations, such as x.foo, and meta-level operations, such as Object.getOwnProperty(x, 'foo'). We attempt to be as fully transparent (leak free) as reasonably possible at virtualizing base level operations. We attempt to be fully non-transparent (leak like a firehose) to meta-level operations. Some of our classification may seem weird: Object.prototype.toString() is meta-level. It can be used to reveal that an object is a trapping proxy. Object.getOwnPropertyNames() is meta-level. Object.keys() is base level.

You're right, some of this is weird :-P. Any detailed rationale?

We don't have a solid rationale. We summarized the interactions between these methods and proxies in a separate section: < strawman:proxies#interaction_of_external_methods_and_proxies

The split between "base" and "meta" methods is certainly open for discussion.

# Mark S. Miller (16 years ago)

On Wed, Dec 9, 2009 at 11:26 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

On the climbing the meta, I'd like to understand how this might interact with other proposals.

get - already can execute arbitrary code due to getters set - already can execute arbitrary code due to setters in - cannot for non host objects delete - cannot for non host objects enumerate - cannot for non host objects

By the currently proposed taxonomy at < strawman:proxies#interaction_of_external_methods_and_proxies>,

all the above are base-level operations. The list at < strawman:proxies#climbing_the_meta_ladder>

explains that all the above can cause user code to execute whereas the last three, as you point out, currently cannot.

hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies? This seems bad since a for (in) loop that filters out non-own properties would be O(n**2) on top of the loop body.

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is meta-level and always returns false on a trapping proxy, revealing that it does not actually have any own properties.

# Mark S. Miller (16 years ago)

On Thu, Dec 10, 2009 at 4:42 PM, Mark S. Miller <erights at google.com> wrote:

On Wed, Dec 9, 2009 at 11:26 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

On the climbing the meta, I'd like to understand how this might interact with other proposals.

get - already can execute arbitrary code due to getters set - already can execute arbitrary code due to setters in - cannot for non host objects delete - cannot for non host objects enumerate - cannot for non host objects

By the currently proposed taxonomy at < strawman:proxies#interaction_of_external_methods_and_proxies>, all the above are base-level operations. The list at < strawman:proxies#climbing_the_meta_ladder> explains that all the above can cause user code to execute whereas the last three, as you point out, currently cannot.

hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies? This seems bad since a for (in) loop that filters out non-own properties would be O(n**2) on top of the loop body.

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is meta-level and always returns false on a trapping proxy, revealing that it does not actually have any own properties.

Arrg! That should be Object.prototype.hasOwnProperty.call(myProxy, name).

aProxy.hasOwnProperty(name) will of course trap as you'd expect, and so is base level.

# Mike Samuel (16 years ago)

2009/12/10 Mark S. Miller <erights at google.com>:

On Wed, Dec 9, 2009 at 11:26 PM, Mike Samuel <mikesamuel at gmail.com> wrote:

On the climbing the meta, I'd like to understand how this might interact with other proposals.

get - already can execute arbitrary code due to getters set - already can execute arbitrary code due to setters in - cannot for non host objects delete - cannot for non host objects enumerate - cannot for non host objects

By the currently proposed taxonomy at strawman:proxies#interaction_of_external_methods_and_proxies, all the above are base-level operations. The list at strawman:proxies#climbing_the_meta_ladder explains that all the above can cause user code to execute whereas the last three, as you point out, currently cannot.

Yes, sorry. I didn't mean to simply reiterate your writeup. I was trying to figure out which ops might end up being able to invoke arbitrary code based on other proposed features. I can't think of any though since iterators seem to not involve virtualizing "in/enumerate".

hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies?  This seems bad since a for (in) loop that filters out non-own properties would be O(n**2) on top of the loop body.

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is meta-level and always returns false on a trapping proxy, revealing that it does not actually have any own properties.

Hmm. So a proxy of an object cannot be used as a drop-in replacement for that object with any code that uses own property loops like for (var k in o) if (Object.hasOwnProperty.call(o, k)) ... ? That seems to make it less useful.

# David-Sarah Hopwood (16 years ago)

Mike Samuel wrote:

Proxy based iterators work well with existing loop constructs though while ('next' in iterator) doSomething(iterator.next);

I don't understand what advantage this has over while (iterator.hasNext()) doSomething(iterator.next());

If next() has side-effects (moving to the next element), it shouldn't be a getter.

# Maciej Stachowiak (16 years ago)

On Dec 10, 2009, at 5:09 PM, Mike Samuel wrote:

2009/12/10 Mark S. Miller <erights at google.com>:

hasOwnProperty - cannot for non host objects

Incidentally, is Object.prototype.hasOwnProperty(myProxy) O(myProxyHandler.keys().length) for proxies? This seems bad since a for (in) loop that filters out non-own properties would be O(n**2)
on top of the loop body.

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is meta-level and always returns false on a trapping proxy, revealing
that it does not actually have any own properties.

Hmm. So a proxy of an object cannot be used as a drop-in replacement for that object with any code that uses own property loops like for (var k in o) if (Object.hasOwnProperty.call(o, k)) ... ? That seems to make it less useful.

It would also make this mechanism unsuitable for building emulations
of DOM objects that have catchall properties in pure ECMAScript. Is
that a design goal for this mechanism?

, Maciej

# Mark S. Miller (16 years ago)

On Thu, Dec 10, 2009 at 6:42 PM, Maciej Stachowiak <mjs at apple.com> wrote:

On Dec 10, 2009, at 5:09 PM, Mike Samuel wrote:

2009/12/10 Mark S. Miller <erights at google.com>:

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is

meta-level and always returns false on a trapping proxy, revealing that it

does not actually have any own properties.

Hmm. So a proxy of an object cannot be used as a drop-in replacement for that object with any code that uses own property loops like for (var k in o) if (Object.hasOwnProperty.call(o, k)) ... ? That seems to make it less useful.

It would also make this mechanism unsuitable for building emulations of DOM objects that have catchall properties in pure ECMAScript. Is that a design goal for this mechanism?

Hi Maciej, I agree that it is a problem. Tom & I are currently discussing what to do about it. But I don't understand your description above. What does "DOM objects that have catchall properties in pure ECMAScript" mean? Thanks.

# Mike Samuel (16 years ago)

2009/12/10 David-Sarah Hopwood <david-sarah at jacaranda.org>:

Mike Samuel wrote:

Proxy based iterators work well with existing loop constructs though     while ('next' in iterator) doSomething(iterator.next);

I don't understand what advantage this has over      while (iterator.hasNext()) doSomething(iterator.next());

Sure. If there were a hasNext() method, that would work. Python does not have such a thing.

If next() has side-effects (moving to the next element), it shouldn't be a getter.

Fair enough.

# Maciej Stachowiak (16 years ago)

On Dec 10, 2009, at 8:30 PM, Mark S. Miller wrote:

On Thu, Dec 10, 2009 at 6:42 PM, Maciej Stachowiak <mjs at apple.com>
wrote:

On Dec 10, 2009, at 5:09 PM, Mike Samuel wrote:

2009/12/10 Mark S. Miller <erights at google.com>:

By the current taxonomy, Object.prototype.hasOwnProperty(myProxy) is meta-level and always returns false on a trapping proxy, revealing
that it does not actually have any own properties.

Hmm. So a proxy of an object cannot be used as a drop-in replacement for that object with any code that uses own property loops like for (var k in o) if (Object.hasOwnProperty.call(o, k)) ... ? That seems to make it less useful.

It would also make this mechanism unsuitable for building emulations
of DOM objects that have catchall properties in pure ECMAScript. Is
that a design goal for this mechanism?

Hi Maciej, I agree that it is a problem. Tom & I are currently
discussing what to do about it. But I don't understand your
description above. What does "DOM objects that have catchall
properties in pure ECMAScript" mean? Thanks.

It looks like I plied too many clauses and prepositional phrases into
once sentence. Let me try to rephrase:

If you want to emulate a DOM object that has catchall properties, then
it needs to return true for hasOwnProperty() for the things that
should be own properties. If you want to build that emulation in pure
ECMAScript (perhaps as a security façade), then you need a catchall
mechanism that lets the object appear to have own properties as seen
through hasOwnProperty. It seems like the proposed mechanism as
described does not allow this - assuming I understood your initial
comment quoted above correctly.

, Maciej

# Mark Miller (16 years ago)

On Thu, Dec 10, 2009 at 9:05 PM, Maciej Stachowiak <mjs at apple.com> wrote:

It looks like I plied too many clauses and prepositional phrases into once sentence. Let me try to rephrase: If you want to emulate a DOM object that has catchall properties, then it needs to return true for hasOwnProperty() for the things that should be own properties. If you want to build that emulation in pure ECMAScript (perhaps as a security façade), then you need a catchall mechanism that lets the object appear to have own properties as seen through hasOwnProperty. It seems like the proposed mechanism as described does not allow this - assuming I understood your initial comment quoted above correctly.

Good. You did understand me correctly. Now that I understand you, I agree that this is a design goal of our mechanism.

Before proposing a fix, let me check if I understand the nature of the problem. Are you saying that, given that d either is a DOM object with an own property "name" or d is an emulation of such a DOM object, that not only must

d.hasOwnProperty(name)

be true, but so must

Object.prototype.hasOwnProperty.call(d, name)

? I think I agree this is a reasonable requirement, since many JS programmers have learned to call hasOwnProperty "statically" in this manner. What about Object.prototype.propertyIsEnumerable? In the current state of our proposal, it is as meta as Object.prototype.hasOwnProperty. Before fixing Object.prototype.hasOwnProperty, I'd like a better sense of whether Object.prototype.propertyIsEnumerable needs to be fixed as well. Thanks.

# Maciej Stachowiak (16 years ago)

On Dec 10, 2009, at 9:19 PM, Mark Miller wrote:

On Thu, Dec 10, 2009 at 9:05 PM, Maciej Stachowiak <mjs at apple.com>
wrote:

It looks like I plied too many clauses and prepositional phrases
into once sentence. Let me try to rephrase: If you want to emulate a DOM object that has catchall properties,
then it needs to return true for hasOwnProperty() for the things that
should be own properties. If you want to build that emulation in pure ECMAScript
(perhaps as a security façade), then you need a catchall mechanism that lets
the object appear to have own properties as seen through
hasOwnProperty. It seems like the proposed mechanism as described does not allow this - assuming I understood your initial comment quoted above correctly.

Good. You did understand me correctly. Now that I understand you, I agree that this is a design goal of our mechanism.

Before proposing a fix, let me check if I understand the nature of the problem. Are you saying that, given that d either is a DOM object with an own property "name" or d is an emulation of such a DOM object, that not only must

d.hasOwnProperty(name)

be true, but so must

Object.prototype.hasOwnProperty.call(d, name)

I'm a little less confident of the latter than the former. However,
Google Code Search finds a number of hits for
Object.prototype.hasOwnProperty.call that appear to be operating on
potentially arbitrary objects and property names. At least some of the
JavaScript code in question looked like it might be actually deployed
on the Web.

? I think I agree this is a reasonable requirement, since many JS programmers have learned to call hasOwnProperty "statically" in this manner. What about Object.prototype.propertyIsEnumerable? In the current state of our proposal, it is as meta as Object.prototype.hasOwnProperty. Before fixing Object.prototype.hasOwnProperty, I'd like a better sense of whether c needs to be fixed as well. Thanks.

Google Code Search sees this pattern appearing in a number of places,
some of which are JavaScript libraries (though others seem like code
bases that could feasibly be updated):

www.google.com/codesearch?q=Object.prototype.propertyIsEnumerable.call&hl=en&btnG=Search+Code

, Maciej

# Mark S. Miller (16 years ago)

On Thu, Dec 10, 2009 at 9:58 PM, Maciej Stachowiak <mjs at apple.com> wrote:

Object.prototype.hasOwnProperty.call(d, name)

I'm a little less confident of the latter than the former. However, Google Code Search finds a number of hits for Object.prototype.hasOwnProperty.call that appear to be operating on potentially arbitrary objects and property names. At least some of the JavaScript code in question looked like it might be actually deployed on the Web.

Google Code Search sees this pattern appearing in a number of places, some of which are JavaScript libraries (though others seem like code bases that could feasibly be updated):

www.google.com/codesearch?q=Object.prototype.propertyIsEnumerable.call&hl=en&btnG=Search+Code

Thanks for reminding me to use that tool!

Constraining the searches to javascript,

www.google.com/codesearch?hl=en&lr=&q=Object.prototype.propertyIsEnumerable.call+lang:javascript&sbtn=Search

shows 85 results whereas

www.google.com/codesearch?hl=en&lr=&q=Object.prototype.hasOwnProperty.call+lang:javascript&sbtn=Search

has 263. I will proceed to worry only about hasOwnProperty until someone objects.

# Maciej Stachowiak (16 years ago)

On Dec 10, 2009, at 10:06 PM, Mark S. Miller wrote:

On Thu, Dec 10, 2009 at 9:58 PM, Maciej Stachowiak <mjs at apple.com>
wrote:

Object.prototype.hasOwnProperty.call(d, name)

I'm a little less confident of the latter than the former. However,
Google Code Search finds a number of hits for
Object.prototype.hasOwnProperty.call that appear to be operating on
potentially arbitrary objects and property names. At least some of
the JavaScript code in question looked like it might be actually
deployed on the Web.

Google Code Search sees this pattern appearing in a number of
places, some of which are JavaScript libraries (though others seem
like code bases that could feasibly be updated):

www.google.com/codesearch?q=Object.prototype.propertyIsEnumerable.call&hl=en&btnG=Search+Code

Thanks for reminding me to use that tool!

Constraining the searches to javascript,

www.google.com/codesearch?hl=en&lr=&q=Object.prototype.propertyIsEnumerable.call+lang:javascript&sbtn=Search

shows 85 results whereas

www.google.com/codesearch?hl=en&lr=&q=Object.prototype.hasOwnProperty.call+lang:javascript&sbtn=Search

has 263. I will proceed to worry only about hasOwnProperty until
someone objects.

Note that these are hits found in source repositories, not on the Web.
So I would not put too much stock in the number of hits except as an
existence proof. Both of these include hits from JavaScript libraries,
likely meaning there are many deployed copies of the code in question
(though unclear if that code path gets hit as the JS libraries are
used in practice).

, Maciej

# Brendan Eich (16 years ago)

On Dec 11, 2009, at 8:36 AM, Maciej Stachowiak wrote:

On Dec 10, 2009, at 10:06 PM, Mark S. Miller wrote:

has 263. I will proceed to worry only about hasOwnProperty until
someone objects.

Note that these are hits found in source repositories, not on the
Web. So I would not put too much stock in the number of hits except
as an existence proof. Both of these include hits from JavaScript
libraries, likely meaning there are many deployed copies of the code
in question (though unclear if that code path gets hit as the JS
libraries are used in practice).

Absolutely. The best case with codesearch would be to get no results
-- that would suggest but not prove that the construct is not used.
Getting any hits casts doubt on claim that the construct isn't used
enough to worry about. More than a few handfuls of hits -> you should

worry.

Mark, I don't think we can pick meta-level methods to worry about and
not worry about, if the goal is transparent wrapping or catch-all
based DOM emulations. OTOH, propertyIsEnumerable is much less used
than hasOwnProperty, whether directly or via .call. But in principle
it is the same kind of animal.

# Mark S. Miller (16 years ago)

On Fri, Dec 11, 2009 at 9:31 AM, Brendan Eich <brendan at mozilla.com> wrote:

On Dec 11, 2009, at 8:36 AM, Maciej Stachowiak wrote:

On Dec 10, 2009, at 10:06 PM, Mark S. Miller wrote:

has 263. I will proceed to worry only about hasOwnProperty until someone

objects.

Note that these are hits found in source repositories, not on the Web. So I would not put too much stock in the number of hits except as an existence proof. Both of these include hits from JavaScript libraries, likely meaning there are many deployed copies of the code in question (though unclear if that code path gets hit as the JS libraries are used in practice).

Absolutely. The best case with codesearch would be to get no results -- that would suggest but not prove that the construct is not used. Getting any hits casts doubt on claim that the construct isn't used enough to worry about. More than a few handfuls of hits -> you should worry.

Mark, I don't think we can pick meta-level methods to worry about and not worry about, if the goal is transparent wrapping or catch-all based DOM emulations. OTOH, propertyIsEnumerable is much less used than hasOwnProperty, whether directly or via .call. But in principle it is the same kind of animal.

I have now changed the spec so that Object.prototype.hasOwnProperty.call and Object.prototype. propertyIsEnumerable.call are base level rather than meta level methods. Proxies can now virtualize them fine, at the price of loading down the handler protocol with two more methods. As discussed earlier, I also added an invoke: trap so that named method invocations can be handled in one step without having to curry over the method name. So, altogether, three more methods. On the other hand, the double reflection needed by the membrane became simpler and faster, since the choke point is now an invoke trap.

Of legacy methods, the only one which is still meta-level is Object.prototype.toString.call, as is necessary to preserve its meaning. The similar Function.prototype.toString.call remains in limbo awaiting a clearer spec. Neither of these should impede faithful emulation of existing DOM expectations.

# Mike Samuel (16 years ago)

2009/12/11 Mark S. Miller <erights at google.com>:

On Fri, Dec 11, 2009 at 9:31 AM, Brendan Eich <brendan at mozilla.com> wrote:

On Dec 11, 2009, at 8:36 AM, Maciej Stachowiak wrote:

On Dec 10, 2009, at 10:06 PM, Mark S. Miller wrote:

has 263. I will proceed to worry only about hasOwnProperty until someone objects.

Note that these are hits found in source repositories, not on the Web. So I would not put too much stock in the number of hits except as an existence proof. Both of these include hits from JavaScript libraries, likely meaning there are many deployed copies of the code in question (though unclear if that code path gets hit as the JS libraries are used in practice).

Absolutely. The best case with codesearch would be to get no results -- that would suggest but not prove that the construct is not used. Getting any hits casts doubt on claim that the construct isn't used enough to worry about. More than a few handfuls of hits -> you should worry.

Mark, I don't think we can pick meta-level methods to worry about and not worry about, if the goal is transparent wrapping or catch-all based DOM emulations. OTOH, propertyIsEnumerable is much less used than hasOwnProperty, whether directly or via .call. But in principle it is the same kind of animal.

I have now changed the spec so that Object.prototype.hasOwnProperty.call and Object.prototype. propertyIsEnumerable.call are base level rather than meta level methods. Proxies can now virtualize them fine, at the price of loading down the handler protocol with two more methods. As discussed earlier, I also added an invoke: trap so that named method invocations can be handled in one step without having to curry over the method name. So, altogether, three more methods. On the other hand, the double reflection needed by the membrane became simpler and faster, since the choke point is now an invoke trap. Of legacy methods, the only one which is still meta-level is Object.prototype.toString.call, as is necessary to preserve its meaning. The similar Function.prototype.toString.call remains in limbo awaiting a clearer spec. Neither of these should impede faithful emulation of existing DOM expectations.

On the interaction of Function.prototype.toString and function proxies, one use case is code that tries to get at a function's name as by doing

function nameOf(f) { if ('name' in f) { return f.name; } // Works on some interpreters var m = ('' + f).match(/^function\s+([^(\s]+)/); return m ? m[1] : void 0; }

I'm not sure how much of the existing code like this invokes toString directly or indirectly instead of using Function.prototype.toString.

Function proxy handlers could implement has('name') then there might not be a need for Function.prototype.toString to support this use case.

# Mark S. Miller (16 years ago)

On Sat, Dec 12, 2009 at 10:53 AM, Mike Samuel <mikesamuel at gmail.com> wrote:

On the interaction of Function.prototype.toString and function proxies, one use case is code that tries to get at a function's name as by doing

function nameOf(f) { if ('name' in f) { return f.name; } // Works on some interpreters var m = ('' + f).match(/^function\s+([^(\s]+)/); return m ? m[1] : void 0; }

I'm not sure how much of the existing code like this invokes toString directly or indirectly instead of using Function.prototype.toString.

Function proxy handlers could implement has('name') then there might not be a need for Function.prototype.toString to support this use case.

Under the current proposal, a trapping function proxy f can virtualize all the following without problem:

'name' in f
f.name
'' + f
f.toString()

The only open issue is

Function.prototype.toString.call(f)

I would have ventured a guess that this isn't used in real code. But having learned my lesson ;), I looked. What do we all think of < www.google.com/codesearch?hl=en&lr=&q=Function.prototype.toString.call+lang:javascript&sbtn=Search

# Maciej Stachowiak (16 years ago)

On Dec 12, 2009, at 11:08 AM, Mark S. Miller wrote:

On Sat, Dec 12, 2009 at 10:53 AM, Mike Samuel <mikesamuel at gmail.com>
wrote: On the interaction of Function.prototype.toString and function proxies, one use case is code that tries to get at a function's name as by doing

function nameOf(f) { if ('name' in f) { return f.name; } // Works on some interpreters var m = ('' + f).match(/^function\s+([^(\s]+)/); return m ? m[1] : void 0; }

I'm not sure how much of the existing code like this invokes toString directly or indirectly instead of using Function.prototype.toString.

Function proxy handlers could implement has('name') then there might not be a need for Function.prototype.toString to support this use case.

Under the current proposal, a trapping function proxy f can
virtualize all the following without problem:

'name' in f
f.name
'' + f
f.toString()

The only open issue is

Function.prototype.toString.call(f)

I would have ventured a guess that this isn't used in real code. But
having learned my lesson ;), I looked. What do we all think of <www.google.com/codesearch?hl=en&lr=&q=Function.prototype.toString.call+lang:javascript&sbtn=Search

?

Interesting. A few of these uses appear to be in JavaScript libraries
so they could be widespread. I see a few different uses:

  • To get the function name
  • To detect if something is a function at all (by seeing if the
    attempt to call it throws)
  • To check for function equality (likely a bogus check!)
  • To get argument names
  • Something else mysterious that I couldn't figure out (looking for
    calls to the Function constructor inside the function body?)

Note though that in the case of DOM emulation specifically, probably
most of these are not relevant. Looking at toString() on DOM methods
is usually done for one of the following reasons:

  1. To get the function name.
  2. To check for "[native code]" to verify that a native method hasn't
    been replaced by JavaScript code (seems like a fairly bogus check).

, Maciej

# Mike Samuel (16 years ago)

2009/12/12 Mark S. Miller <erights at google.com>:

On Sat, Dec 12, 2009 at 10:53 AM, Mike Samuel <mikesamuel at gmail.com> wrote:

On the interaction of Function.prototype.toString and function proxies, one use case is code that tries to get at a function's name as by doing

function nameOf(f) {  if ('name' in f) { return f.name; }  // Works on some interpreters  var m = ('' + f).match(/^function\s+([^(\s]+)/);  return m ? m[1] : void 0; }

I'm not sure how much of the existing code like this invokes toString directly or indirectly instead of using Function.prototype.toString.

Function proxy handlers could implement has('name') then there might not be a need for Function.prototype.toString to support this use case.

Under the current proposal, a trapping function proxy f can virtualize all the following without problem:     'name' in f     f.name     '' + f     f.toString() The only open issue is     Function.prototype.toString.call(f) I would have ventured a guess that this isn't used in real code. But having learned my lesson ;), I looked. What do we all think of www.google.com/codesearch?hl=en&lr=&q=Function.prototype.toString.call+lang:javascript&sbtn=Search?

So in your search results, prototype is extracting formal parameter names. argumentNames: function() { var names = Function.prototype.toString.call(this) .match(/^[\s(]function[^(](([^)]*))/)[1] .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; },