Proxy-for-array transparency issues

# Tom Van Cutsem (12 years ago)

Originally, direct proxies were specified such that a direct proxy would inherit the [[Class]] of the target object it wraps. The intent was for e.g. proxies-for-arrays to be recognized properly as arrays.

In ES6 the [[Class]] property is gone and the current draft instead uses wording such as "if O is an exotic Array object..."

My understanding is that a proxy for an array would not pass such a test, unless we explicitly state otherwise.

Allen, could you clarify your intent?

If proxies for arrays do not pass such tests, some built-ins behave in unexpected ways.

Here's the list of relevant built-ins, based on searching the ES6 spec for "is an exotic Array object":

  • Object.prototype.toString
  • Array.isArray
  • Array.prototype.concat
  • JSON.stringify
  • JSON.parse
  • ArrayBuffer.isView

If we don't make proxies-for-arrays work transparently, we get results such as:

var p = new Proxy( [1,2] , {} );

Object.prototype.toString.call(p) // "[object Object]", expected "[object Array]"
Array.isArray(p) // false, expected true
[0].concat(p) // [0,p], expected [0,1,2]
JSON.stringify(p) // "{\"0\":1,\"1\":2}", expected "[1,2]"

Note that none of these built-ins actually relies upon some structural array invariant. Passing in a proxy that has properties such as "length", "0", etc. works equally well.

It's worth noting that I hit upon these issues because users of my harmony-reflect shim, which are using direct proxies today in ES5, have reported them (see tvcutsem/harmony-reflect#13, tvcutsem/harmony-reflect#19). This adds some evidence that users expect the above built-ins to behave transparently w.r.t. proxies for their use cases. My library patches some of these built-ins to recognize my own emulated proxies, but this is just an ad hoc solution. ES6 users will obviously not be able to do this.

# Allen Wirfs-Brock (12 years ago)

On Jul 29, 2013, at 9:06 AM, Tom Van Cutsem wrote:

Hi,

Originally, direct proxies were specified such that a direct proxy would inherit the [[Class]] of the target object it wraps. The intent was for e.g. proxies-for-arrays to be recognized properly as arrays.

In ES6 the [[Class]] property is gone and the current draft instead uses wording such as "if O is an exotic Array object..."

My understanding is that a proxy for an array would not pass such a test, unless we explicitly state otherwise.

yes that's correct

Allen, could you clarify your intent?

What you are running into is very closely related to vthe private state access issues that led us to define DelegatingHandler/ForwardHandler, etc. The difference here is that you are concerned about external behavioral branding rather than internal implementation branding of the sort that occurs for private state access.

The legacy [[Class]] internal property conflated these two concepts. Sometimes it was used for to ensure that a built-in method was operating upon an instance that actually had the internal state or conformed to other implementation level invariants needed by the method. Other times, [[Class]] was tested for basic external behavioral classification purposes that don't really care at all about implementation level object invariants.

In most cases, the ES6 spec. language such as "if O is an exotic X object" or "does X have a [[XXX]] internal data property" works fine as a direct replacement of an ES5 [[Class]] test because there are a one-to-one correspond between a ES6 built-in that is represented by specific kind of exotic object or that has a specific internal data property and with a ES5 built-in with the corresponding [[Class]] value.

However, a proxy on such a built-in does not pass such a test. A Proxy object whose target is an Array is an exotic proxy object, not an exotic array object. It would be unsafe to allow such a proxy to be recognized an an exotic array regardless of whether it was done via the ES6 spec. language or via [[Class]]==Array if we still had it. A proxy for an array and an array is not likely to have the same implementation shape. A method that tests for an exotic array object might should be able to assume, for example, that its implementation depended encoding of the "length" property is present. Disaster would follow if the method tried to operate upon a proxy instance instead of an actual exotic array instance.

The general solution to this problem is to decouple the shape branding/testing from the behavior testing. This is already done a number of places in the ES6 spec to support subclassing flexibility.

