Calling toString on function proxy throws TypeError exception

# Thomas Greiner (9 years ago)

According to developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString Function.prototype.toString is supposed to throw a TypeError exception when used on a function proxy. That's also consistent with how it's defined at www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.tostring but presumably only because the Proxy scenario is not explicitly mentioned.

The problem I see with that is that it makes proxies distinguishable from their targets even if they don't specify any traps. At least in cases where the mere existence of a proxy needs to be kept secret, this introduces a severe limitation.

That behavior also goes against the "transparent virtualization" principle as outlined by Axel Rauschmayer (see www.2ality.com/2014/12/es6-proxies.html#transparent_virtualization_and_handler_encapsulation ):

Proxies are shielded in two ways:

  • It is impossible to determine whether an object is a proxy or not (transparent virtualization).
  • You can’t access a handler via its proxy (handler encapsulation).

Thomas

# Brian Terlson (9 years ago)

Proxies without handlers are not indistinguishable from their targets. There are many examples, eg. let p = new Proxy(new Map(), {}); p.set(1, 1); also throws.

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Thomas Greiner Sent: Thursday, October 22, 2015 4:39 AM To: es-discuss <es-discuss at mozilla.org>

Subject: Calling toString on function proxy throws TypeError exception

According to developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toStringna01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FFunction%2FtoString&data=01|01|brian.terlson%40microsoft.com|17838fa9a81040efef5108d2dad58478|72f988bf86f141af91ab2d7cd011db47|1&sdata=GOvHpju8FRlkPatB05axAFp7P8G0iRA%2F%2FU8mj%2FqnCX4%3D Function.prototype.toString is supposed to throw a TypeError exception when used on a function proxy. That's also consistent with how it's defined at www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.tostringna01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.ecma-international.org%2Fecma-262%2F6.0%2F%23sec-function.prototype.tostring&data=01|01|brian.terlson%40microsoft.com|17838fa9a81040efef5108d2dad58478|72f988bf86f141af91ab2d7cd011db47|1&sdata=Epix453P5jAq8rLpp9sIHXY8AVXtCiP99%2BohNXmf0p0%3D but presumably only because the Proxy scenario is not explicitly mentioned.

The problem I see with that is that it makes proxies distinguishable from their targets even if they don't specify any traps. At least in cases where the mere existence of a proxy needs to be kept secret, this introduces a severe limitation.

That behavior also goes against the "transparent virtualization" principle as outlined by Axel Rauschmayer (see www.2ality.com/2014/12/es6-proxies.html#transparent_virtualization_and_handler_encapsulationna01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.2ality.com%2F2014%2F12%2Fes6-proxies.html%23transparent_virtualization_and_handler_encapsulation&data=01|01|brian.terlson%40microsoft.com|17838fa9a81040efef5108d2dad58478|72f988bf86f141af91ab2d7cd011db47|1&sdata=a4sAwSPF11X28Ck082Ds%2FkFWQH%2F6I%2BgnGO6%2BURwrOM4%3D):

Proxies are shielded in two ways:

  • It is impossible to determine whether an object is a proxy or not (transparent virtualization).
  • You can’t access a handler via its proxy (handler encapsulation).

Thomas

# Allen Wirfs-Brock (9 years ago)

On Oct 22, 2015, at 11:23 AM, Mark S. Miller <erights at google.com> wrote:

Tom, this doesn't sound right to me. Didn't we intend Function.prototype.toString to be transparent thru function proxies?

cc'ing Allen and Brian

There is nothing unique to toString going on there. This is just the problem that the default proxy handlers are not transparent across method calls in that they pass the proxy object rather than the target object as the this value to function that they ultimately invoke. If that method needs to access internal slots of its this value or has this identify dependencies they don’t work. It turns out that most Function.prototype methods have such dependencies.

# Mark S. Miller (9 years ago)

I know that's true in general. But we made a few exceptions, especially for functions and arrays. I thought F.p.toString was one, but maybe not. I just don't remember.

# Allen Wirfs-Brock (9 years ago)

On Oct 22, 2015, at 11:43 AM, Mark S. Miller <erights at google.com> wrote:

I know that's true in general. But we made a few exceptions, especially for functions and arrays. I thought F.p.toString was one, but maybe not. I just don't remember.

There are no such special cases that I’m aware of, and I don’t know how they would work. See the proxy [[Call]] internal method ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist, ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist

This is basically a coordination issue between the access of a method via a proxy [[Get]] and the invocation of the retrieved function via a [[Call]. Essentially, the creation of a membrane is required to make it work. As I’m sure you will recall, we explored using an invoke trap to help deal with this sort of situation but eventually abandoned that. At the time we recognized that proxies that only used default handler would not be transparent in such situations and decide that it would be ok.

# Mark Miller (9 years ago)

Ok, that all makes sense and is fine with me. Thanks for clarifying.

Tom, I'm still curious what you remember?

# Tom Van Cutsem (9 years ago)

2015-10-22 21:03 GMT+02:00 Mark Miller <erights at gmail.com>:

Ok, that all makes sense and is fine with me. Thanks for clarifying.

Tom, I'm still curious what you remember?

What I remember is that the original direct proxies spec did the transparent forwarding. wiki.ecmascript.org still seems to be down, but an archived copy of late 2014 (thank you Wayback Machine) shows that the draft spec indeed specified forwarding:

< web.archive.org/web/20141214082226/http://wiki.ecmascript.org/doku.php?id=harmony:proxies_spec

*15.3.4.2 Function.prototype.toString ( O ) *

When called on a Proxy that is callable (i.e. whose target has a [[Call]] internal method), returns the result of applying the built-in Function.prototype.toString on the Proxy’s [[Target]]. Otherwise, throw the same TypeError that would be thrown if this function is applied to a non-function object.

For a revoked proxy, IsCallable is false so this operation throws a TypeError.

I cannot recall any specific discussions on why that behavior was changed to throwing a TypeError instead. The only reason I can come up with is that it could be deemed a violation of the proxy's encapsulation in that it should not leak such details about its target object.

I think it would be useful to at least reconsider the original forwarding behavior. I think that in practice, forwarding toString() to the target is not harmful, and the more places where code throws on a Proxy, the less useful Proxies become altogether.

I noticed there is a stage 1 proposal to revise the specification of F.p.toString() < michaelficarra/Function-prototype-toString-revision>,

including standardizing its behavior for host objects. Perhaps the behavior of F.p.toString() when applied to (function) proxies can become a part of this revision effort?

# Allen Wirfs-Brock (9 years ago)

On Oct 22, 2015, at 1:38 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

... I cannot recall any specific discussions on why that behavior was changed to throwing a TypeError instead. The only reason I can come up with is that it could be deemed a violation of the proxy's encapsulation in that it should not leak such details about its target object.

I think it would be useful to at least reconsider the original forwarding behavior. I think that in practice, forwarding toString() to the target is not harmful, and the more places where code throws on a Proxy, the less useful Proxies become altogether.

I noticed there is a stage 1 proposal to revise the specification of F.p.toString() <michaelficarra/Function-prototype-toString-revision, michaelficarra/Function-prototype-toString-revision>, including standardizing its behavior for host objects. Perhaps the behavior of F.p.toString() when applied to (function) proxies can become a part of this revision effort?

Cheers, Tom

Why would it be reasonable to do this for toString, but not also call, apply, and bind? And ultimately this problem exists for all built-in methods that have have internal state dependencies. I just don’t see why we care so much specifically about toString

# Tom Van Cutsem (9 years ago)

2015-10-22 23:09 GMT+02:00 Allen Wirfs-Brock <allen at wirfs-brock.com>:

Why would it be reasonable to do this for toString, but not also call, apply, and bind? And ultimately this problem exists for all built-in methods that have have internal state dependencies. I just don’t see why we care so much specifically about toString

Calling Function.prototype.{call,apply,bind} on a function proxy should obviously work (not throw a TypeError), as a function proxy has well-defined behavior in such cases. For toString(), the two obvious choices are to forward (show the function body of the target function), or to generate a custom toString() representation like other exotic objects do (e.g. "function () { [function proxy] }", or perhaps even show the toString() representation of the apply trap).

# Claude Pache (9 years ago)

Almost every method found on Function.prototype "works" (or, at least, does not throw a TypeError before trying to work) if and only if the target is callable, or if and only if typeof applied to the target produces "function". That should not be different for Function.prototype.toString(). Even if "working" means producing a useless string à la "function () { [native code] }".

# Allen Wirfs-Brock (9 years ago)

On Oct 23, 2015, at 6:43 AM, Claude Pache <claude.pache at gmail.com> wrote:

Almost every method found on Function.prototype "works" (or, at least, does not throw a TypeError before trying to work) if and only if the target is callable, or if and only if typeof applied to the target produces "function". That should not be different for Function.prototype.toString(). Even if "working" means producing a useless string à la "function () { [native code] }".

—Claude

Right, I should have look closer at the actual spec for call, apply, and blind rather than depending upon memory. The reason they work on proxies is that they only depend upon the MOP API of their this object. In other words, they are generic across all object that have a [[Call]] internal method.

Function.prototype.toString is different in that it is dependent upon the actual internal structure of the object is is applied to rather than the object’s MOP API. This is necessary because F.p.toString must access information (the source code of the function or the ability to recreate it) that is not available through the MOP. As I’ve mentioned in earlier messages, F.p.toString is in this regard like all other methods that directly access private internal state.

F.p.toString could be respecified to explicitly recognize proxies and drill through them but it isn’t clear why F.p.toString should be singled out from all other private state accessing buiit-in methods in this regard.

But, let’s assume we did make F.p.toString drill through callable proxies. What are the implications.

First remember, that a callable proxy can only be created on a target object that already has a [[Call]] internal method. The target might itself be a callable proxy, but ultimately the chain of targets must reach an object that has a primitive [[Call]] behavior. This would normally be either an ECMAScript function object or a built-in function object.

So, while it is perfectly reasonable to define a callable proxy that, for example, in its [[Call]] handler runs a Lisp interpreter over a captured S-expression, such a proxy must either directly or indirectly have as its target an ECMAScript function. If you were defining such a proxy where the Lisp interpreter is embedded within its [[Call]] handler, you might use something like: function() {} as the target. If you apply an enhanced proxy drill throughing F.p.toString to such a proxy you would get the source code for function() {}. How is that useful?

Another way, to implement our Lisp interpreting proxy would be to use as the target an ECMAScript function that actually implements the Lisp interpreter. In that case, the [[Call]] handler would simply call the target function passing the captured S-expression to it as an argument. If you apply an enhanced proxy drill throughing F.p.toString to that sort of a proxy you would get back the source code for the Lisp interpreter. This seems potentially harmful. Revealing the source code of the Lisp interpreter might reveal exploitable secrets. Heck, the very fact that a Lisp interpreter is being used might be a secret.

So, why would we want to do this?

# Mark Miller (9 years ago)

I like the idea a function proxy is more encapsulating of its implementation than a function is.

I also like the idea of treating a function proxy as a builtin callable, rather than a function written in JS, and return something like "function () { [function proxy] }" as Tom suggested or "function () { [native code] }" as Claude suggested. We need progress on the draft spec for F.p.toString reform, including the standardized pattern for the function sources that are not supposed to parse, e.g., "function () { [...stuff...] }".

# Allen Wirfs-Brock (9 years ago)

On Oct 26, 2015, at 11:20 AM, Mark Miller <erights at gmail.com> wrote:

I like the idea a function proxy is more encapsulating of its implementation than a function is.

I also like the idea of treating a function proxy as a builtin callable, rather than a function written in JS, and return something like "function () { [function proxy] }" as Tom suggested or "function () { [native code] }" as Claude suggested. We need progress on the draft spec for F.p.toString reform, including the standardized pattern for the function sources that are not supposed to parse, e.g., "function () { [...stuff...] }”.

I guess I still don’t understand the use case for applying there built-in F.p.toString to any callable. If you are explicitly defining a callable proxy you may want to define a toString method for it that does something that makes sense for the specific kind of callable you are creating. But when you expect to do:

   evalableString = Function.prototype.toString.call(anyOldCallable);

with the expectation that the result you are going to get will be useful for anything?

# Mark Miller (9 years ago)

Only because typeof f === 'function' divides the world into callables and non callables.

On Oct 26, 2015 3:20 PM, "Allen Wirfs-Brock" <allen at wirfs-brock.com> wrote:

On Oct 26, 2015, at 11:20 AM, Mark Miller <erights at gmail.com> wrote:

I like the idea a function proxy is more encapsulating of its

implementation than a function is.

I also like the idea of treating a function proxy as a builtin

callable, rather than a function written in JS, and return something like "function () { [function proxy] }" as Tom suggested or "function () { [native code] }" as Claude suggested. We need progress on the draft spec for F.p.toString reform, including the standardized pattern for the function sources that are not supposed to parse, e.g., "function () { [...stuff...] }”.

I guess I still don’t understand the use case for applying there built-in

F.p.toString to any callable. If you are explicitly defining a callable proxy you may want to define a toString method for it that does something that makes sense for the specific kind of callable you are creating. But when you expect to do:

   evalableString = Function.prototype.toString.call(anyOldCallable);

with the expectation that the result you are going to get will be useful

for anything?

function () { [...] } is how callables I idiomatically give a response that is not useful for anything. That's all, but it is enough.

# Claude Pache (9 years ago)

Le 26 oct. 2015 à 20:20, Allen Wirfs-Brock <allen at wirfs-brock.com> a écrit :

On Oct 26, 2015, at 11:20 AM, Mark Miller <erights at gmail.com> wrote:

I like the idea a function proxy is more encapsulating of its implementation than a function is.

I also like the idea of treating a function proxy as a builtin callable, rather than a function written in JS, and return something like "function () { [function proxy] }" as Tom suggested or "function () { [native code] }" as Claude suggested. We need progress on the draft spec for F.p.toString reform, including the standardized pattern for the function sources that are not supposed to parse, e.g., "function () { [...stuff...] }”.

I guess I still don’t understand the use case for applying there built-in F.p.toString to any callable. If you are explicitly defining a callable proxy you may want to define a toString method for it that does something that makes sense for the specific kind of callable you are creating. But when you expect to do:

  evalableString = Function.prototype.toString.call(anyOldCallable);

with the expectation that the result you are going to get will be useful for anything?

Allen

The expectation is not that F.p.toString will always return something useful; it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.

For example, a random programmer may write:

Function.isGenerator = function () {
    return typeof this == "function" && this.toString().match(/^function\s*\*/) != null
}

That function will work (in the sense of: will return an answer; I'm not judging the quality of that answer) with anything reasonable fed to it (where "reasonable" excludes things like (class { static toString() { throw "pwnd!" }})). Well, ... until a proxy for a function is encountered.

# Claude Pache (9 years ago)

Le 27 oct. 2015 à 09:35, Claude Pache <claude.pache at gmail.com> a écrit :

Le 26 oct. 2015 à 20:20, Allen Wirfs-Brock <allen at wirfs-brock.com> a écrit :

On Oct 26, 2015, at 11:20 AM, Mark Miller <erights at gmail.com> wrote:

I like the idea a function proxy is more encapsulating of its implementation than a function is.

I also like the idea of treating a function proxy as a builtin callable, rather than a function written in JS, and return something like "function () { [function proxy] }" as Tom suggested or "function () { [native code] }" as Claude suggested. We need progress on the draft spec for F.p.toString reform, including the standardized pattern for the function sources that are not supposed to parse, e.g., "function () { [...stuff...] }”.

I guess I still don’t understand the use case for applying there built-in F.p.toString to any callable. If you are explicitly defining a callable proxy you may want to define a toString method for it that does something that makes sense for the specific kind of callable you are creating. But when you expect to do:

 evalableString = Function.prototype.toString.call(anyOldCallable);

with the expectation that the result you are going to get will be useful for anything?

Allen

The expectation is not that F.p.toString will always return something useful; it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.

For example, a random programmer may write:

Function.isGenerator = function () {
   return typeof this == "function" && this.toString().match(/^function\s*\*/) != null
}

Naturally, I meant:

Function.isGenerator = function (f) {
   return typeof f == "function" && f.toString().match(/^function\s*\*/) != null
}
# Boris Zbarsky (9 years ago)

On 10/27/15 4:35 AM, Claude Pache wrote:

it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.

It's totally false for random "host objects" with a [[Call]] in ES5, per spec and in at least some implementations. As you can tell in Firefox for example:

Function.prototype.toString.call(document.createElement("object"))

(though it does not throw for document.all in Firefox, for interesting implementation reasons).

That function will work (in the sense of: will return an answer; I'm not judging the quality of that answer) with anything reasonable fed to it (where "reasonable" excludes things like (class { static toString() { throw "pwnd!" }})).

Won't work with an HTMLObjectElement in at least some browsers. How "reasonable" that is, who knows.

# Boris Zbarsky (9 years ago)

On 10/27/15 9:14 AM, Boris Zbarsky wrote:

On 10/27/15 4:35 AM, Claude Pache wrote:

it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.

It's totally false for random "host objects" with a [[Call]] in ES5, per spec and in at least some implementations. As you can tell in Firefox for example:

Function.prototype.toString.call(document.createElement("object"))

(though it does not throw for document.all in Firefox, for interesting implementation reasons).

Though note that I would be just fine with defining Function.prototype.toString to return something akin to what it does for "host functions" for any "host object" with a [[Call]]...

# Claude Pache (9 years ago)

Le 27 oct. 2015 à 14:14, Boris Zbarsky <bzbarsky at mit.edu> a écrit :

On 10/27/15 4:35 AM, Claude Pache wrote:

it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.

It's totally false for random "host objects" with a [[Call]] in ES5, per spec and in at least some implementations. As you can tell in Firefox for example:

Function.prototype.toString.call(document.createElement("object"))

(though it does not throw for document.all in Firefox, for interesting implementation reasons).

That function will work (in the sense of: will return an answer; I'm not judging the quality of that answer) with anything reasonable fed to it (where "reasonable" excludes things like (class { static toString() { throw "pwnd!" }})).

Won't work with an HTMLObjectElement in at least some browsers. How "reasonable" that is, who knows.

-Boris

You're right. But since document.createElement("object") does not inherit from Function.prototype, the code (f.toString()) accidentally works after all.

(I've tried not to be too smart in my example by writing f.toString() instead of Function.prototype.toString.call(f). Maybe I should have been even less smart by defining an instance method on Function.prototype instead of a static method on Function...)

# Mark S. Miller (9 years ago)

Notice that whatever we decide on the issue, functionProxy.toString() will work regardless, since you'd be getting the toString method itself through the membrane. functionProxy.toString will be a function proxy for the target.toString method. The invocation on the toString proxy with functionProxy as this will be translated by the membrane back into an invocation of target.toString with target as this.

The issue we're debating is only relevant on an edge case -- when explicitly invoking F.p.toString.call(functionProxy).

# Jordan Harband (9 years ago)

Mark: considering that explicitly invoking a builtin prototype method, expecting a throw, to test for the [[Call]] internal slot, was the branding approach the committee agreed to preserve when it reaffirmed Symbol.toStringTag, as an alternative to Object#toString.call, I think Claude's point ("The expectation is not that F.p.toString will always return something useful; it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.") is the one that convinces me.

Shouldn't tests in existing code that rely on Function#toString not throwing to indicate that something is callable, or throwing to indicate that it is not, remain true for a function proxy?

# Boris Zbarsky (9 years ago)

On 10/27/15 12:47 PM, Jordan Harband wrote:

I think Claude's point ("The expectation is not that F.p.toString will always return something useful; it is that, for any callable object, it will return a string and not throw, because it was so since the dawn of JS.") is the one that convinces me.

But that statement isn't true, as I pointed out. It just isn't, no matter how much we may wish it.

# Claude Pache (9 years ago)

Le 27 oct. 2015 à 15:52, Mark S. Miller <erights at google.com> a écrit :

Notice that whatever we decide on the issue, functionProxy.toString() will work regardless, since you'd be getting the toString method itself through the membrane. functionProxy.toString will be a function proxy for the target.toString method.

... at the condition that the proxy handler explicitly traps the getting of the toString method. It is certainly the case for impermeable membranes, but not for all proxies. Concretely, new Proxy(function() {}, {}).toString() does throw in ES2015.

# Allen Wirfs-Brock (9 years ago)

On Oct 27, 2015, at 2:37 AM, Claude Pache <claude.pache at gmail.com> wrote:

Naturally, I meant:

Function.isGenerator = function (f) {
  return typeof f == "function" && f.toString().match(/^function\s*\*/) != null
}

—Claude

That function will work (in the sense of: will return an answer; I'm not judging the quality of that answer) with anything reasonable fed to it (where "reasonable" excludes things like (class { static toString() { throw "pwnd!" }})). Well, ... until a proxy for a function is encountered.

—Claude

This may have been pragmatically useful but it has never been a reliable test because there was nothing that stops somebody from adding an arbiietrary toString own property to f or from using dunder photo to change the prototype used to supply toString.

Also, going back to at lest ES3, F.p.toString was specified to throw a TypeError when applied to an object that “is not Function object”.

That said, I think it is plausible that the predicate in step 2 of tc39.github.io/ecma262/#sec-function.prototype.tostring, tc39.github.io/ecma262/#sec-function.prototype.tostring could be replaced with: IsCallable(func)

# Mark Miller (9 years ago)

Non-membraned proxies are irreparably non-transparent. This case is not worth worrying about.

# Claude Pache (9 years ago)

Le 27 oct. 2015 à 18:41, Allen Wirfs-Brock <allen at wirfs-brock.com> a écrit :

That said, I think it is plausible that the predicate in step 2 of tc39.github.io/ecma262/#sec-function.prototype.tostring, tc39.github.io/ecma262/#sec-function.prototype.tostring could be replaced with: IsCallable(func)

Yes please. From a user's point-of-view, it is not clear why implementations treat differently window.alert and document.createElement('object').

# Tom Van Cutsem (9 years ago)

2015-10-27 12:47 GMT-04:00 Jordan Harband <ljharb at gmail.com>:

Shouldn't tests in existing code that rely on Function#toString not throwing to indicate that something is callable, or throwing to indicate that it is not, remain true for a function proxy?

Excluding some host objects (as Boris pointed out), I think this does capture the programmer's expectation in the common case.

When I wrote my Proxy shim for ES5 < tvcutsem/harmony-reflect>, I got feedback from

developers to patch F.p.toString() to please unwrap the proxy, as this seemed to be the general expectation (just like Array.isArray should return true for proxies-for-arrays).

I don't have a strong opinion on whether F.p.toString() should drill through the proxy or just return "function() { [...] }", but would really like it not to throw. A function proxy is, for all intents and purposes, a valid function. Having F.p.toString report that it is called on an "incompatible object" just feels wrong.

Even though just calling toString() would work fine on a membraned function proxy, the idiom of storing built-in functions in variables and explicitly .call()-ing those built-in functions is, to my understanding, still very common.