Conditional repair accessors
Simpler implementation
MODIFY
.travis.yml
.travis.yml
@@ -3,6 +3,7 @@ node_js:
- "10"
- "9"
- "8"
+ - "8.0"
- "7"
- "6"
@@ -26,5 +27,6 @@ branches:
matrix:
fast_finish: true
allow_failures:
+ - node_js: "8.0"
- node_js: "7"
- node_js: "6"

ADD
shim/LICENSE-corejs
+Copyright (c) 2014-2018 Denis Pushkarev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

MODIFY
shim/NOTICE
shim/NOTICE
@@ -5,4 +5,5 @@ This product includes software developed at TC39 (http://ecma-international.org/
Includes modified work from:
- Aura Locker Service Copyright 2015 Salesforce.com Inc under the Apache 2.0 license (https://github.com/forcedotcom/aura)
- Caja Copyright 2011 Google Inc under the Apache 2.0 license (https://github.com/google/caja)
+- CoreJS Copyright (c) 2014-2018 Denis Pushkarev under the MIT license (https://github.com/zloirock/core-js)
- V8 Copyright 2008 Google Inc under the BSD license (https://github.com/v8/v8)

MODIFY
shim/src/accessors.js
shim/src/accessors.js
/* istanbul ignore file */
export function repairAccessors() {
const {
- getPrototypeOf,
defineProperty,
+ defineProperties,
getOwnPropertyDescriptor,
+ getPrototypeOf,
prototype: objectPrototype
} = Object;
@@ -32,52 +33,78 @@ export function repairAccessors() {
// so we need to repair these for security. Thus it is our responsibility to fix
// this, and we need to include repairAccessors. E.g. Chrome in 2016.
- // todo: this shim should only be applied if the security bug is present.
+ try {
+ // Verify that the method is not callable.
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ objectPrototype.__lookupGetter__('dummy');
+ } catch (ignore) {
+ // Throws, no need to patch.
+ return;
+ }
+
+ function toObject(obj) {
+ if (obj === undefined || obj === null) {
+ throw new TypeError(`can't convert undefined or null to object`);
+ }
+ return Object(obj);
+ }
+
+ function asPropertyName(obj) {
+ if (typeof obj === 'symbol') {
+ return obj;
+ }
+ return `${obj}`;
+ }
+
+ function aFunction(obj, accessor) {
+ if (typeof obj !== 'function') {
+ throw TypeError(`invalid ${accessor} usage`);
+ }
+ return obj;
+ }
- function makeDefineAccessor(method, accessor) {
- defineProperty(objectPrototype, method, {
- value(prop, func) {
- const result = defineProperty(this, prop, {
- [accessor]: func,
+ defineProperties(objectPrototype, {
+ __defineGetter__: {
+ value: function __defineGetter__(prop, func) {
+ const O = toObject(this);
+ defineProperty(O, prop, {
+ get: aFunction(func, 'getter'),
enumerable: true,
configurable: true
});
- // Note that we cannot assume that defineProperty reports failure by throwing.
- // To fix an obscure problem (link needed), defineProperty is now allowed to
- // report failure by returning false as well.
- if (result === false) {
- throw new TypeError(`Cannot redefine property: ${[prop]}`);
- }
}
+ },
+ __defineSetter__: {
+ value: function __defineSetter__(prop, func) {
+ const O = toObject(this);
+ defineProperty(O, prop, {
+ set: aFunction(func, 'setter'),
+ enumerable: true,
+ configurable: true
});
}
-
- makeDefineAccessor('__defineGetter__', 'get');
- makeDefineAccessor('__defineSetter__', 'set');
-
- // TOCTTOU and .asString() games could enable attacker to skip some
- // intermediate ancestors, so we stringify/propify this once, first.
- function asPropertyName(prop) {
- if (typeof prop === 'symbol') {
- return prop;
+ },
+ __lookupGetter__: {
+ value: function __lookupGetter__(prop) {
+ let O = toObject(this);
+ prop = asPropertyName(prop);
+ let desc;
+ while (O && !(desc = getOwnPropertyDescriptor(O, prop))) {
+ O = getPrototypeOf(O);
}
- return `${prop}`;
+ return desc && desc.get;
}
-
- function makeLookupAccessor(method, accessor) {
- defineProperty(objectPrototype, method, {
- value(prop) {
- prop = asPropertyName(prop); // sanitize property name/symbol
- let base = this;
+ },
+ __lookupSetter__: {
+ value: function __lookupSetter__(prop) {
+ let O = toObject(this);
+ prop = asPropertyName(prop);
let desc;
- while (base && !(desc = getOwnPropertyDescriptor(base, prop))) {
- base = getPrototypeOf(base);
+ while (O && !(desc = getOwnPropertyDescriptor(O, prop))) {
+ O = getPrototypeOf(O);
}
- return desc && desc[accessor];
+ return desc && desc.set;
}
- });
}
-
- makeLookupAccessor('__lookupGetter__', 'get');
- makeLookupAccessor('__lookupSetter__', 'set');
+ });
}

ADD
shim/test/realm/accessors.js
+// Adapted from CoreJS Copyright (c) 2014-2018 Denis Pushkarev.
+// This code is governed by the MIT license found in the LICENSE file.
+
+import test from 'tape';
+import { repairAccessors } from '../../src/accessors';
+
+/* eslint-disable no-restricted-properties, no-underscore-dangle */
+
+repairAccessors();
+
+const {
+ create,
+ prototype: { __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__ }
+} = Object;
+
+test('Object#__defineGetter__', t => {
+ t.plan(8);
+
+ t.equal(typeof __defineGetter__, 'function');
+ t.equal(__defineGetter__.length, 2);
+ t.equal(__defineGetter__.name, '__defineGetter__');
+
+ const object = {};
+ t.equal(object.__defineGetter__('key', () => 42), undefined, 'void');
+ t.equal(object.key, 42, 'works');
+
+ object.__defineSetter__('key', function() {
+ this.foo = 43;
+ });
+ object.key = 44;
+ t.ok(object.key === 42 && object.foo === 43, 'works with setter');
+
+ t.throws(() => __defineGetter__.call(null, 1, () => {}), TypeError, 'Throws on null as `this`');
+ t.throws(
+ () => __defineGetter__.call(undefined, 1, () => {}),
+ TypeError,
+ 'Throws on undefined as `this`'
+ );
+});
+
+test('Object#__defineSetter__', t => {
+ t.plan(8);
+
+ t.equal(typeof __defineSetter__, 'function');
+ t.equal(__defineSetter__.length, 2);
+ t.equal(__defineSetter__.name, '__defineSetter__');
+
+ const object = {};
+ t.equal(
+ object.__defineSetter__('key', function() {
+ this.foo = 43;
+ }),
+ undefined,
+ 'void'
+ );
+ object.key = 44;
+ t.equal(object.foo, 43, 'works');
+
+ object.__defineSetter__('key', function() {
+ this.foo = 43;
+ });
+ object.__defineGetter__('key', () => 42);
+ object.key = 44;
+ t.ok(object.key === 42 && object.foo === 43, 'works with getter');
+
+ t.throws(
+ () =>
+ __defineSetter__.call(null, 1, () => {
+ /* empty */
+ }),
+ TypeError,
+ 'Throws on null as `this`'
+ );
+ t.throws(
+ () =>
+ __defineSetter__.call(undefined, 1, () => {
+ /* empty */
+ }),
+ TypeError,
+ 'Throws on undefined as `this`'
+ );
+});
+
+test('Object#__lookupGetter__', t => {
+ t.plan(11);
+
+ t.equal(typeof __lookupGetter__, 'function');
+ t.equal(__lookupGetter__.length, 1);
+ t.equal(__lookupGetter__.name, '__lookupGetter__');
+ // assert.looksNative(__lookupGetter__);
+ t.equal(Object.getOwnPropertyDescriptor(Object.prototype, '__lookupGetter__').enumerable, false);
+ t.equal({}.__lookupGetter__('key'), undefined, 'empty object');
+ t.equal({ key: 42 }.__lookupGetter__('key'), undefined, 'data descriptor');
+
+ const object = {};
+ function setter() {}
+ object.__defineGetter__('key', setter);
+
+ t.equal(object.__lookupGetter__('key'), setter, 'own getter');
+ t.equal(create(object).__lookupGetter__('key'), setter, 'proto getter');
+ t.equal(create(object).__lookupGetter__('foo'), undefined, 'empty proto');
+
+ t.throws(() => __lookupGetter__.call(null, 1, () => {}), TypeError, 'Throws on null as `this`');
+ t.throws(
+ () => __lookupGetter__.call(undefined, 1, () => {}),
+ TypeError,
+ 'Throws on undefined as `this`'
+ );
+});
+
+test('Object#__lookupSetter__', t => {
+ t.plan(11);
+
+ t.equal(typeof __lookupSetter__, 'function');
+ t.equal(__lookupSetter__.length, 1);
+ t.equal(__lookupSetter__.name, '__lookupSetter__');
+ // assert.looksNative(__lookupSetter__);
+ t.equal(Object.getOwnPropertyDescriptor(Object.prototype, '__lookupSetter__').enumerable, false);
+ t.equal({}.__lookupSetter__('key'), undefined, 'empty object');
+ t.equal({ key: 42 }.__lookupSetter__('key'), undefined, 'data descriptor');
+
+ const object = {};
+ function setter() {}
+ object.__defineSetter__('key', setter);
+
+ t.equal(object.__lookupSetter__('key'), setter, 'own getter');
+ t.equal(create(object).__lookupSetter__('key'), setter, 'proto getter');
+ t.equal(create(object).__lookupSetter__('foo'), undefined, 'empty proto');
+
+ t.throws(() => __lookupSetter__.call(null, 1, () => {}), TypeError, 'Throws on null as `this`');
+ t.throws(
+ () => __lookupSetter__.call(undefined, 1, () => {}),
+ TypeError,
+ 'Throws on undefined as `this`'
+ );
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineGetter__/getter-non-callable.js
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-additional-properties-of-the-object.prototype-object
+description: Behavior when getter is not callable
+info: |
+ [...]
+ 2. If IsCallable(getter) is false, throw a TypeError exception.
+features: [Symbol]
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineGetter__/getter-non-callable.js', t => {
+ t.plan(7);
+
+ const test = () => {
+ const subject = {};
+ const symbol = Symbol('');
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(typeof Object.prototype.__defineGetter__, 'function');
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineGetter__(key, '');
+ },
+ TypeError,
+ 'string'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineGetter__(key, 23);
+ },
+ TypeError,
+ 'number'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineGetter__(key, true);
+ },
+ TypeError,
+ 'boolean'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineGetter__(key, symbol);
+ },
+ TypeError,
+ 'symbol'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineGetter__(key, {});
+ },
+ TypeError,
+ 'object'
+ );
+
+ t.equal(toStringCount, 0);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineSetter__/setter-non-callable.js
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-additional-properties-of-the-object.prototype-object
+description: Behavior when getter is not callable
+info: |
+ [...]
+ 2. If IsCallable(setter) is false, throw a TypeError exception.
+features: [Symbol]
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineSetter__/setter-non-callable.js', t => {
+ t.plan(7);
+
+ const test = () => {
+ const subject = {};
+ const symbol = Symbol('');
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(typeof Object.prototype.__defineSetter__, 'function');
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineSetter__(key, '');
+ },
+ TypeError,
+ 'string'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineSetter__(key, 23);
+ },
+ TypeError,
+ 'number'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineSetter__(key, true);
+ },
+ TypeError,
+ 'boolean'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineSetter__(key, symbol);
+ },
+ TypeError,
+ 'symbol'
+ );
+
+ t.throws(
+ () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ subject.__defineSetter__(key, {});
+ },
+ TypeError,
+ 'object'
+ );
+
+ t.equal(toStringCount, 0);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

MODIFY
shim/test/test262/annexB/built-ins/Object/prototype/__defineSetter__/this-non-obj.js
shim/test/test262/annexB/built-ins/Object/prototype/__defineSetter__/this-non-obj.js
@@ -24,6 +24,7 @@ test('test262/annexB/built-ins/Object/prototype/__defineSetter__/this-non-obj.js
}
};
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
t.equal(typeof __defineSetter__, 'function');
t.throws(

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__lookupGetter__/this-non-obj.js
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-additional-properties-of-the-object.prototype-object
+description: Behavior when "this" value is not Object-coercible
+info: |
+ 1. Let O be ? ToObject(this value).
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupGetter__/this-non-obj.js', t => {
+ t.plan(4);
+
+ const test = () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const __lookupGetter__ = Object.prototype.__lookupGetter__;
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ t.equal(typeof __lookupGetter__, 'function');
+
+ t.throws(
+ () => {
+ __lookupGetter__.call(undefined, key);
+ },
+ TypeError,
+ 'undefined'
+ );
+
+ t.throws(
+ () => {
+ __lookupGetter__.call(null, key);
+ },
+ TypeError,
+ 'null'
+ );
+
+ t.equal(toStringCount, 0);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__lookupSetter__/this-non-obj.js
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-additional-properties-of-the-object.prototype-object
+description: Behavior when "this" value is not Object-coercible
+info: |
+ 1. Let O be ? ToObject(this value).
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupSetter__/this-non-obj.js', t => {
+ t.plan(4);
+
+ const test = () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const __lookupGetter__ = Object.prototype.__lookupGetter__;
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ t.equal(typeof __lookupGetter__, 'function');
+
+ t.throws(
+ () => {
+ __lookupGetter__.call(undefined, key);
+ },
+ TypeError,
+ 'undefined'
+ );
+
+ t.throws(
+ () => {
+ __lookupGetter__.call(null, key);
+ },
+ TypeError,
+ 'null'
+ );
+
+ t.equal(toStringCount, 0);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

Conditional repair accessors
Simpler implementation