@@ -81,219 +81,162 @@ is equivalent to `a->(createFn())`.
81
81
There are ** no other special rules** .
82
82
83
83
## Why a bind-` this ` operator
84
- [ ` Function.prototype.bind ` ] [ call ] and [ ` Function.prototype.call ` ] [ bind ]
85
- are very common in ** object-oriented JavaScript** code.
86
- They are useful methods that allows us to apply functions to any object,
87
- binding their first arguments to the ` this ` bindings within those functions,
88
- no matter the current object environment.
89
- ` bind ` and ` call ` allow us to ** extend** an ** object** with a function
90
- as if that function were ** its own method** .
91
- They serve as an important link between
92
- the ** object-oriented** and ** functional** styles in JavaScript.
84
+ In short:
85
+
86
+ 1 . [ ` .bind ` ] [ bind ] , [ ` .call ` ] [ call ] , and [ ` .apply ` ] [ apply ]
87
+ are very useful and very common in JavaScript codebases.
88
+ 2 . But ` .bind ` , ` .call ` , and ` .apply ` are clunky and unergonomic.
93
89
94
90
[ bind ] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
95
91
[ call ] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
96
-
97
- Why then would we need an operator that does the same thing?
98
- Because ` bind ` and ` call ` are vulnerable to ** global mutation** .
99
-
100
- For example, when we run our code in an untrusted environment,
101
- an adversary may mutate global prototype objects
102
- such as ` Array.prototype ` ,
103
- reassigning or deleting their methods.
104
-
105
- ``` js
106
- // The adversary’s code.
107
- delete Array .prototype .slice ;
108
-
109
- // Our own trusted code, running later.
110
- // Due to the adversary, this unexpectedly throws an error.
111
- [0 , 1 , 2 ].slice (1 , 2 );
92
+ [ apply ] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
93
+
94
+ ### ` .bind ` , ` .call ` , and ` .apply ` are very common
95
+ The dynamic ` this ` binding is a fundamental part of JavaScript design and practice today.
96
+ Because of this, developers frequently need to change the ` this ` binding.
97
+ ` .bind ` , ` .call ` , and ` .apply ` are arguably three of the most commonly used functions
98
+ in all JavaScript.
99
+
100
+ We can estimate ` .bind ` , ` .call ` , and ` .apply ` ’s prevalences using [ Node Gzemnid] [ ] .
101
+ Although [ Gzemnid can be deceptive] [ ] , we are only seeking rough estimations.
102
+
103
+ [ Node Gzemnid ] : https://github.com/nodejs/Gzemnid
104
+ [ Gzemnid can be deceptive ] : https://github.com/nodejs/Gzemnid/blob/main/README.md#deception
105
+
106
+ First, we download the 2019-06-04 [ pre-built Gzemnid dataset] [ ]
107
+ for the top-1000 downloaded NPM packages.
108
+ We also need Gzemnid’s ` search.topcode.sh ` script in the same active directory,
109
+ which in turn requires the lz4 command suite.
110
+ ` search.topcode.sh ` will output lines of code from the top-1000 packages
111
+ that match the given regular expression.
112
+
113
+ [ pre-built NPM dataset ] : https://gzemnid.nodejs.org/datasets/
114
+
115
+ ``` bash
116
+ ./search.topcode.sh ' \.call\b' | head
117
+ grep -aE " \.call\b"
118
+ 177726827 debug-4.1.1.tgz/src/common.js:101: match = formatter.call(self, val);
119
+ 177726827 debug-4.1.1.tgz/src/common.js:111: createDebug.formatArgs.call(self, args);
120
+ 154772106 kind-of-6.0.2.tgz/index.js:54: type = toString.call(val);
121
+ 139612972 readable-stream-3.4.0.tgz/errors-browser.js:26: return _Base.call(this, getMessage(arg1, arg2, arg3)) || this;
122
+ 139612972 readable-stream-3.4.0.tgz/lib/_stream_duplex.js:60: Readable.call(this, options);
123
+ 139612972 readable-stream-3.4.0.tgz/lib/_stream_duplex.js:61: Writable.call(this, options);
124
+ 139612972 readable-stream-3.4.0.tgz/lib/_stream_passthrough.js:34: Transform.call(this, options);
125
+ 139612972 readable-stream-3.4.0.tgz/lib/_stream_readable.js:183: Stream.call(this);
126
+ 139612972 readable-stream-3.4.0.tgz/lib/_stream_readable.js:786: var res = Stream.prototype.on.call(this, ev, fn);
112
127
```
113
128
114
- In order to harden JavaScript applications against this attack,
115
- we can extract critical global prototype methods into variables
116
- before any untrusted code may run.
117
- We would then use our critical methods with their ` call ` methods.
118
-
119
- ``` js
120
- // Our own trusted code, running before any adversary.
121
- const { slice } = Array .prototype ;
122
-
123
- // The adversary’s code.
124
- delete Array .prototype .slice ;
125
-
126
- // Our own trusted code, running later.
127
- // In spite of the adversary, this no longer throws an error.
128
- slice .call ([0 , 1 , 2 ], 1 , 2 );
129
+ We use ` awk ` to count those matching lines of code
130
+ and compare their numbers for ` bind ` , ` call ` , ` apply ` ,
131
+ and several other frequently used functions.
132
+
133
+ ``` bash
134
+ > ls
135
+ search.topcode.sh
136
+ slim.topcode.1000.txt.lz4
137
+ > ./search.topcode.sh ' \.call\b' | awk ' END { print NR }'
138
+ 500084
139
+ > ./search.topcode.sh ' \.apply\b' | awk ' END { print NR }'
140
+ 225315
141
+ > ./search.topcode.sh ' \.bind\b' | awk ' END { print NR }'
142
+ 170248
143
+ > ./search.topcode.sh ' \b.map\b' | awk ' END { print NR }'
144
+ 1016503
145
+ > ./search.topcode.sh ' \bconsole.log\b' | awk ' END { print NR }'
146
+ 271915
147
+ > ./search.topcode.sh ' \.set\b' | awk ' END { print NR }'
148
+ 168872
149
+ > ./search.topcode.sh ' \.push\b' | awk ' END { print NR }'
150
+ 70116
129
151
```
130
152
131
- But this approach is still vulnerable to mutation of ` Function.prototype ` :
153
+ These results suggest that usage of ` .call ` , ` .bind ` , and ` .apply `
154
+ are comparable to usage of other frequently used standard functions.
155
+ In this dataset, their combined usage even exceeds that of ` console.log ` .
132
156
133
- ``` js
134
- // Our own trusted code, running before any adversary.
135
- const { slice } = Array .prototype ;
157
+ Obviously, [ this methodology has many pitfalls] [ Gzemnid can be deceptive ] ,
158
+ but we are only looking for roughly estimated orders of magnitude
159
+ relative to other baseline functions.
160
+ Gzemnid counts each library’s codebase only once; it does not double-count dependencies.
136
161
137
- // The adversary’s code.
138
- delete Array .prototype .slice ;
139
- delete Function .prototype .call ;
162
+ In fact, this method definitely underestimates the prevalences
163
+ of ` .bind ` , ` .call ` , and ` .apply `
164
+ by excluding the large JavaScript codebases of Node and Deno.
165
+ Node and Deno [ copiously use bound functions for security] [ security-use-case ]
166
+ hundreds or thousands of times.
140
167
141
- // Our own trusted code, running later.
142
- // Due to the adversary, this throws an error again.
143
- slice .call ([0 , 1 , 2 ], 1 , 2 );
144
- ```
145
-
146
- There is currently no way to harden code against mutation of ` Function.prototype ` .
147
- A new operator, however, would not be vulnerable to mutation:
168
+ [ security-use-case ] : https://github.com/js-choi/proposal-bind-this/blob/main/security-use-case.md
148
169
149
- ``` js
150
- // Our own trusted code, running before any adversary.
151
- const { slice } = Array . prototype ;
170
+ ### ` .bind ` , ` .call ` , and ` .apply ` are clunky
171
+ JavaScript developers are used to using methods in a [ noun–verb–noun word order ] [ ]
172
+ that resembles English and other [ SVO human languages ] [ ] : ` obj.fn(arg) ` .
152
173
153
- // The adversary’s code.
154
- delete Array .prototype .slice ;
155
- delete Function .prototype .call ;
174
+ [ SVO human languages ] : https://en.wikipedia.org/wiki/Category:Subject–verb–object_languages
156
175
157
- // Our own trusted code, running later.
158
- // In spite of the adversary, this no longer throws an error.
159
- // It also is considerably more readable.
160
- [0 , 1 , 2 ]- > slice (1 , 2 );
161
- ```
176
+ However, ` .bind ` , ` .call ` , and ` .apply ` flip this “natural” word order,
177
+ They flip the first noun and the verb,
178
+ and they interpose the verb’s ` Function.prototype ` method between them:
179
+ ` obj.call(arg) ` .
162
180
163
- As a bonus, the syntax is also considerably more readable
164
- than code that uses ` bind ` or ` call ` .
165
- A bound function could be called inline
166
- as if it were ** actually a method** in the object
167
- whose ** property key** is the ** function itself** :
181
+ Consider the following real-life code using ` .bind ` or ` .call ` ,
182
+ and compare them to versions that use the ` -> ` operator.
183
+ The difference is especially evident when you read them aloud.
168
184
169
185
``` js
170
- function extensionMethod () {
171
- return this ;
172
- }
186
+ // kind-of-6.0.2.tgz/index.js
187
+ type = toString . call (val) ;
188
+ type = val - > toString ();
173
189
174
- obj .actualMethod ();
175
- obj- > extensionMethod ();
176
- // Compare with extensionMethod.call(obj).
177
- ```
190
+ // debug-4.1.1.tgz/src/common.js
191
+ match = formatter .call (self , val);
192
+ match = self - > formatter (val);
178
193
179
- ## Real-world examples
180
- Only minor formatting changes have been made to the status-quo examples.
194
+ createDebug . formatArgs . call ( self , args);
195
+ self - > ( createDebug . formatArgs )(args);
181
196
182
- ### Node.js
183
- Node.js’s runtime depends on many built-in JavaScript global intrinsic objects
184
- that are vulnerable to mutation or prototype pollution by third-party libraries.
185
- When initializing a JavaScript runtime, Node.js therefore caches
186
- wrapped versions of every global intrinsic object (and its methods)
187
- in a [ large ` primordials ` object] [ primordials.js ] .
197
+ // readable-stream-3.4.0.tgz/errors-browser.js
198
+ return _Base .call (this , getMessage (arg1, arg2, arg3)) || this ;
199
+ return this - > _Base (getMessage (arg1, arg2, arg3)) || this ;
188
200
189
- Many of the global intrinsic methods inside of the ` primordials ` object
190
- rely on the ` this ` binding.
191
- ` primordials ` therefore contains numerous entries that look like this:
192
- ``` js
193
- ArrayPrototypeConcat: uncurryThis (Array .prototype .concat ),
194
- ArrayPrototypeCopyWithin: uncurryThis (Array .prototype .copyWithin ),
195
- ArrayPrototypeFill: uncurryThis (Array .prototype .fill ),
196
- ArrayPrototypeFind: uncurryThis (Array .prototype .find ),
197
- ArrayPrototypeFindIndex: uncurryThis (Array .prototype .findIndex ),
198
- ArrayPrototypeLastIndexOf: uncurryThis (Array .prototype .lastIndexOf ),
199
- ArrayPrototypePop: uncurryThis (Array .prototype .pop ),
200
- ArrayPrototypePush: uncurryThis (Array .prototype .push ),
201
- ArrayPrototypePushApply: applyBind (Array .prototype .push ),
202
- ArrayPrototypeReverse: uncurryThis (Array .prototype .reverse ),
203
- ```
204
- …and so on, where ` uncurryThis ` is ` Function.prototype.call.bind `
205
- (also called [ “call-binding”] [ call-bind ] ),
206
- and ` applyBind ` is the similar ` Function.prototype.apply.bind ` .
201
+ // readable-stream-3.4.0.tgz/lib/_stream_readable.js
202
+ var res = Stream .prototype .on .call (this , ev, fn);
203
+ var res = this - > (Stream .prototype .on )(ev, fn);
207
204
208
- [ call-bind ] : https://npmjs.com/call-bind
205
+ var res = Stream .prototype .removeAllListeners .apply (this , arguments );
206
+ var res = this - > (Stream .prototype .removeAllListeners )(... arguments );
209
207
210
- In other words, Node.js must ** wrap** every ` this ` -sensitive global intrinsic method
211
- in a ` this ` -uncurried ** wrapper function** ,
212
- whose first argument is the method’s ` this ` value,
213
- using the ` uncurryThis ` helper function.
208
+ // yargs-13.2.4.tgz/lib/middleware.js
209
+ Array .prototype .push .apply (globalMiddleware, callback)
210
+ globalMiddleware- > (Array .prototype .push )(... callback)
214
211
215
- The result is that code that uses these global intrinsic methods,
216
- like this code adapted from [ node/lib/internal/v8_prof_processor.js] [ ] :
217
- ``` js
218
- // `specifier` is a string.
219
- const file = specifier .slice (2 , - 4 );
220
-
221
- // Later…
222
- if (process .platform === ' darwin' ) {
223
- tickArguments .push (' --mac' );
224
- } else if (process .platform === ' win32' ) {
225
- tickArguments .push (' --windows' );
226
- }
227
- tickArguments .push (... process .argv .slice (1 ));
228
- ```
229
- …must instead look like this:
230
- ``` js
231
- // Note: This module assumes that it runs before any third-party code.
232
- const {
233
- ArrayPrototypePush ,
234
- ArrayPrototypePushApply ,
235
- ArrayPrototypeSlice ,
236
- StringPrototypeSlice ,
237
- } = primordials;
238
-
239
- // Later…
240
- const file = StringPrototypeSlice (specifier, 2 , - 4 );
241
-
242
- // Later…
243
- if (process .platform === ' darwin' ) {
244
- ArrayPrototypePush (tickArguments, ' --mac' );
245
- } else if (process .platform === ' win32' ) {
246
- ArrayPrototypePush (tickArguments, ' --windows' );
247
- }
248
- ArrayPrototypePushApply (tickArguments, ArrayPrototypeSlice (process .argv , 1 ));
249
- ```
212
+ // yargs-13.2.4.tgz/lib/command.js
213
+ [].push .apply (positionalKeys, parsed .aliases [key])
250
214
251
- This code is now protected against prototype pollution by accident and by adversaries
252
- (e.g., ` delete Array.prototype.push ` or ` delete Array.prototype[Symbol.iterator] ` ).
253
- However, this protection comes at two costs:
215
+ // pretty-format-24.8.0.tgz/build-es5/index.js
216
+ var code = fn . apply (colorConvert, arguments );
217
+ var code = colorConvert - > fn ( ... arguments );
254
218
255
- 1 . These [ uncurried wrapper functions sometimes dramatically reduce performance] [ #38248 ] .
256
- This would not be a problem if Node.js could cache
257
- and use the intrinsic methods directly.
258
- But the only current way to use intrinsic methods
259
- would be with ` Function.prototype.call ` , which is also vulnerable to mutation.
219
+ // q-1.5.1.tgz/q.js
220
+ return value .apply (thisp, args);
221
+ return thisp- > value (... args);
260
222
261
- 2 . The Node.js community has had [ much concern about barriers to contribution ] [ #30697 ]
262
- by ordinary JavaScript developers, due to the unidiomatic code encouraged by these
263
- uncurried wrapper functions.
223
+ // rxjs-6.5.2.tgz/src/internal/operators/every.ts
224
+ result = this . predicate . call ( this . thisArg , value, this . index ++ , this . source );
225
+ result = this . thisArg - > ( this . predicate )(value, this . index ++ , this . source );
264
226
265
- Both of these problems are much improved by the bind-` this ` operator.
266
- Instead of wrapping every global method with ` uncurryThis ` ,
267
- Node.js could cached and used ** directly**
268
- without worrying about ` Function.prototype.call ` mutation:
227
+ // bluebird-3.5.5.tgz/js/release/synchronous_inspection.js
228
+ return isPending .call (this ._target ());
229
+ return this ._target ()- > isPending ();
269
230
270
- ``` js
271
- // Note: This module assumes that it runs before any third-party code.
272
- const $apply = Function .prototype .apply ;
273
- const $push = Array .prototype .push ;
274
- const $arraySlice = Array .prototype .slice ;
275
- const $stringSlice = String .prototype .slice ;
276
-
277
- // Later…
278
- const file = specifier- > $stringSlice (2 , - 4 );
279
-
280
- // Later…
281
- if (process .platform === ' darwin' ) {
282
- tickArguments- > $push (' --mac' );
283
- } else if (process .platform === ' win32' ) {
284
- tickArguments- > $push (' --windows' );
285
- }
286
- $push- > $apply (tickArguments, process .argv - > $arraySlice (1 ));
287
- ```
231
+ var matchesPredicate = tryCatch (item).call (boundTo, e);
232
+ var matchesPredicate = boundTo- > (tryCatch (item))(e);
288
233
289
- Performance has improved, and readability has improved.
290
- There are no more uncurried wrapper functions;
291
- instead, the code uses the intrinsic methods in a notation
292
- similar to normal method calling with ` . ` .
234
+ // graceful-fs-4.1.15.tgz/polyfills.js
235
+ return fs$read . call (fs, fd, buffer, offset, length, position, callback)
236
+ return fs - > fs$read (fd, buffer, offset, length, position, callback)
237
+ ```
293
238
294
- [ node/lib/internal/v8_prof_processor.js ] : https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/v8_prof_processor.js
295
- [ #38248 ] : https://github.com/nodejs/node/pull/38248
296
- [ #30697 ] : https://github.com/nodejs/node/issues/30697
239
+ [ noun–verb–noun word order ] : https://en.wikipedia.org/wiki/Subject–verb–object
297
240
298
241
## Non-goals
299
242
A goal of this proposal is ** simplicity** .
0 commit comments