For example, a number of the ES<=5 String method such as "replace" have different behavior depending upon whether an argument is a RegExp or a string. In the ES6 spec. the [[Class]]==RegExp test is replaced by an HasProperty test for @@isRegExp and and a form of double dispatching. This permits subclasses of RegExp (or even alternative regular expression engines that don't inherit from the built-in RegExp) to be used in that argument position. It may also be transparent to proxying (depending upon the handler).

In general, an case by case analysis needs to be done for each of the situations where [[Class]] is used in ES5 for behavioral checking to se if and how an alternative needs to be provided for ES6.

If proxies for arrays do not pass such tests, some built-ins behave in unexpected ways.

What's expected? Just because a proxy has an exotic array object as its target doesn't mean that is functions as an exotic array.

Here's the list of relevant built-ins, based on searching the ES6 spec for "is an exotic Array object":

  • Object.prototype.toString

As currently spec'ed. toString will invoke the HasProperty(@@toStringTag) of the proxy and if the result is true invoke the Get trap to retrieve the value used to construct the toString result. All of the new built-ins have @@toStringTag methods. I considered adding them for the built--ins that carry over from ES5. Then, if the @@toStringTag of Array.prototype is "Array" then Object.prototype.toString(new Proxy([ ], { })) should return "[Object Array]". However, this would be a breaking change for existing code explicitly wires their prototype chain to inherit from Array.prototype.

  • Array.isArray

We've discussed the meaning of this in the past and have agreed that it should be considered a test for exotic array-ness and in particular the length invariant. A Proxy whose target is an exotic array may or may not qualify. We could add a @@isArray tag, but I'm not convinced that it is really needed.

  • Array.prototype.concat

As currently spec'ed the [[Class]] test has been replaced with an exotic array test on the this value (supporting creating subclass instances) and a IsConcatSpreadable abstract operation (defined in terms of @@isConcatSpreadable) falling back to an exotic array test) used to decide whether to spread argument values.

To make concat spread a proxied array you would have to explicitly define the @@isConcatSpreadable property of the target object. To maintain legacy computability with objects inheriting from Array.prototype we can't just have include @@isConcatSpreadable on Array.prototype.

There may be other teaks we could do. But we talked about concat at last weeks meeting WRT Typed Arrays and concluded that concat is pretty bogus and it may be a turd that is not worth polishing.

  • JSON.stringify

There are two distinct use cases of [[Class]]==Array in the ES5 spec of this function. Both are currently in the ES6 spec. as exotic array tests. The first use case is to see if the "replaceer" argument is an white-list array. This could be special cased via a @@isJSONArray that would work through a proxy, but I dubious that the additional complexity is justified.

The other use case is when deciding whether to use { } or [ ] notation to encode the object. The exotic array test is the right one for ES5 compatibility. The appropriate JSON encoding of a Proxy that has an exotic array target is going to be application dependent. JSON.parse already has an extensibility hook (toJSON) that presumably should be sufficient to deal with such objects.

  • JSON.parse

I believe that this [[Class]]==Array (now is exotic array) test is only applied to objects created by evaluating the JSON text as if it was an object literal. Such objects will never be proxies.

  • ArrayBuffer.isView

yup. The use cases for isView aren't all that clear to me. It could be expressed a @@isView test if it has important use cases.

If we don't make proxies-for-arrays work transparently, we get results such as:

var p = new Proxy( [1,2] , {} );

Yes, and the only reason Array.prototype methods will work is because they have all been carefully defined to be generic and not depend upon any internal array state. The methods of most other built-ins will fail if you create an object in this manner because it defaults to delegating rather than forwarding.

Object.prototype.toString.call(p) // "[object Object]", expected "[object Array]" Array.isArray(p) // false, expected true [0].concat(p) // [0,p], expected [0,1,2] JSON.stringify(p) // "{"0":1,"1":2}", expected "[1,2]"

Note that none of these built-ins actually relies upon some structural array invariant. Passing in a proxy that has properties such as "length", "0", etc. works equally well.

see above...

It's worth noting that I hit upon these issues because users of my harmony-reflect shim, which are using direct proxies today in ES5, have reported them (see [1],[2]). This adds some evidence that users expect the above built-ins to behave transparently w.r.t. proxies for their use cases. My library patches some of these built-ins to recognize my own emulated proxies, but this is just an ad hoc solution. ES6 users will obviously not be able to do this.

They may expect this, but I don't see what generalizations we can make. Whether a proxy over a built-in is behaviorally substitutable for the built-in completely dependent upon the the definition of the specific proxy. I think this is just something that anybody who uses Proxy needs to be aware of.

# David Bruant (12 years ago)

Le 29/07/2013 20:41, Allen Wirfs-Brock a écrit :

The legacy [[Class]] internal property conflated these two concepts. Sometimes it was used for to ensure that a built-in method was operating upon an instance that actually had the internal state or conformed to other implementation level invariants needed by the method. Other times, [[Class]] was tested for basic external behavioral classification purposes that don't really care at all about implementation level object invariants.

In most cases, the ES6 spec. language such as "if O is an exotic X object" or "does X have a [[XXX]] internal data property" works fine as a direct replacement of an ES5 [[Class]] test because there are a one-to-one correspond between a ES6 built-in that is represented by specific kind of exotic object or that has a specific internal data property and with a ES5 built-in with the corresponding [[Class]] value.

Can all the [[Class]] replacement tests be passed by proxies in a way or another so that a proxy can impersonate a given "class" of exotic object? (most likely by having one of these objects as target)

Also, I fail to understand the difference between "if O is an exotic X object" and "if O.[[Class]] === X".

If proxies for arrays do not pass such tests, some built-ins behave in unexpected ways.

What's expected? Just because a proxy has an exotic array object as its target doesn't mean that is functions as an exotic array.

This suggests that the opposite could be true, that is that a proxy with any target might impersonates an array as long as it passes some tests. I wonder how much of a good idea this is.

The (granted implicit) model of "a proxy for exotic object X is seen by all algorithms as an exotic object X" feels simpler even if it means that a proxy might not act as an internal algorithm expects. In any case, regardless of how many tests a proxy passes, it has always a way to wrongly behave after having passed the test.

Here's the list of relevant built-ins, based on searching the ES6 spec for "is an exotic Array object":

  • Object.prototype.toString

As currently spec'ed. toString will invoke the HasProperty(@@toStringTag) of the proxy and if the result is true invoke the Get trap to retrieve the value used to construct the toString result. All of the new built-ins have @@toStringTag methods. I considered adding them for the built--ins that carry over from ES5. Then, if the @@toStringTag of Array.prototype is "Array" then Object.prototype.toString(new Proxy([ ], { })) should return "[Object Array]".

Excellent. It also means that any proxy that customizes its @@toStringTag could have O.p.toString return "[Object Array]", right?

However, this would be a breaking change for existing code explicitly wires their prototype chain to inherit from Array.prototype.

Saving existing code from proxies is a dead end in my opinion.

  • Array.isArray

We've discussed the meaning of this in the past and have agreed that it should be considered a test for exotic array-ness and in particular the length invariant. A Proxy whose target is an exotic array may or may not qualify.

I don't really understand what you're testing here to checking the length invariant. Can a proxy be written to pass this test? Can a proxy be written so that the value of this test changes over time?

  • JSON.stringify

There are two distinct use cases of [[Class]]==Array in the ES5 spec of this function. Both are currently in the ES6 spec. as exotic array tests. The first use case is to see if the "replaceer" argument is an white-list array. This could be special cased via a @@isJSONArray that would work through a proxy, but I dubious that the additional complexity is justified.

Maybe a test on whether the value is iterable? (so that replacer can also be a Set, etc.)

It's worth noting that I hit upon these issues because users of my harmony-reflect shim, which are using direct proxies today in ES5, have reported them (see [1],[2]). This adds some evidence that users expect the above built-ins to behave transparently w.r.t. proxies for their use cases. My library patches some of these built-ins to recognize my own emulated proxies, but this is just an ad hoc solution. ES6 users will obviously not be able to do this.

They may expect this, but I don't see what generalizations we can make. Whether a proxy over a built-in is behaviorally substitutable for the built-in completely dependent upon the the definition of the specific proxy.

Again, this seems to suggest that a proxy could pretend to be a Date to one algorithm, an Array to another and a RegExp to another. I'm not sure what good comes out of that.

# Brendan Eich (12 years ago)

David Bruant wrote:

Also, I fail to understand the difference between "if O is an exotic X object" and "if O.[[Class]] === X".

+1

What have we gained?

# Brandon Benvie (12 years ago)

On 7/29/2013 5:14 PM, Brendan Eich wrote:

David Bruant wrote:

Also, I fail to understand the difference between "if O is an exotic X object" and "if O.[[Class]] === X".

+1

What have we gained?

It seems to me like duck-typing based on internal properties was basically superseded by @@create and this change could be rolled back.

# Allen Wirfs-Brock (12 years ago)

On Jul 29, 2013, at 5:11 PM, David Bruant wrote:

Can all the [[Class]] replacement tests be passed by proxies in a way or another so that a proxy can impersonate a given "class" of exotic object? (most likely by having one of these objects as target)

Nope. We would have had the same problems if we had kept [[Class]]. In ES<=5.1 [[Class]] is used to conflate situational specific testing for a number of independent characteristics of objects. Some of those characteristics can not be transparently prloxied (for example this object reference directly refers a special object representation with some specific implementation dependent representation of some private state 0)

Also, I fail to understand the difference between "if O is an exotic X object" and "if O.[[Class]] === X".

There really isn't much. But getting rid of [[Class]] enables us to separate the previously conflated characteristics.

This suggests that the opposite could be true, that is that a proxy with any target might impersonates an array as long as it passes some tests. I wonder how much of a good idea this is.

One of the goals for ES6 proxies was to enable self-hosting of built-ins. In theory, there is no reason that a proxy couldn't be use buy an implementation to implement its "exotic array objects". However, because the spec. explicitly distinguishes "exotic array objects" from other built-in exotic object and from general proxies, such a host implementation would still have to have a way to identify the proxy-implemented exotic arrays as such and for test for them in every context where the spec. says an "exotic array object" is required. That branding test would be that implementations way of implementing "is O is an exotic Array object".

The (granted implicit) model of "a proxy for exotic object X is seen by all algorithms as an exotic object X" feels simpler even if it means that a proxy might not act as an internal algorithm expects.

memory safety hazard. Every place that checks for "is O an exotic X object" would have to have a subsequent "is O a Proxy whose target is an exotic X" and take different code paths. If you screw it up, you may have memory safety issues.

In any case, regardless of how many tests a proxy passes, it has always a way to wrongly behave after having passed the test.

The key things are hard dependencies between native code built-in methods that expect specific fixed object layouts and the actual instances of those objects.

Excellent. It also means that any proxy that customizes its @@toStringTag could have O.p.toString return "[object Array]", right?

right, but I forgot about step 20.e of Object.prototype.toString in the current draft. This tries to prevent using @@toStringTag to return any of the ES<6 era [[Class]] tags. There is past controversy about that step. Some think such aliased results should be allows (eg, the use case I described just above). Other think that it should throw in this case rather than the current hack in the spec. There is also an argument that permitting something other than a true exotic array (for example) to return "[object Array]]" could be a breaking change if such an object is encountered by some legacy code.

Saving existing code from proxies is a dead end in my opinion.

This has nothing to do with Proxies.

Consider:

var x = [__proto__: Array.prototype].

In ES<=5.1

Object.prototype.toString(x)  //returns "[object Object]"

If in ES6, Array.prototype has an built-in @@toStringTag property whose value is "Array" then:

Object.prototype.toString(x)  //returns "[object Array]"

I don't really understand what you're testing here to checking the length invariant.

It's an implementation internal test. It is what it means to be "an exotic array object". See 8.4.2 of the current ES6 draft for the complete specification of what it means to be an "exotic array object"

Can a proxy be written to pass this test?

no, not with portable ES code. The logic is within Array.isArray, not within the object it is applied to.

Can a proxy be written so that the value of this test changes over time?

no

Maybe a test on whether the value is iterable? (so that replacer can also be a Set, etc.)

That would be an extension to the current JSON.stringify functionality. Not implausible, but nobody has proposed any such changes for ES6.

Again, this seems to suggest that a proxy could pretend to be a Date to one algorithm, an Array to another and a RegExp to another. I'm not sure what good comes out of that.

What do you mean to "be a Date". The ES6 spec. defines Dateness very specifically. It is an ordinary object (ie, not a Proxy) that has a [[DateValue]] internal data property that can be recognized as such by an implementation. The only way the ES6 spec. provides for creating such an object is via the Date[[@@create]] method defined in 15.9.3.5 of the current draft. This method is inherited by subclass constructors so instances of subclasses of the Date constructor, by default, also will pass the Dateness test.

However, the this is not a very interesting test. It is only used to make sure that built-in Date.prototype methods that assume that they have direct access to a [[DateValue]] slot actually are operating upon such an object.

Application level JS code really should not be doing such pseudo-nominal type testing. At the application level, JS operates best using dynamic behavioral types. If an object has the methods specified for Date instances and those methods return the expected results, why do you case whether or not you are dealing with an instance of the original built-in Date or an instance of somebodies alternative Date implementation.

Similarly, the ES6 spec. is careful to make sure that an object that is an instance of an enhanced or alternative regular expression engine can be used anyplace a built-in RegExp object was previously required.

# David Bruant (12 years ago)

Le 30/07/2013 03:09, Allen Wirfs-Brock a écrit :

Nope. We would have had the same problems if we had kept [[Class]]. In ES<=5.1 [[Class]] is used to conflate situational specific testing for a number of independent characteristics of objects. Some of those characteristics can not be transparently prloxied (for example this object reference directly refers a special object representation with some specific implementation dependent representation of some private state 0)

By "this object reference", are you talking about the "this" keyword? Even then, I wouldn't be sure to understand. Which part of ES5.1 are you referring to? I believe a code snippet to explain would make things easier to understand.

There really isn't much. But getting rid of [[Class]] enables us to separate the previously conflated characteristics.

When you test for "if O is an exotic array object", what characteristic are you testing for, then? This still feels like branding to me.

One of the goals for ES6 proxies was to enable self-hosting of built-ins. In theory, there is no reason that a proxy couldn't be use buy an implementation to implement its "exotic array objects". However, because the spec. explicitly distinguishes "exotic array objects" from other built-in exotic object and from general proxies, such a host implementation would still have to have a way to identify the proxy-implemented exotic arrays as such and for test for them in every context where the spec. says an "exotic array object" is required. That branding test would be that implementations way of implementing "is O is an exotic Array object".

Self-hosted code is privileged and may have some power not described by the ES spec (and as far as I know, that's actually the case both for SpiderMonkey and V8 self-hosted code). So self-hosted code can distinguish an actual array from a proxy, that's not an issue. Non-privileged code being able to distinguish an array and a proxy for an array is more worrisome.

memory safety hazard. Every place that checks for "is O an exotic X object" would have to have a subsequent "is O a Proxy whose target is an exotic X" and take different code paths. If you screw it up, you may have memory safety issues.

That's an implementation hazard; not sure what your point is here. Note that the design of proxies (can only be new objects, are limited to an immutable {target, handler} data structure, stratified API, etc.) go a long way in preventing memory safety issues. It sounds reasonable to think that implementors will do the necessary rest of work to prevent these issues in already implemented algorithms. Actually, if the stratification is respected and there is no free bypass, there is no reason to have memory safety issues.

The key things are hard dependencies between native code built-in methods that expect specific fixed object layouts and the actual instances of those objects.

I don't think you're answering my point. All tests that methods can pass are pre-conditions. If a proxy can pass the test, it can fake it and then behave mistakenly. If a proxy can't pass the test, then user-land code can distinguish an object from its target. Something has to be given up. And giving up on indistinguishability would defeat the purpose of proxies (and the expectation as feedback on Tom's library suggests).

This has nothing to do with Proxies.

Consider:

oh ok. Sorry about that, I had completely misinterpreted your point here.

no, not with portable ES code. The logic is within Array.isArray, not within the object it is applied to.

If some code contains an Array.isArray test, it will behave differently if running against a membrane or normal objects. I don't think that's desirable. For membranes to be transparent, a proxy must be able to behave the exact same way its target would. Clearly if the logic of some built-in is within the built-in not within the object it is applied to, this is plain impossible.

What do you mean to "be a Date".

I wrote "pretend to be a Date". That's at least to the algorithms testing, no matter how they do it.

The ES6 spec. defines Dateness very specifically. It is an ordinary object (ie, not a Proxy) that has a [[DateValue]] internal data property that can be recognized as such by an implementation. The only way the ES6 spec. provides for creating such an object is via the Date[[@@create]] method defined in 15.9.3.5 of the current draft. This method is inherited by subclass constructors so instances of subclasses of the Date constructor, by default, also will pass the Dateness test.

Same problem than above about membrane transparency.

Aside, but related: instead of passing a target (an already built object), it might be an interesting idea to have a Proxy constructor that takes an @@create so that the target passes the @@create-related tests.

# Allen Wirfs-Brock (12 years ago)

On Jul 29, 2013, at 7:04 PM, David Bruant wrote:

By "this object reference", are you talking about the "this" keyword?

no special "this" here. I'm just saying that at the implementation level, when you are doing something with an object value what you really have is a "reference" (ie a pointer) to a heap allocated data structure. If the implementation code is going to do something that depends on the data structures actual memory layout, then you better be sure that you have a pointer to the actual data structure you expect.

Even then, I wouldn't be sure to understand. Which part of ES5.1 are you referring to? I believe a code snippet to explain would make things easier to understand.

In ES5 some of this is subtle, for example in the introduction to 15.9.5 (Properties of Date Prototuype Object) is this paragrpah:

In following descriptions of functions that are properties of the Date prototype object, the phrase “this Date object” refers to the object that is the this value for the invocation of the function. Unless explicitly noted otherwise, none of these functions are generic; a TypeError exception is thrown if the this value is not an object for which the value of the [[Class]] internal property is "Date". Also, the phrase “this time value” refers to the Number value for the time represented by this Date object, that is, the value of the [[PrimitiveValue]] internal property of this Date object.

and each Date.prototype method starts with a step like:

  1. Let t be this time value.

What this is really saying is that this method only works on object instances that have the internal layout created by the Date constructor.

For a broader discussion of the use of [[Class]] in ES5 see docs.google.com/document/d/1sSUtri6joyOOh23nVDfMbs1wDS7iDMDUFVVeHeRdSIw/edit?hl=en&authkey=CI-FopgC&pli=1

When you test for "if O is an exotic array object", what characteristic are you testing for, then? This still feels like branding to me.

It is exactly branding, but branding for the specific characteristics that are defined ES6 clause 8.4.2. It doesn't include other things that [[Class]] was sometimes used for.

Self-hosted code is privileged and may have some power not described by the ES spec (and as far as I know, that's actually the case both for SpiderMonkey and V8 self-hosted code).

I'm not talking about existing self-hosted code that uses mechanisms other than Proxy. Some self hosted code might be privileged but that isn't necessarily the case for everything that is self hosted. For example, there is nothing that would need to be particularly privileged about an implementation of Set that is implemented by internally using a Map. You wouldn't even need to use a Proxy for that.

So self-hosted code can distinguish an actual array from a proxy, that's not an issue. Non-privileged code being able to distinguish an array and a proxy for an array is more worrisome.

I go the other way, in general there should be nothing special about "self-hosting code". Perhaps the phrase is even misleading. What I'm really saying that there should be no particular reason that anything other than the most primitive elements of the the ES built-ins can't be expressed in ES code.

A proxy with an Array as its target is not an Array. Whether such an object is substitutable for an Array instance is high dependent on the details of its handler and other aspects of its implementation. Also please see harmony:virtual_object_api which address related issues. In general, you can't assume that Proxy(x, {}) is substitutable for x. It just doesn't work that way.

That's an implementation hazard; not sure what your point is here.

The major of such test in the spec. are abstracting over such implementation hazards. That's why they are there. Most of the other uses are simply for backwards compatibility with spec. legacy usages.

Note that the design of proxies (can only be new objects, are limited to an immutable {target, handler} data structure, stratified API, etc.) go a long way in preventing memory safety issues. It sounds reasonable to think that implementors will do the necessary rest of work to prevent these issues in already implemented algorithms.

No, the design actually creates potential safety issues that must be carefully guarded against. For example, the default delegation semantics for [[Invoke]] (it would be the same if [[Get]]/[[Ca]] is used) calls a method on the target object with the proxy object as its this value. If the target object is exotic or has implementation specific private state (eg, [[DateValue]]) the this value (the Proxy) is the wrong kind of object for the method to operate upon. Any implementation level optimization must include some sort of guard.

Actually, if the stratification is respected and there is no free bypass, there is no reason to have memory safety issues.

Don't know what you mean by free bypass, but it fact that is not a bad description of the default delegation semantics.

I don't think you're answering my point. All tests that methods can pass are pre-conditions. If a proxy can pass the test, it can fake it and then behave mistakenly. If a proxy can't pass the test, then user-land code can distinguish an object from its target.

Here is a simple way Proxies fail your expectations.

let brandRegistry = new WeakSet();
class Branded {
   [@@create]() {
       let obj = super();
       brandRegistry.add(obj);
       return obj
   }
   doSomething() {
       if (! Branded.isBranded(this)) throw TypeError("wrong kind of object");
       /...
    }
   static isBranded(obj) {
      return brandRegistry.has(obj)
    }
}

let obj = new Branded;
let pObj = new Proxy (obj, {P });

Branded.isBranded (obj);//true
Branded.isBranded(pObj); //false

obj.doSomething();  //returns
pObj.doSomething();  //throws

None of the has anything to do with the presence of absence of the ES<=5.1 [[Class]] internal property. Instead it is much more fundamental to the semantics of Proxy.

Something has to be given up. And giving up on indistinguishability would defeat the purpose of proxies (and the expectation as feedback on Tom's library suggests).

You suggestion is? My point is ES internal branding, whether you call it "check for exotic XXX object", "check for the[[XXX]] internal data property" or check if [[Class]] == "Array" h does not work with the Direct Proxy default handler semantics.

If some code contains an Array.isArray test, it will behave differently if running against a membrane or normal objects. I don't think that's desirable. For membranes to be transparent, a proxy must be able to behave the exact same way its target would. Clearly if the logic of some built-in is within the built-in not within the object it is applied to, this is plain impossible.

It depends upon the type of handler you use for the Proxy's that are form your membrane. The default delegating handler behavior isn't what you want. But a ForwardingHanlder should work for you. At least that's what MarkM and TomVC tell me.

I wrote "pretend to be a Date". That's at least to the algorithms testing, no matter how they do it.

My question still stands. What do you mean when you say "x is a Date". What characteristics of Date are you actually trying to test. Is this a Date according to what you have in mind:

let obj = new Date();
obj.__proto__ = null;

Same problem than above about membrane transparency.

This one I have specifically discussed with Tom and Mark. It depends upon using the right kind of handlers for you membrane.

Aside, but related: instead of passing a target (an already built object), it might be an interesting idea to have a Proxy constructor that takes an @@create so that the target passes the @@create-related tests.

I don't think is makes any different. That's not the source of the problems.

new Proxy(new Array() , { })

has a target that is an exotic array object created via Array[[@@create]] the problem is that the Proxy (and it is what carries identify) is not can not be an exotic array object.

# David Bruant (12 years ago)

2013/7/30 Allen Wirfs-Brock <allen at wirfs-brock.com>

no special "this" here. I'm just saying that at the implementation level, when you are doing something with an object value what you really have is a "reference" (ie a pointer) to a heap allocated data structure. If the implementation code is going to do something that depends on the data structures actual memory layout, then you better be sure that you have a pointer to the actual data structure you expect.

This apply to Date and the "time value", but I fail to see why this doesn't apply to every object and every property. For instance, every object is expected to have a [[Prototype]] object (at least in the ES5 era), which most likelty had a reserved slot in current implementations. Proxies challenge this, because they don't have a [[Prototype]], so the same sort of "memory safety hazard" apply for [[Prototype]]. But I'm not too worry about that. I imagine implementors are well-aware of how deeply proxies are to integrate (retrofit!) in the object model. They'll figure it out for Date objects as well.

What this is really saying is that this method only works on object instances that have the internal layout created by the Date constructor.

There is no "really saying". There is saying, there are implementations that rely on this saying of the spec and since the saying is changing (admittedly somewhere else in the spec), they'll have to adapt their implementation to take into account the new sayings. I'm not sure worrying about implementations is helpful in this debate. This is a low-level feature. A good test suite applying built-in algorithms to proxies will go a long way preventing implementation issues.

For a broader discussion of the use of [[Class]] in ES5 see docs.google.com/document/d/1sSUtri6joyOOh23nVDfMbs1wDS7iDMDUFVVeHeRdSIw/edit?hl=en&authkey=CI-FopgC&pli=1

Cases are annoyingly numerous, but finite. A test suite can go a long way. I heard the test suite is moving to Github :-) Only missing is an extensive doc of the test framework.

It is exactly branding, but branding for the specific characteristics that are defined ES6 clause 8.4.2. It doesn't include other things that [[Class]] was sometimes used for.

If the intent behind branding is "internal memory layout", I argue that this is implementation details leaking into abstraction and that it's a bad idea especially as it serves no purpose other than memory safety issues that might happen in the transition era between the "before proxy" world and the "after proxy" world.

A proxy with an Array as its target is not an Array. Whether such an object is substitutable for an Array instance is high dependent on the details of its handler and other aspects of its implementation. Also please see harmony:virtual_object_api which address related issues.

This is not enough. this-binding ("proxy.getMonth()" when the target is a Date) is only half of the story. The other half remains unadressed ("Date.prototype.getMonth.call(proxy)"). Exotic objects aren't always used as "this" value. This is exactly Tom's feedback actually. We need the same solution for both cases (this and non-this usage of methods).

The major of such test in the spec. are abstracting over such implementation hazards. That's why they are there.

Let's let implementors deal with implementation issues. Let's just make sure the spec isn't impossible or too high barrier to implement. A test suite will do the rest. Let's care only for tests that make sense when authoring. Implementors are free to dive in here to say I'm wrong and why.

No, the design actually creates potential safety issues that must be carefully guarded against. For example, the default delegation semantics for [[Invoke]] (it would be the same if [[Get]]/[[Ca]] is used) calls a method on the target object with the proxy object as its this value. If the target object is exotic or has implementation specific private state (eg, [[DateValue]]) the this value (the Proxy) is the wrong kind of object for the method to operate upon. Any implementation level optimization must include some sort of guard.

Again, this is an implementation concern. The implementors needs you describe are in the way of authoring (based on the feedback brought to the list).

Here is a simple way Proxies fail your expectations.

...

None of the has anything to do with the presence of absence of the ES<=5.1 [[Class]] internal property. Instead it is much more fundamental to the semantics of Proxy.

A target and a proxy have a different object reference. If the difference can be kept to that, transparent membranes can be implemented, even your Branded class. Any code can expect objects with 2 identities to behave differently anyway.

You suggestion is? My point is ES internal branding, whether you call it "check for exotic XXX object", "check for the[[XXX]] internal data property" or check if [[Class]] == "Array" h does not work with the Direct Proxy default handler semantics.

Is there a handler semantics with which it can fully work? That's all is needed.

It depends upon the type of handler you use for the Proxy's that are form your membrane. The default delegating handler behavior isn't what you want. But a ForwardingHanlder should work for you. At least that's what MarkM and TomVC tell me.

After Tom initial post on this thread? Apparently not based on feedback Tom brought to the list. He had to patch the built-ins for cases where the argument of a built-in is an exotic object of a given sort.

My question still stands. What do you mean when you say "x is a Date". What characteristics of Date are you actually trying to test.

oh ok. Then the important part of my sentence is "to one algorithm" (that implicitly repeats to the other part listed exotic object types). I don't care about the exact definition used in the built-in algorithms. It just doesn't feel clean if an object can impersonate a Date (whatever definition is used) in a built-in algorithm and a RegExp (whatever definition is used) in another built-in algorithm. Maybe I just need to get over this point. That's not the major concern here.

Is this a Date according to what you have in mind:

let obj = new Date();
obj.__proto__ = null;

The definition used in most spec algorithms is "has a this time value", so I guess this passes.

This one I have specifically discussed with Tom and Mark. It depends upon using the right kind of handlers for you membrane.

This thread feedback brings new data and it seems that the current handler offering isn't enough (and I understand can't be).

# Tom Van Cutsem (12 years ago)

tl;dr:

I would argue that Array.isArray should return true for proxies-for-arrays. The other built-ins are less crucial and could stay the way they are.

Summarizing the previous discussion:

  • I agree that it is not safe to generalize: a proxy for an X is not in general substitutable for X.
  • It follows that having proxies-for-X pass all tests of the form "if O is an exotic X" is undesirable.
  • Hence, we should look at each of the tests and decide on a case-by-case basis whether proxies can be permitted.

For the specific case of arrays, let's go over the list again:

  • Array.isArray

We've discussed the meaning of this in the past and have agreed that it should be considered a test for exotic array-ness and in particular the length invariant. A Proxy whose target is an exotic array may or may not qualify. We could add a @@isArray tag, but I'm not convinced that it is really needed.

David's point about membranes being broken if Array.isArray(membranedArray) returns false is a valid point. It means code outside a membrane will observe all arrays inside the membrane as ordinary objects. This breaks transparency quite badly.

Even with a custom proxy handler, membranes would not be able to fix this, unless the membrane abstraction itself patches Array.isArray to recognize its own membrane wrappers. The problem is not that array generics won't work on wrapped arrays, it's that wrapped arrays have lost their "isArray" brand.

As far as I can tell, the only real array invariant is that Array.isArray(obj) currently implies that obj must have a non-configurable data property named "length" that holds a number.

But we're in luck: since "length" is non-configurable on arrays, and since direct proxies were carefully specced such that they could not violate non-configurability of target properties, a proxy-for-array will be obliged to return a valid "length" value as well. Hence having Array.isArray(proxyForArray) return true makes sense.

Having a user-customizable @@isArray tag seems a bridge too far because then we'd lose the invariant that Array.isArray tests for. But having Array.isArray return true for exotic arrays and only proxies whose target is an exotic array seems both safe and useful.

  • Array.prototype.concat

As currently spec'ed the [[Class]] test has been replaced with an exotic array test on the this value (supporting creating subclass instances) and a IsConcatSpreadable abstract operation (defined in terms of @@isConcatSpreadable) falling back to an exotic array test) used to decide whether to spread argument values. To make concat spread a proxied array you would have to explicitly define the @@isConcatSpreadable property of the target object. To maintain legacy computability with objects inheriting from Array.prototype we can't just have include @@isConcatSpreadable on Array.prototype. There may be other teaks we could do. But we talked about concat at last weeks meeting WRT Typed Arrays and concluded that concat is pretty bogus and it may be a turd that is not worth polishing.

Sorry, I was looking at jorendorff's online ES6 draft which did not include this extensibility hook yet. It seems the @@isConcatSpreadable hook is sufficient for proxies-for-arrays to allow themselves to be spread. Good!

  • JSON.stringify

There are two distinct use cases of [[Class]]==Array in the ES5 spec of this function. Both are currently in the ES6 spec. as exotic array tests. The first use case is to see if the "replaceer" argument is an white-list array. This could be special cased via a @@isJSONArray that would work through a proxy, but I dubious that the additional complexity is justified.

I agree. This would be a rare use case, and the issue is broader than just proxies anyway (e.g. allowing any iterable).

The other use case is when deciding whether to use { } or [ ] notation to encode the object. The exotic array test is the right one for ES5 compatibility. The appropriate JSON encoding of a Proxy that has an exotic array target is going to be application dependent. JSON.parse already has an extensibility hook (toJSON) that presumably should be sufficient to deal with such objects.

I didn't think of the toJSON() hook. That should work, at the cost of giving up some transparency since [].toJSON returns undefined while proxyForArray.toJSON would need to return a function.

  • JSON.parse

I believe that this [[Class]]==Array (now is exotic array) test is only applied to objects created by evaluating the JSON text as if it was an object literal. Such objects will never be proxies.

Agreed.

  • ArrayBuffer.isView

yup. The use cases for isView aren't all that clear to me. It could be expressed a @@isView test if it has important use cases.

I'm not familiar enough with ArrayBuffers to understand the consequences. By analogy with Array.isArray, if a proxy-for-arraybuffer automatically upholds all observable invariants of ArrayBuffers, then arguably the test should return true for proxies. What would be the observable invariants of an ArrayBuffer?

# André Bargull (12 years ago)

I'm not familiar enough with ArrayBuffers to understand the consequences. By analogy with Array.isArray, if a proxy-for-arraybuffer automatically upholds all observable invariants of ArrayBuffers, then arguably the test should return true for proxies. What would be the observable invariants of an ArrayBuffer?

ArrayBuffer.isView() should not return true for exotic array objects. That was just a spec bug, fixed in rev16 (ecmascript#1570). As currently spec'ed, ArrayBuffer.isView() only returns true for TypedArray objects and DataView objects (both kind of objects have got a [[ViewedArrayBuffer]] internal data property). And there are no observable invariants which can be used for this case.

# David Bruant (12 years ago)

2013/7/30 Tom Van Cutsem <tomvc.be at gmail.com>

tl;dr:

I would argue that Array.isArray should return true for proxies-for-arrays. The other built-ins are less crucial and could stay the way they are.

What about WeakMaps? Maps? Sets? How is the line drawn between "crucial" and "less crucial"? How is going to be decided whether new exotic object types are crucial? I feel the line is very context-specific. I can easily agree that arrays are more used than other exotic objects for now (WeakMap/Map/Set may change that, we'll see), but a Date enhancing library will want to work with proxies (proxies it does not have necessarily created), and it will become crucial for proxies-on-dates to work there. Importance is relative to context and doing a "common denominator" analysis to assess importance can be detrimental for specific use cases.

"I agree that it is not safe to generalize: a proxy for an X is not in general substitutable for X." => How much unsafe? What are the risks? (implementation concerns don't apply. Let's solve this with tests. Implementors can also add assertions, very much as they do today anyway 1) A built-in algorithm will misbehave? I think that all of them don't have side-effects beyond the objects being acted upon. Worst case, it will throw, which is a regular risk anyway for a lot of built-in algorithms anyway. This risk already exists if an Array.prototype method is applied to an object with funky getter/setters (for "length" or numerical properties).

# Tom Van Cutsem (12 years ago)

2013/7/30 David Bruant <bruant.d at gmail.com>

What about WeakMaps? Maps? Sets?

I meant the specific list of built-in methods on Array, apart from isArray, are less crucial.

We'll have to evaluate the other types you mention on a case-by-case basis as well.

How is the line drawn between "crucial" and "less crucial"? How is going to be decided whether new exotic object types are crucial? I feel the line is very context-specific.

I think that's essentially Allen's point: with built-ins, you arrive at rock-bottom, at the edges of the platform. You cannot easily generalize anymore. We need to look at each of these "is an exotic X" tests on a case-by-case basis and use our judgment whether it makes sense for proxies to pass the test.

"I agree that it is not safe to generalize: a proxy for an X is not in general substitutable for X." => How much unsafe? What are the risks?

I don't think a general answer exists, because each of these built-ins has its own idiosyncratic set of invariants.

# Allen Wirfs-Brock (12 years ago)

On Jul 30, 2013, at 1:25 AM, David Bruant wrote:

This apply to Date and the "time value", but I fail to see why this doesn't apply to every object and every property. For instance, every object is expected to have a [[Prototype]] object (at least in the ES5 era), which most likelty had a reserved slot in current implementations. Proxies challenge this, because they don't have a [[Prototype]], so the same sort of "memory safety hazard" apply for [[Prototype]]. But I'm not too worry about that. I imagine implementors are well-aware of how deeply proxies are to integrate (retrofit!) in the object model. They'll figure it out for Date objects as well.

It's important to be clear here that things Date's "time value" slot, the [[DateValue]] internal data property, are not actual "Properties". They represent private internal per instance state that is not accessible through any of the normal property access mechanisms. It is an unfortunate accident of history that we use the word "property" in talking about them. I've considered using replacing "internal data property" with another term. Maybe I should just go ahead and change over to calling "private data slots".

So far in ES<=6 dealing with such private data slots is something that could be treated in a relatively ad hoc manner within the ES spec. and by implementations. But in ES7 we really want and need user definable per instance private data. The issues we are encountering here are a precursor of the issue we will see down the line and what we do now may constrain what we can do later.

[[Prototype]] and other aspects of the basic ES object model are specified as part of the MOP (section 8.1.6.2) and mirrored by the Proxy handler interface. Every object whether ordinary, exotic, built-in, host-provided, etc. must expose the MOP interface to the implementation. Supporting that interface is what it means to be an ES object. "private data slots" are not part of the MOP, they represent an implementation specific side-channel.

Strictly speaking, the MOP accesses the inheritance prototype chain via the [[GetInheritance]]/[[SetInheritance]] MOP operation. [[Prototype]] is just an internal data slot that ordinary objects use as backing store for those operations. Exotic objects do not necessary have a [[Prototype]] internal data slot. Proxy exotic objects and the currently spec. for Symbol exotic objects are examples of objects that don't need a [[Prototype]] internal data property.

Cases are annoyingly numerous, but finite. A test suite can go a long way. I heard the test suite is moving to Github :-) Only missing is an extensive doc of the test framework.

Tests are normative and you can't write a valid test suite in the absence of an accurate specification.

This is not enough. this-binding ("proxy.getMonth()" when the target is a Date) is only half of the story. The other half remains unadressed ("Date.prototype.getMonth.call(proxy)"). Exotic objects aren't always used as "this" value. This is exactly Tom's feedback actually. We need the same solution for both cases (this and non-this usage of methods).

I also raised this objection and it is what led to the recent update to the virtual object API referenced above. What that change does is make it easier for ES programmer to create proxies that behave in this manner.

But, in the end we could not come up with a solution for the Date.prototype.getMonth.call(proxy) issue. The [[Invoke]] MOP operation/trap was added to minimize the actual occurrence of this scenario. You can't use 'call' to force a built-in to operate upon the wrong kind of object.

Let's let implementors deal with implementation issues. Let's just make sure the spec isn't impossible or too high barrier to implement. A test suite will do the rest. Let's care only for tests that make sense when authoring. Implementors are free to dive in here to say I'm wrong and why.

ES spec. largely exists to constrain how implementors deal with such issue in order to guarantee that different implementations have observably identical behavior.

I think you are glossing over real issues that require real design thinking and which probably can't be addressed until ES7. With no offense to TomVC (who does the best job of any of our contributors in producing sound spec. material), I think the original Proxy strawman treatment of [[Class]] was gloss.

I complete disagree WRT tests. Tests written without reference to some normative specification are just a reflection of how their developer thinks the system should work.

Is there a handler semantics with which it can fully work? That's all is needed.

The ForwardingHandler semantics is close to what you want. But it still doesn't work the way you would like for direct "call" invocation or for things like Array.isArray. The base issue for either of these is that they don't indirect through the proxy handler and hence the handler doesn't have an opportunity to replace the proxy reference with the target reference.

The definition used in most spec algorithms is "has a this time value", so I guess this passes.

Yes, it is sufficient for the spec algorithms which is a safety check that is only making sure that you don't apply a Date method to an object that doesn't have the required implementation level time value slot.

But you are talking about the application layer . It is easy to say you want a way to "test if an object is a Date" or "is an Array". But what do you mean by those concepts at the application level. The ES spec. internally is able to make such tests, conforming to the internal invariants it needs to maintain. But those invariants aren't necessarily what you are asking for at the application level.

At the root of this thread is the question of what does Array.isArray actually test and what are its application level use cases.. Array.isArray was a new method that was added in ES5. It wasn't added as a test to see if an object conformed to some Platonic Ideal of array-ness. It was specifically a reliable iframe-independent test for instances of the Array built-in constructor. I'm not really sure whether that is actually a very important use case, but that was the specific use case it was designed for.

Do you have new use cases, that motivate the design of an enhanced Array.isArray?

It would be quite easy to extend Array.isArray(obj) so that it delegated back to obj with a @@areYouAnArray method such as we are doing with @@isRegExp in certain places. However, if we do that you loose support for the original Array.isArray use case because any object could decide to return true from Array.isArray.

This thread feedback brings new data and it seems that the current handler offering isn't enough (and I understand can't be).

yup

# David Bruant (12 years ago)

Le 30/07/2013 18:57, Allen Wirfs-Brock a écrit :

So far in ES<=6 dealing with such private data slots is something that could be treated in a relatively ad hoc manner within the ES spec. and by implementations. But in ES7 we really want and need user definable per instance private data. The issues we are encountering here are a precursor of the issue we will see down the line and what we do now may constrain what we can do later.

I could not agree more. Should proxies be deferred waiting for private data, then? Or, maybe they can be put in quarantaine in some way. For instance, making the constructor throw if the target isn't a regular object. That's for the time being until the "private state" (whether "private internal slots" or user data) is sorted out. A max-min sort of proxy, I guess.

Tests are normative and you can't write a valid test suite in the absence of an accurate specification.

I was answering to your concern about built-in implementations and memory safety hazard. If a test suite contains a good share of tests to exercise how built-ins react when passed proxies as arguments (including 'this'), this would provide some level of confidence that the implementations aren't too memory unsafe. What the tests exactly returns will of course have to be determine by an accurate spec, no doubt here. I was thinking of tests as a coverage method here, not as a conformance tool.

I also raised this objection and it is what led to the recent update to the virtual object API referenced above. What that change does is make it easier for ES programmer to create proxies that behave in this manner.

But, in the end we could not come up with a solution for the Date.prototype.getMonth.call(proxy) issue. The [[Invoke]] MOP operation/trap was added to minimize the actual occurrence of this scenario. You can't use 'call' to force a built-in to operate upon the wrong kind of object.

The introduction of [[invoke]] in this particular context might be an instance of "may constrain what we can do later". Specifically, it may collide and feel duplicate if a more generic solution is found for Date.prototype.getMonth.call(proxy); a more "useful" example of which is Set.call(proxy).

ES spec. largely exists to constrain how implementors deal with such issue in order to guarantee that different implementations have observably identical behavior.

How did the discussion shift from "memory safety issue" to "observably identical behavior"? In any case, I agree with you.

I think you are glossing over real issues that require real design thinking and which probably can't be addressed until ES7.

I think I understand the real issues :-) I don't have a idea to solve them and even less an idea TC39 would agree on, but I understand them. I feel that the [[invoke]] proposal is an attempt to get the maximum out of proxies without addressing them and I wonder whether it would be better to wait.

