Closed js-choi closed 3 years ago
@acutmore left a comment on the Gist, to which I’m replying here:
// The adversary’s code. delete Set.prototype.slice;
I think this was supposed to be
delete Set.prototype.has
:)
Yes, the delete Set.prototype.slice
is a mistake; thanks for catching it!
There is currently no way to harden trusted code against an adversary’s mutation of Function.prototype.call or Function.prototype.bind.
This is not entirely true. It can be hardened against it just relies on being able to run code before anyone else (just like capturing any global/method before it can be modified).
// at start before other code runs: const uncurryThis = Function.bind.bind(Function.call); // f => Function.call.bind(f) // f => (…args) => f.call(…args) // or const {apply} = Reflect; const uncurryThis = f => (that, …args) => apply(f, that, args); // then const setHas = uncurryThis(Set.prototype.has); // later setHas(new Set([1]), 1); // true
It is true that guarding against Function.prototype.call
mutation is possible with Function.bind.bind
. This is another old mistake which I’ll need to fix.
This guard requires wrapping every global method you use in an uncurried-this
wrapper function, à la @ljharb’s call-bind library, which is transitively used by a ton of websites.
So the argument that I need to make is that (1) this intrinsic call-binding is just one common use case of a very commonly used pair of functions (bind/call), and (2) despite its frequency, using bind/call is currently clunky and its ergonomics should be optimized with an operator.
I do anticipate that the biggest argument against this will be “the method-wrapping is fine; just use the pipe operator |>
if you want a fluent postfix expression”. I am still trying to think of ways to respond to that reasonable objection. But first I need to modify and import this Gist.
to me it’s really not about the postfix part, i just need a way to syntactically change the receiver of a function - it’s just that once that exists, it’s kind of silly to force the awkward ordering of .call when we could easily have natural o.a(b)
ordering.
i just need a way to syntactically change the receiver of a function
@ljharb: People will probably say, “Why do you need syntax to change your function’s receiver? Why not just keep using Function.call
or your uncurryThis
/callBind
wrapper?” It’s already kind of happened in that Matrix conversation, too.
The best argument against that anti-argument that I can think of is developer ergonomics. Using Function.call
/uncurryThis
/callBind
is weird, clunky, and obscuring. o->fn(a)
or whatever is more ergonomic than Function.call(fn, o)
because it’s less wordy and because the ordering resembles o.fn(a)
.
If it weren’t for that ergonomic difference, then it’s like what @acutmore said: everything is already possible in userland without syntax. (Possible inherent performance problems with wrapped functions notwithstanding, cf. #8.)
Part of it is robustness, and part of it is ergonomics. We have indisputable evidence that the current state of things is not ergonomic; nobody uses call/bind/apply when an arrow function or unsafe call suffices.
I need to integrate this old Gist that compares this proposal with @hax’s Stage-1 Extensions proposal. There are a few problems with the Gist, too.
The biggest problem is, as per the Matrix discussion from two days ago and also discussion in #8, the document focuses too much on the security use case.
I’m going to redo this document to focus more on the general frequency of
bind
/call
and how clunky they currently are.