JavaScript: The Bad Parts
A (biased) JSVM engineers’ perspective
Benedikt Meurer
@bmeurer
Disclaimer
“I don’t do JavaScript!”
with statement
with statement
const o = {� x: 1,� y: 2�};
�with (o) {� x = x + 1;�}
�// o.x is 2 now
with binds object properties
to (locally) scoped names, looks like a handy feature!
with statement
class A {� constructor() { this.x = 0; }� bar() { return "Bar"; }
}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}
�// a.x is 1 now
with statement
class A {� constructor() { this.x = 0; }
bar() { return "Bar"; }�}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}
�// a.x is 1 now
class A {� constructor() { this.x = 0; }� foo() { return "Bar"; }�}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}
�// a.x is "Bar" now
with statement
var keys = [];�with (Array.prototype) {� keys.push('foo');�}
with statement
Object.keys(Array.prototype[Symbol.unscopables]);�// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']��var keys = [];�with (Array.prototype) {� keys.push('foo');�}
“The Array.prototype[Symbol.unscopables] property contains property names that were not included in the ECMAScript standard prior to the ES2015 version. These properties are excluded from with statement bindings.”
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables
with statement
Built-in subclassing
Array subclassing
class MyArray extends Array { }��const a = new MyArray(1, 2, 3);��const b = a.filter(x => x > 1);
�// Now b is also an instance of MyArray with [2, 3]
Array subclassing
Array subclassing
Sample Array[@@species] bug (CVE-2016-7200, Edge)
class Dummy {� // Always constructs a non-empty Array instance
constructor() { return [1, 2, 3]; }�}��class MyArray extends Array {� static get [Symbol.species]() { return Dummy; }�}��var a = new MyArray({}, [], "natalie", 7, 7, 7, 7, 7);�var o = a.filter(i => true); // Memory corruption!
Array[@@species] vulnerabilities
Array indexed accessor bugs with Array subclassing
Array subclassing security
Performance impact of Promise subclassing
Temporal dead zone
vs.�Transpilers
Temporal dead zone
“In ECMAScript 6, accessing a let or const variable before its declaration (within its scope) causes a ReferenceError. The time span when that happens, between the creation of a variable’s binding and its declaration, is called the temporal dead zone.”
-- Axel Rauschmayer
RxJS Observable example
forEach(next: (value: T) => void, PromiseCtor?: typeof Promise): Promise<void> {� return new PromiseCtor<void>((resolve, reject) => {� const subscription = this.subscribe(value => {� if (subscription) {� try {� next(value);� } catch (err) {� reject(err);� subscription.unsubscribe();� }� } else {� next(value);� }� }, reject, resolve);� });�}
subscription might be accessed
before the assignment happens
=> ReferenceError
RxJS Observable example
forEach(next: (value: T) => void, PromiseCtor?: typeof Promise): Promise<void> {� return new PromiseCtor<void>((resolve, reject) => {� const subscription = this.subscribe(value => {� if (subscription) {� try {� next(value);� } catch (err) {� reject(err);� subscription.unsubscribe();� }� } else {� next(value);� }� }, reject, resolve);� });�}
TypeScript was hiding the bug because it turns the const into
var during transpilation
RxJS Observable example
Simplified example
function foo(c) { return c(); }��const x = foo(() => x ? 1 : 2 );��// ReferenceError: x is not defined
function foo(c) { return c(); }��var x = foo(function() {� return x ? 1 : 2;�});��// x is assigned the value 2
Conclusion