I complete disagree WRT tests. Tests written without reference to some normative specification are just a reflection of how their developer thinks the system should work.

Again, I thought of tests as a coverage tool more than a conformance one (though obviously, testing conformance along the way is obviously a nice property, but has to wait for an actual spec)

The ForwardingHandler semantics is close to what you want.

The part I'm being insistent on is the delta between what this handler enables and what it does not. The use of 'this' has been special-cased. I understand the biais that leads to this decision, but I don't think it's a good rationale. I think it's an equivalent biais than the one of the original Proxy design where the [[prototype]] was passed to the proxy.create function as if the [[Prototype]] internal property was that special in the MOP. 'this' is the 0th argument of functions (and .call shifts the order making this statement false in absolute terms), there is no good reason to give it a special treatment.

But it still doesn't work the way you would like for direct "call" invocation or for things like Array.isArray. The base issue for either of these is that they don't indirect through the proxy handler and hence the handler doesn't have an opportunity to replace the proxy reference with the target reference.

Wouldn't replacing the proxy reference for the target reference be violating stratification? Per exotic type traps would solve the issue generically, I think.

Do you have new use cases, that motivate the design of an enhanced Array.isArray?

I cited membrane transparency a couple of times. Tom seems to agree. Others have opinions?

It would be quite easy to extend Array.isArray(obj) so that it delegated back to obj with a @@areYouAnArray method such as we are doing with @@isRegExp in certain places. However, if we do that you loose support for the original Array.isArray use case because any object could decide to return true from Array.isArray. ...

