From f114d74076184fc9f6b963e4e4f78cac0d57c24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 11:20:34 -0400 Subject: [PATCH 01/15] =?UTF-8?q?explainer/spec:=20Rename=20to=20bind-this?= =?UTF-8?q?=20operator=20To=20distinguish=20from=20other=20uses=20of=20the?= =?UTF-8?q?=20word=20=E2=80=9Cbind=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++------ spec.html | 45 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3b1dc56..579676c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bind operator for JavaScript +# Bind-`this` operator for JavaScript ECMAScript Stage-0 Proposal. J. S. Choi, 2021. * **[Formal specification][]** @@ -10,8 +10,8 @@ ECMAScript Stage-0 Proposal. J. S. Choi, 2021. (A [formal specification][] is available.) **Method binding** `->` is a **left-associative infix operator**. -Its right-hand side is an **identifier** -or an **expression** in `(` `)`, +Its right-hand side is a **chain of identifiers identifier** (like `f` or `o.x.y`) +or an **expression** in `(` `)` (like `hof()`), either of which must evaluate to a **function**. Its left-hand side is some expression that evaluates to an **object**. The `->` operator **binds** its left-hand side @@ -24,6 +24,10 @@ equivalent to `fn.bind(arr)`, except that its behavior does **not change** if code elsewhere **reassigns** the **global method** `Function.prototype.bind`. +The right-hand side may be a property-access chain. +`arr->o.x.y` would be roughly +equivalent to `o.x.y.bind(arr)`. + Likewise, `obj->(createMethod())` would be roughly equivalent to `createMethod().bind(obj)`. @@ -44,7 +48,7 @@ and optional chains. |`new` expressions with arguments | `new C()->fn` | | Optional chains | `a?.b->fn` | -The bound functions created by the bind operator +The bound functions created by the bind-`this` operator are **indistinguishable** from the bound functions that are already created by [`Function.prototype.bind`][bind]. Both are **exotic objects** that do not have a `prototype` property, @@ -59,7 +63,7 @@ is equivalent to `a->(createFn())`. There are **no other special rules**. -## Why a bind operator +## Why a bind-`this` operator [`Function.prototype.bind`][call] and [`Function.prototype.call`][bind] are very common in **object-oriented JavaScript** code. They are useful methods that allows us to apply functions to any object, @@ -155,7 +159,7 @@ obj->extensionMethod(); // Compare with extensionMethod.call(obj). ``` -The bind operator can also **extract** a **method** from a **class** +The bind-`this` operator can also **extract** a **method** from a **class** into a function whose first parameter becomes its `this` binding:\ for example, `const { slice } = Array.prototype; arr->slice(1, 3);`.\ It can also similarly extract a method from an **instance** diff --git a/spec.html b/spec.html index f3cd9d6..e5de817 100644 --- a/spec.html +++ b/spec.html @@ -1,5 +1,5 @@
-title: ES bind operator (2021)
+title: ES bind-this operator (2021)
 status: proposal
 stage: 0
 location: https://github.com/js-choi/proposal-bind-operator
@@ -12,7 +12,7 @@
 
   

Introduction

This is the formal specification - for a proposed bind operator `->` in JavaScript. + for a proposed bind-`this` operator `->` in JavaScript. It modifies the original ECMAScript specification with several new or revised clauses. See Static Semantics: IsFunctionDefinition MemberExpression `.` PrivateIdentifier MemberExpression `->` `(` Expression `)` - MemberExpression `->` IdentifierReference + MemberExpression `->` IdentifierReference @@ -277,12 +277,12 @@

Static Semantics: Early Errors

-

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind operator:

+

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind-`this` operator:


             a->b
             `c`
           
-

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind operator:

+

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind-`this` operator:


             a.b
             `c`
@@ -300,12 +300,12 @@ 

Static Semantics: Early Errors

-

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind operator:

+

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind-`this` operator:


             a()->b
             `c`
           
-

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind operator:

+

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind-`this` operator:


             a().b
             `c`
@@ -323,12 +323,12 @@ 

Static Semantics: Early Errors