If, for instance, you have an "areYouAnArray" trap that only works if the target is an Array (recursive definition with actual arrays as base case), then Array.isArray isn't spoofable by any object. Mark and Tom once used a expression about direct proxies. Something like "invariant forwarding" or something like that, where the target imposes restrictions on the handler. This could be extended to exotic objects by adding per exotic type only traps.

This thread feedback brings new data and it seems that the current handler offering isn't enough (and I understand can't be).

yup

Glad we agree here :-p

# Allen Wirfs-Brock (12 years ago)

On Jul 30, 2013, at 12:40 PM, David Bruant wrote:

Wouldn't replacing the proxy reference for the target reference be violating stratification?

It depends upon the use for the proxy. There is no stratification if the Proxy is being used to implement the a virtual object.

Per exotic type traps would solve the issue generically, I think.

It might for ES6 and for this use case. But it doesn't generalize to user defined classes with private state. It also does solve the problem for identity based testing. EG, using a WeakMap for branding or holding private state. The Proxy object and its target object are not the same map key.

I cited membrane transparency a couple of times. Tom seems to agree. Others have opinions?

I was really asking about purpose of application level Array.isArray tests. What is the application really asking when it uses that test? Is it really asking "array-like"? What counts as "array-like"?

If, for instance, you have an "areYouAnArray" trap that only works if the target is an Array (recursive definition with actual arrays as base case), then Array.isArray isn't spoofable by any object. Mark and Tom once used a expression about direct proxies. Something like "invariant forwarding" or something like that, where the target imposes restrictions on the handler. This could be extended to exotic objects by adding per exotic type only traps.

Let me reinterpret this as as request for a [[GetNomimalType]] trap. What are the set of expected values? What happens if the handler lies? What could somebody do with the result?

# Till Schneidereit (12 years ago)

On Tue, Jul 30, 2013 at 10:19 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

I was really asking about purpose of application level Array.isArray tests. What is the application really asking when it uses that test? Is it really asking "array-like"? What counts as "array-like"?

My guess is: whether the generic Array methods work for this object. Which is a sensible thing to want to know, and which, imo, Array.isArray seems to provide, if you don't know better.

# Allen Wirfs-Brock (12 years ago)

On Jul 30, 2013, at 2:21 PM, Till Schneidereit wrote:

My guess is: whether the generic Array methods work for this object. Which is a sensible thing to want to know, and which, imo, Array.isArray seems to provide, if you don't know better.

In which case, we are talking about bogus tests because that isn't really what Array.isArray determines and the generic Array methods will work (to some degree or another) on any object.

We could define a new predicate Array.isArrayLike with a proxy friendly extensions mechanism (@@isArrayLike). It wouldn't be the same test as Array.isArray and the result would be more a statement of intent than an absolute guarantee.

Would anybody use it? Would it's existence be enough to eliminate the Array.isArray/proxy concern?

# David Bruant (12 years ago)