-

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind operator:

+

This production exists in order to prevent automatic semicolon insertion rules () from being applied to the following code with the bind-`this` operator:


             a?.()->b
             `c`
           
-

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind operator:

+

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without the bind-`this` operator:


             a?.().b
             `c`
@@ -340,7 +340,7 @@ 

Static Semantics: Early Errors

-

Bind Operator

+

Bind-`this` operator

This section is a wholly new subclause to be inserted after the 1. Return _F_. -

Function objects created using the bind operator are exotic objects. They also do not have a *"prototype"* property.

+

Function objects created using the bind-`this` operator are exotic objects. They also do not have a *"prototype"* property.

@@ -455,4 +455,27 @@

Runtime Semantics: ChainEvaluation

+ + +

Decorators

+

Syntax

+ + DecoratorList[Yield, Await] : + DecoratorList[?Yield, ?Await]? Decorator[?Yield, ?Await] + + Decorator[Yield, Await] : + `@init:` DecoratorMemberExpression[?Yield, ?Await] + `@init:` DecoratorCallExpression[?Yield, ?Await] + `@` DecoratorMemberExpression[?Yield, ?Await] + `@` DecoratorCallExpression[?Yield, ?Await] + + DecoratorMemberExpression[Yield, Await] : + IdentifierReference[?Yield, ?Await] + DecoratorMemberExpression[?Yield, ?Await] `.` IdentifierName + `(` Expression[+In, ?Yield, ?Await] `)` + + DecoratorCallExpression[Yield, Await] : + DecoratorMemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] + +
From cb3ff69100ae610e87ad0e816643a0f9c320ac66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 11:28:42 -0400 Subject: [PATCH 02/15] explainer: Clarify property-accessor non-goal --- README.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 579676c..0e33670 100644 --- a/README.md +++ b/README.md @@ -205,23 +205,27 @@ but method extraction is **already possible** with this proposal.\ is not much wordier than\ `const slice = arr&.slice; slice(1, 3);` -**Extension getters and setters** -(i.e., extending objects with new property getters or setters -**without mutating** the object) -may **also** be useful, -and this proposal would be **forward compatible** with such a feature -using the **same operator** `->` for **property-object binding**, -in addition to this proposal’s **method binding**. -Getter/setter binding could be added in a separate proposal -using `{ get () {}, set () {} }` objects. -For example, we could add an extension getter `allDivs` -to a `document` object like so: +**Extracting property accessors** (i.e., getters and setters) +is not a goal of this proposal. +Get/set accessors are **not like** methods. Methods are **values**. +Accessors themselves are **not values**; +they are functions that activate when getting or setting properties. +Getters/setters have to be extracted using `Object.getOwnPropertyDescriptor`; +they are not handled in a special way. +This verbosity may be considered to be desirable [syntactic salt][]: +it makes the developer’s intention (to extract getters/setters – and not methods) +more explicit. + ```js -const allDivs = { - get () { return this.querySelectorAll('div'); } -}; +const { get: $getSize } = + Object.getOwnPropertyDescriptor( + Set.prototype, 'size'); + +// The adversary’s code. +delete Set; delete Function; -document->allDivs; +// Our own trusted code, running later. +new Set([0, 1, 2])->$getSize(); ``` **Function/expression application**, @@ -232,3 +236,5 @@ Instead, it is addressed by the **pipe operator**, with which this proposal’s syntax **works well**.\ For example, we could untangle `h(await g(o->f(0, v)), 1)`\ into `v |> o->f(0, %) |> await g(%) |> h(%, 1)`. + +[syntactic salt]: https://en.wikipedia.org/wiki/Syntactic_sugar#Syntactic_salt From 2759adba02e38dfd274db7ec53898f807a51796b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 12:05:39 -0400 Subject: [PATCH 03/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Re?= =?UTF-8?q?word,=20note=20Function.call=20equivalence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0e33670..34589c1 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,17 @@ equivalent to `createMethod().bind(obj)`. If the operator’s right-hand side does not evaluate to a function during runtime, then the program throws a `TypeError`. +The bound functions created by the bind-`this` operator +are **indistinguishable** from the bound functions +that are already created by [`Function.prototype.bind`][bind]. +Both are **exotic objects** that do not have a `prototype` property, +and which may be called like any typical function. + +From this definition, `o->f(...args)` +is **indistinguishable** from `Function.prototype.call(o, ...args)`, +except that its behavior does **not change** +if code elsewhere **reassigns** the global method `Function.prototype.call`. + Function binding has equal **[precedence][]** with **member expressions**, call expressions, `new` expressions with arguments, and optional chains. @@ -48,14 +59,8 @@ and optional chains. |`new` expressions with arguments | `new C()->fn` | | Optional chains | `a?.b->fn` | -The bound functions created by the bind-`this` operator -are **indistinguishable** from the bound functions -that are already created by [`Function.prototype.bind`][bind]. -Both are **exotic objects** that do not have a `prototype` property, -and which may be called like any typical function. - -Similarly to the `?.` optional-chaining token, -the `->` token may be **padded by whitespace**.\ +Similarly to the `.` and `?.` operators, +the `->` operator may be **padded by whitespace**.\ For example, `a -> m`\ is equivalent to `a->fn`,\ and `a -> (createFn())`\ @@ -111,7 +116,7 @@ delete Array.prototype.slice; slice.call([0, 1, 2], 1, 2); ``` -But this is still vulnerable to mutation of `Function.prototype`: +But this approach is still vulnerable to mutation of `Function.prototype`: ```js // Our own trusted code, running before any adversary. From 36a20e5f7012116ca3934100556d5e698d3c1e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 12:06:34 -0400 Subject: [PATCH 04/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Im?= =?UTF-8?q?prove=20precedence=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 34589c1..8712575 100644 --- a/README.md +++ b/README.md @@ -45,19 +45,26 @@ is **indistinguishable** from `Function.prototype.call(o, ...args)`, except that its behavior does **not change** if code elsewhere **reassigns** the global method `Function.prototype.call`. -Function binding has equal **[precedence][]** with +The `this`-bind operator has equal **[precedence][]** with **member expressions**, call expressions, `new` expressions with arguments, and optional chains. +Like those operators, the `this`-bind operator also may be short-circuited +by optional chains in its left-hand side. [precedence]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence -| Left-hand side | Example | -| ------------------------------- | ------------- | -| Primary expressions | `a->fn` | -| Member expressions | `a.b->fn` | -| Call expressions | `a()->fn` | -|`new` expressions with arguments | `new C()->fn` | -| Optional chains | `a?.b->fn` | +| Left-hand side | Example | Grouping +| ---------------------------------- | ------------ | -------------- +| Member expressions |`a.b->fn.c` |`((a.b)->fn).c` +| Call expressions |`a()->fn()` |`((a())->fn)()` +| Optional chains |`a?.b->fn` |`(a?.b)->fn` +|`new` expressions with arguments |`new C(a)->fn`|`(new C(a))->fn` +|`new` expressions without arguments*|`new a->fn` |`new (a->fn)` + +\* Like `.` and `?.`, the `this`-bind operator also have tighter precedence +than `new` expressions without arguments. +Of course, `new a->fn` is not a very useful expression, +just like how `new (fn.bind(a))` is not a very useful expression. Similarly to the `.` and `?.` operators, the `->` operator may be **padded by whitespace**.\ From f747cfd837e137de39d5808e50dbb909f6689dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 12:06:34 -0400 Subject: [PATCH 05/15] explainer: Node.js real-world examples Closes #5. --- README.md | 124 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 8712575..b65477d 100644 --- a/README.md +++ b/README.md @@ -171,38 +171,121 @@ obj->extensionMethod(); // Compare with extensionMethod.call(obj). ``` -The bind-`this` operator can also **extract** a **method** from a **class** -into a function whose first parameter becomes its `this` binding:\ -for example, `const { slice } = Array.prototype; arr->slice(1, 3);`.\ -It can also similarly extract a method from an **instance** -into a function that always uses that instance as its `this` binding:\ -for example, `const arr = arr->(arr.slice); slice(1, 3);`. - ## Real-world examples Only minor formatting changes have been made to the status-quo examples. - - - - - -
Status quo -With binding +### Node.js +Node.js’s runtime depends on many built-in JavaScript global intrinsic objects +that are vulnerable to mutation or prototype pollution by third-party libraries. +When initializing a JavaScript runtime, Node.js therefore caches +wrapped versions of all global intrinsic objects (and their methods) +in a [large `primordials` object][primordials.js]. +This `primordials` object contains numerous entries that look like this: +```js +ArrayPrototypeConcat: uncurryThis(Array.prototype.concat), +ArrayPrototypeCopyWithin: uncurryThis(Array.prototype.copyWithin), +ArrayPrototypeFill: uncurryThis(Array.prototype.fill), +ArrayPrototypeFind: uncurryThis(Array.prototype.find), +ArrayPrototypeFindIndex: uncurryThis(Array.prototype.findIndex), +ArrayPrototypeLastIndexOf: uncurryThis(Array.prototype.lastIndexOf), +ArrayPrototypePop: uncurryThis(Array.prototype.pop), +ArrayPrototypePush: uncurryThis(Array.prototype.push), +ArrayPrototypePushApply: UncurryThisStaticApply +ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), +``` +…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)`. -
+In other words, Node.js must **wrap** every global intrinsic method +in a `this`-uncurried **wrapper function**, +whose first argument is the method’s `this` value, +using the `uncurryThis` helper function. +The result is that code that uses these global intrinsics, +like this code adapted from [node/lib/internal/v8_prof_processor.js][]: +```js + // specifier is a string. + const file = specifier.slice(2, -4); +``` +… ```js -??? + if (process.platform === 'darwin') { + tickArguments.push('--mac'); + } else if (process.platform === 'win32') { + tickArguments.push('--windows'); + } + tickArguments.push(...process.argv.slice(1)); ``` -From ???. +…must instead look like this: +```js +// Note: This module assumes that it runs before any third-party code. +const { + ArrayPrototypePush, + ArrayPrototypeSlice, + StringPrototypeSlice, +} = primordials; +``` +… +```js + const file = StringPrototypeSlice(specifier, 2, -4); +``` +… +```js + if (process.platform === 'darwin') { + ArrayPrototypePush(tickArguments, '--mac'); + } else if (process.platform === 'win32') { + ArrayPrototypePush(tickArguments, '--windows'); + } + ArrayPrototypePush(tickArguments, + ...ArrayPrototypeSlice(process.argv, 1)); +``` + +This code is now protected against prototype pollution by accident and by adversaries +(e.g., `delete Array.prototype.push`). +However, this protection comes at two costs: + +1. These [uncurried wrapper functions sometimes dramatically reduce performance][#38248]. + This would not be a problem if Node.js could cache + and use the intrinsic methods directly. + But the only current way to use intrinsic methods + would be with `Function.prototype.call`, which is also vulnerable to mutation. - +2. The Node.js community has had [much concern about barriers to contribution][#30697] + by ordinary JavaScript developers, due to the unidiomatic code encouraged by these + uncurried wrapper functions. + +Both of these problems are much improved by the bind-`this` operator. +Instead of wrapping every global method with `uncurryThis`, +Node.js could cached and used **directly** +without worrying about `Function.prototype.call` mutation: ```js -??? +// Note: This module assumes that it runs before any third-party code. +const $push = Array.prototype.push; +const $arraySlice = Array.prototype.slice; +const $stringSlice = String.prototype.slice; ``` +… +```js + const file = specifier->stringSlice(2, -4); +``` +… +```js + if (process.platform === 'darwin') { + tickArguments->push('--mac'); + } else if (process.platform === 'win32') { + tickArguments->push('--windows'); + } + tickArguments->push(...process.argv->slice(1)); +``` + +Performance has improved, and readability has improved. +There are no more uncurried wrapper functions; +instead, the code uses the intrinsic methods in a notation +similar to normal method calling with `.`. -
+[node/lib/internal/v8_prof_processor.js]: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/v8_prof_processor.js +[#38248]: https://github.com/nodejs/node/pull/38248 +[#30697]: https://github.com/nodejs/node/issues/30697 ## Non-goals A goal of this proposal is **simplicity**. @@ -250,3 +333,4 @@ For example, we could untangle `h(await g(o->f(0, v)), 1)`\ into `v |> o->f(0, %) |> await g(%) |> h(%, 1)`. [syntactic salt]: https://en.wikipedia.org/wiki/Syntactic_sugar#Syntactic_salt +[primordials.js]: https://github.com/nodejs/node/blob/master/lib/internal/per_context/primordials.js From df1e216711a6668f2f0545526581c51161a418cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=C2=A0Choi?= Date: Thu, 30 Sep 2021 13:06:17 -0400 Subject: [PATCH 06/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Ty?= =?UTF-8?q?po?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jordan Harband --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b65477d..1dfa31d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ECMAScript Stage-0 Proposal. J. S. Choi, 2021. (A [formal specification][] is available.) **Method binding** `->` is a **left-associative infix operator**. -Its right-hand side is a **chain of identifiers identifier** (like `f` or `o.x.y`) +Its right-hand side is a **chain of identifiers**, **identifier** (like `f` or `o.x.y`), or an **expression** in `(` `)` (like `hof()`), either of which must evaluate to a **function**. Its left-hand side is some expression that evaluates to an **object**. From 1c8d7faab52e44e05f4ed4c1b2da795ee1ba0672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=C2=A0Choi?= Date: Thu, 30 Sep 2021 13:06:58 -0400 Subject: [PATCH 07/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Node:=20Refer=20t?= =?UTF-8?q?o=20call-binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jordan Harband --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1dfa31d..0fcd941 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ ArrayPrototypePush: uncurryThis(Array.prototype.push), ArrayPrototypePushApply: UncurryThisStaticApply ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), ``` -…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)`. +…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)` (also called ["call-binding"](https://npmjs.com/call-bind)). In other words, Node.js must **wrap** every global intrinsic method in a `this`-uncurried **wrapper function**, From 1043b49e5fc404e072095dc241dbd8aa678a3de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 13:12:18 -0400 Subject: [PATCH 08/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Fi?= =?UTF-8?q?x=20inconsistent=20RHS=20See=20https://github.com/js-choi/propo?= =?UTF-8?q?sal-bind-this-operator/pull/7#discussion=5Fr719598071.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0fcd941..0d8ddb5 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ ECMAScript Stage-0 Proposal. J. S. Choi, 2021. (A [formal specification][] is available.) **Method binding** `->` is a **left-associative infix operator**. -Its right-hand side is a **chain of identifiers**, **identifier** (like `f` or `o.x.y`), -or an **expression** in `(` `)` (like `hof()`), +Its right-hand side is an **identifier** (like `f`) +or a parenthesized **expression** (like `hof()`), either of which must evaluate to a **function**. Its left-hand side is some expression that evaluates to an **object**. The `->` operator **binds** its left-hand side @@ -24,10 +24,6 @@ equivalent to `fn.bind(arr)`, except that its behavior does **not change** if code elsewhere **reassigns** the **global method** `Function.prototype.bind`. -The right-hand side may be a property-access chain. -`arr->o.x.y` would be roughly -equivalent to `o.x.y.bind(arr)`. - Likewise, `obj->(createMethod())` would be roughly equivalent to `createMethod().bind(obj)`. From d861ef8a2b32ea09f005dbdd764b7a25522820d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 13:27:00 -0400 Subject: [PATCH 09/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Node:=20Small=20f?= =?UTF-8?q?ormatting=20nits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d8ddb5..e01ee06 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,10 @@ ArrayPrototypePush: uncurryThis(Array.prototype.push), ArrayPrototypePushApply: UncurryThisStaticApply ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), ``` -…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)` (also called ["call-binding"](https://npmjs.com/call-bind)). +…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)` +(also called [“call-binding”][call-bind]). + +[call-bind]: https://npmjs.com/call-bind In other words, Node.js must **wrap** every global intrinsic method in a `this`-uncurried **wrapper function**, From e8537951f21a8f2b3575c999003f3568018760d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 14:10:15 -0400 Subject: [PATCH 10/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20ho?= =?UTF-8?q?f()=20RHS=20must=20be=20(hof())=20https://github.com/js-choi/pr?= =?UTF-8?q?oposal-bind-this-operator/pull/7#pullrequestreview-768165829?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e01ee06..8db2825 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ECMAScript Stage-0 Proposal. J. S. Choi, 2021. **Method binding** `->` is a **left-associative infix operator**. Its right-hand side is an **identifier** (like `f`) -or a parenthesized **expression** (like `hof()`), +or a parenthesized **expression** (like `(hof())`), either of which must evaluate to a **function**. Its left-hand side is some expression that evaluates to an **object**. The `->` operator **binds** its left-hand side From 5ab70dcacd112bf64fa5c0affbfe5eabe98a19d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=C2=A0Choi?= Date: Thu, 30 Sep 2021 14:18:15 -0400 Subject: [PATCH 11/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Fi?= =?UTF-8?q?x=20Function.call=20equivalence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bradley Farias --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8db2825..17dc223 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Both are **exotic objects** that do not have a `prototype` property, and which may be called like any typical function. From this definition, `o->f(...args)` -is **indistinguishable** from `Function.prototype.call(o, ...args)`, +is **indistinguishable** from `Function.prototype.call(f, o, ...args)`, except that its behavior does **not change** if code elsewhere **reassigns** the global method `Function.prototype.call`. From 8767fa694f2e56834402614ce9cdb01d3f536aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=C2=A0Choi?= Date: Thu, 30 Sep 2021 14:18:48 -0400 Subject: [PATCH 12/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Node:=20Clarify?= =?UTF-8?q?=20wrapped=20intrinsic=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bradley Farias --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 17dc223..935e331 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), [call-bind]: https://npmjs.com/call-bind -In other words, Node.js must **wrap** every global intrinsic method +In other words, Node.js must **wrap** every `this` sensitive intrinsic method in a `this`-uncurried **wrapper function**, whose first argument is the method’s `this` value, using the `uncurryThis` helper function. From 015cd2972f3694cdd0f72f756a9eff039bd3ad83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 14:38:20 -0400 Subject: [PATCH 13/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Node:=20Clarify?= =?UTF-8?q?=20wrapped=20intrinsic=20methods=20more?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 935e331..2242f9e 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,12 @@ Only minor formatting changes have been made to the status-quo examples. Node.js’s runtime depends on many built-in JavaScript global intrinsic objects that are vulnerable to mutation or prototype pollution by third-party libraries. When initializing a JavaScript runtime, Node.js therefore caches -wrapped versions of all global intrinsic objects (and their methods) +wrapped versions of every global intrinsic object (and its methods) in a [large `primordials` object][primordials.js]. -This `primordials` object contains numerous entries that look like this: + +Many of the global intrinsic methods inside of the `primordials` object +rely on the `this` binding. +`primordials` therefore contains numerous entries that look like this: ```js ArrayPrototypeConcat: uncurryThis(Array.prototype.concat), ArrayPrototypeCopyWithin: uncurryThis(Array.prototype.copyWithin), @@ -194,12 +197,12 @@ ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), [call-bind]: https://npmjs.com/call-bind -In other words, Node.js must **wrap** every `this` sensitive intrinsic method +In other words, Node.js must **wrap** every `this`-sensitive global intrinsic method in a `this`-uncurried **wrapper function**, whose first argument is the method’s `this` value, using the `uncurryThis` helper function. -The result is that code that uses these global intrinsics, +The result is that code that uses these global intrinsic methods, like this code adapted from [node/lib/internal/v8_prof_processor.js][]: ```js // specifier is a string. From e2291164dc8b47b0fb1ef179431cf50608570d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=C2=A0Choi?= Date: Thu, 30 Sep 2021 16:01:17 -0400 Subject: [PATCH 14/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Description:=20Fi?= =?UTF-8?q?x=20erroneous=20.call=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jordan Harband --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2242f9e..d304ae2 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Both are **exotic objects** that do not have a `prototype` property, and which may be called like any typical function. From this definition, `o->f(...args)` -is **indistinguishable** from `Function.prototype.call(f, o, ...args)`, +is **indistinguishable** from `f.call(o, ...args)`, except that its behavior does **not change** if code elsewhere **reassigns** the global method `Function.prototype.call`. From 04e752738c7ca80d5bae018f5dac94112530976a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 30 Sep 2021 16:27:59 -0400 Subject: [PATCH 15/15] =?UTF-8?q?explainer=20=C2=A7=C2=A0Node:=20Use=20Fun?= =?UTF-8?q?ction.apply;=20other=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d304ae2..a711022 100644 --- a/README.md +++ b/README.md @@ -189,11 +189,12 @@ ArrayPrototypeFindIndex: uncurryThis(Array.prototype.findIndex), ArrayPrototypeLastIndexOf: uncurryThis(Array.prototype.lastIndexOf), ArrayPrototypePop: uncurryThis(Array.prototype.pop), ArrayPrototypePush: uncurryThis(Array.prototype.push), -ArrayPrototypePushApply: UncurryThisStaticApply +ArrayPrototypePushApply: applyBind(Array.prototype.push), ArrayPrototypeReverse: uncurryThis(Array.prototype.reverse), ``` -…and so on, where `uncurryThis` is `Function.bind.bind(Function.call)` -(also called [“call-binding”][call-bind]). +…and so on, where `uncurryThis` is `Function.prototype.call.bind` +(also called [“call-binding”][call-bind]), +and `applyBind` is the similar `Function.prototype.apply.bind`. [call-bind]: https://npmjs.com/call-bind @@ -205,11 +206,10 @@ using the `uncurryThis` helper function. The result is that code that uses these global intrinsic methods, like this code adapted from [node/lib/internal/v8_prof_processor.js][]: ```js - // specifier is a string. + // `specifier` is a string. const file = specifier.slice(2, -4); -``` -… -```js + + // Later… if (process.platform === 'darwin') { tickArguments.push('--mac'); } else if (process.platform === 'win32') { @@ -222,27 +222,25 @@ like this code adapted from [node/lib/internal/v8_prof_processor.js][]: // Note: This module assumes that it runs before any third-party code. const { ArrayPrototypePush, + ArrayPrototypePushApply, ArrayPrototypeSlice, StringPrototypeSlice, } = primordials; -``` -… -```js + + // Later… const file = StringPrototypeSlice(specifier, 2, -4); -``` -… -```js + + // Later… if (process.platform === 'darwin') { ArrayPrototypePush(tickArguments, '--mac'); } else if (process.platform === 'win32') { ArrayPrototypePush(tickArguments, '--windows'); } - ArrayPrototypePush(tickArguments, - ...ArrayPrototypeSlice(process.argv, 1)); + ArrayPrototypePushApply(tickArguments, ArrayPrototypeSlice(process.argv, 1)); ``` This code is now protected against prototype pollution by accident and by adversaries -(e.g., `delete Array.prototype.push`). +(e.g., `delete Array.prototype.push` or `delete Array.prototype[Symbol.iterator]`). However, this protection comes at two costs: 1. These [uncurried wrapper functions sometimes dramatically reduce performance][#38248]. @@ -262,22 +260,21 @@ without worrying about `Function.prototype.call` mutation: ```js // Note: This module assumes that it runs before any third-party code. +const $apply = Function.prototype.apply; const $push = Array.prototype.push; const $arraySlice = Array.prototype.slice; const $stringSlice = String.prototype.slice; -``` -… -```js - const file = specifier->stringSlice(2, -4); -``` -… -```js + + // Later… + const file = specifier->$stringSlice(2, -4); + + // Later… if (process.platform === 'darwin') { - tickArguments->push('--mac'); + tickArguments->$push('--mac'); } else if (process.platform === 'win32') { - tickArguments->push('--windows'); + tickArguments->$push('--windows'); } - tickArguments->push(...process.argv->slice(1)); + $push->$apply(tickArguments, process.argv->$arraySlice(1)); ``` Performance has improved, and readability has improved.