Le 30/07/2013 22:19, Allen Wirfs-Brock a écrit :

On Jul 30, 2013, at 12:40 PM, David Bruant wrote:

Wouldn't replacing the proxy reference for the target reference be violating stratification?

It depends upon the use for the proxy. There is no stratification if the Proxy is being used to implement the a virtual object.

Per exotic type traps would solve the issue generically, I think.

It might for ES6 and for this use case. But it doesn't generalize to user defined classes with private state.

Spent 2 hours on this email refining a proposal. It started with the idea of per exotic type traps. Then moved to user-defined traps. Then to something more generic:

The problem we have is that some function have particular expectations on some of their arguments (including 'this') and proxies may violate these expectations. What if functions could "defend against" proxies by encouraging them to unwrap? new trap: "defensiveFunctionCall"

 var f = makeDefensiveFunction(a => a.a);
 var o = { a : 37 };
 f(o); // 37

 var p = new Proxy(o, {
     defensiveFunctionCall: (target, defensiveFunction, position) => {
         return; // means agreement for the target to be passed
         // to defensiveFunction as position-th argument
         // throw to disagree
     }
 });

 f(p) // calls the defensiveFunctionCall trap.
 // the proxy chooses to reveal its target to the function by returning
 // the function is called with the target as argument
 // 37 is returned

Class syntax could make methods defensive against proxies by default.

 class Secretive{
     private secret = String(Math.random()).substr(2)

     public getPartial(){
         return this.secret.substr(0, 3);
     }

     public changeSecret(){
         this.secret = String(Math.random()).substr(2);
     }

 }

 var s = new Secretive();
 console.log(s.getPartial()) // 3 digits

 var p = new Proxy(s, {
     defensiveFunctionCall: (target, defensiveFunction, position) => {
         return;
     }
 });
 console.log(p.getPartial()) // get trap, getPartial function is returned.
 // getPartial is about to be called with the p as 'this' (0th arg)
 // because it's defensive, the proxy's defensiveFunctionCall trap is called
 // return from the trap means agreement
 // the getPartial method is called on the actual instance, not the proxy
 // thus, the secret is never revealed to the proxy

What this takes is for the proxy to reveal its target. This shouldn't be a problem for built-ins as they won't leak the target, so they can all be "defensive against proxies" by default, I think (beware of chaining?). A proxy for a Secretive (user-defined class with private state) has no reason to hide its target to a Secretive method if the target is a Secretive instance (because the class constructor created the target).

Please note that I have just described a new primitive that allows a defense against Sam's attack 1.

I imagine I've been overlooking details, but here is a generic solution that enable functions to defend against proxies (unwrapping the proxy), while providing a mechanism for proxies to keep their integrity (throwing in the trap). In the case of a function against a proxy, one has to give up something. Either the function isn't defensive and lets the proxy "visit its guts" or the proxy lets the function see its guts (the target). But it's not possible for both to keep their integrity while the function runs against the proxy. And maybe that's a good trade-off?

It also does solve the problem for identity based testing. EG, using a WeakMap for branding or holding private state. The Proxy object and its target object are not the same map key.

Is there any expectation to be able to solve identity-based branding with proxies?

I was really asking about purpose of application level Array.isArray tests. What is the application really asking when it uses that test? Is it really asking "array-like"? What counts as "array-like"?

oh ok, sorry about that. Then no, I don't think I have a new use case that motivates the design of an enhanced Array.isArray.

Let me reinterpret this as as request for a [[GetNomimalType]] trap.

That's not what I meant (I hesitated to use this example and I shouldn't have, sorry). I was bouncing on your @@areYouAnArray. This is a request for a [[thisTimeValue]] trap on Dates and a [[MapData]] trap (actually, maybe it's 4 traps) for Maps, etc.

# Till Schneidereit (12 years ago)

On Wed, Jul 31, 2013 at 12:19 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

My guess is: whether the generic Array methods work for this object. Which is a sensible thing to want to know, and which, imo, Array.isArray seems to provide, if you don't know better.

In which case, we are talking about bogus tests because that isn't really what Array.isArray determines and the generic Array methods will work (to some degree or another) on any object.

I'm aware. And I don't have evidence for tests like that existing. Maybe somebody looking to verify that some object fulfills some kind of contract would know better than naively using isArray without knowing what it actually does.

We could define a new predicate Array.isArrayLike with a proxy friendly extensions mechanism (@@isArrayLike). It wouldn't be the same test as Array.isArray and the result would be more a statement of intent than an absolute guarantee.

Would anybody use it? Would it's existence be enough to eliminate the Array.isArray/proxy concern?

It probably would, if the concern is a valid one at all. Somebody naive enough to use isArray would probably still stumble over the existence of an isArrayLike.

It's probably just not worth it, though.

# Tom Van Cutsem (12 years ago)

2013/7/31 Allen Wirfs-Brock <allen at wirfs-brock.com>

We could define a new predicate Array.isArrayLike with a proxy friendly extensions mechanism (@@isArrayLike). It wouldn't be the same test as Array.isArray and the result would be more a statement of intent than an absolute guarantee.

Would anybody use it? Would it's existence be enough to eliminate the Array.isArray/proxy concern?

Adding yet another function doesn't solve the problem: there is extant ES5 code out there that uses Array.isArray. It will fail on proxies-for-arrays, even when those proxies-for-arrays would be perfectly substitutable for actual arrays (e.g. membraned arrays).

Here's a contrived example where a function behaves differently depending on whether you pass it an array or not:

// call makePoint( [ x, y] ) to construct a new Point(x,y)
// call makePoint(x) to construct a new Point(x,x)
function makePoint(arg) {
  if (Array.isArray(arg)) {
    return new Point(arg[0], arg[1]);
  } else {
    return new Point(arg, arg);
  }
}

While looking for Array.isArray on GitHub I found an example of an in-the-wild function that does this kind of argument-inspection (even twice in the same function): cnicq/DB/blob/1f3a88501f819378ec7919b5f87d20bbd98da5a7/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/statistics_strategy.js#L19

In particular, this line of code looks like a common idiom when you want to abstract over dealing with either 1 or many things:

var tagObjects = Array.isArray(tags) ? tags : [tags];
// then do a for-loop over the array

Given that ES6 will allow users to create subclasses of Array, where subclass instances will be branded as arrays, Array.isArray's contract will already become more diluted. In that light, I don't see the problem with Array.isArray(proxyForArray) returning true.

On the flip side: DOM nodelists, while behaviorally similar to arrays, also don't pass the Array.isArray test. Does this anger developers? Or is this common wisdom and people have learned to live with the differences between nodelists and arrays?

# Erik Arvidsson (12 years ago)

On Jul 31, 2013 6:40 AM, "Tom Van Cutsem" <tomvc.be at gmail.com> wrote:

On the flip side: DOM nodelists, while behaviorally similar to arrays,

also don't pass the Array.isArray test. Does this anger developers? Or is this common wisdom and people have learned to live with the differences between nodelists and arrays?

Yes, but mostly it is because NodeList etc do not have Array.prototype on the prototype chain. _______________________________________________