Simplify Accessors (DRY)
MODIFY
.travis.yml
.travis.yml
@@ -26,6 +26,5 @@ branches:
matrix:
fast_finish: true
allow_failures:
- - node_js:
- - "7"
- - "6"
+ - node_js: "7"
+ - node_js: "6"

MODIFY
shim/src/accessors.js
shim/src/accessors.js
/**
* Replace the legacy accessors of Object to comply with strict mode
- * and ES2016 semantics, we do this by redefining them while in 'use strict'
- * https://tc39.github.io/ecma262/#sec-object.prototype.__defineGetter__
-
-We need this repair, but would it be included when the real realms is
-integrated into the language. If not, what are we getting here?
-
-Also note that this changes the primal versions.
-
-On some platforms, the implementation of these functions act as if they are
-in sloppy mode: if they're invoked badly, they will expose the global object,
-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: It would be better to detect and only repair the functions that have
-the bug.
+ * and ES2016 semantics, we do this by redefining them while in 'use strict'.
+ *
+ * todo: list the issues resolved
+ *
+ * This function can be used in two ways: (1) invoked directly to fix the primal
+ * realm's Object.prototype, and (2) converted to a string to be executed
+ * inside each new RootRealm to fix their Object.prototypes. Evaluation requires
+ * the function to have no dependencies, so don't import anything from the outside.
*/
-// todo: this file should be moved out to a separate repo and npm module
-
-// We use this function in two ways. We use it directly to fix the primal
-// realm's Object.prototype, and we convert it into a string to be executed
-// inside each new RootRealm to fix their Object.prototypes too. So don't
-// import anything from the outside.
+// todo: this file should be moved out to a separate repo and npm module.
// todo: This function is serialized and evaluated outside of the primal
// realms and it currently can't contain code coverage metrics.
/* istanbul ignore file */
export function repairAccessors() {
- const { getPrototypeOf, defineProperties, defineProperty, getOwnPropertyDescriptor } = Object;
+ const {
+ getPrototypeOf,
+ defineProperty,
+ getOwnPropertyDescriptor,
+ prototype: objectPrototype
+ } = Object;
- // 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;
- }
- return `${prop}`;
- }
+ // On some platforms, the implementation of these functions act as if they are
+ // in sloppy mode: if they're invoked badly, they will expose the global object,
+ // 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.
- defineProperties(Object.prototype, {
- __defineGetter__: {
+ function makeDefineAccessor(method, accessor) {
+ defineProperty(objectPrototype, method, {
value(prop, func) {
- return defineProperty(this, prop, {
- get: func,
+ const result = defineProperty(this, prop, {
+ [accessor]: func,
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(prop, func) {
- return defineProperty(this, prop, {
- set: func,
- enumerable: true,
- configurable: true
});
}
- },
- __lookupGetter__: {
- value(prop) {
- prop = asPropertyName(prop); // sanitize property name/symbol
- let base = this;
- let desc;
- while (base && !(desc = getOwnPropertyDescriptor(base, prop))) {
- base = getPrototypeOf(base);
+
+ 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;
}
- return desc && desc.get;
+ return `${prop}`;
}
- },
- __lookupSetter__: {
+
+ function makeLookupAccessor(method, accessor) {
+ defineProperty(objectPrototype, method, {
value(prop) {
prop = asPropertyName(prop); // sanitize property name/symbol
let base = this;
@@ -81,10 +73,13 @@ export function repairAccessors() {
while (base && !(desc = getOwnPropertyDescriptor(base, prop))) {
base = getPrototypeOf(base);
}
- return desc && desc.set;
- }
+ return desc && desc[accessor];
}
});
}
+ makeLookupAccessor('__lookupGetter__', 'get');
+ makeLookupAccessor('__lookupSetter__', 'set');
+}
+
export const repairAccessorsShim = `(${repairAccessors})();`;

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineGetter__/define-new.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 property does not exist
+info: |
+ [...]
+ 3. Let desc be PropertyDescriptor{[[Get]]: getter, [[Enumerable]]: true,
+ [[Configurable]]: true}.
+ 4. Let key be ? ToPropertyKey(P).
+ 5. Perform ? DefinePropertyOrThrow(O, key, desc).
+ 6. Return undefined.
+includes: [propertyHelper.js]
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineGetter__/define-new.js', t => {
+ t.plan(6);
+
+ const test = () => {
+ const subject = {};
+ const get = function() {};
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const result = subject.__defineGetter__('stringAcsr', get);
+
+ const desc = Object.getOwnPropertyDescriptor(subject, 'stringAcsr');
+
+ t.equal(desc.enumerable, true, 'descriptor should be enumerable');
+ t.equal(desc.configurable, true, 'descriptor should be configurable');
+
+ t.equal(desc.get, get, 'descriptor `get` method');
+ t.equal(desc.set, undefined, 'descriptor `set` method');
+ t.equal(desc.value, undefined, 'descriptor `value` property');
+
+ t.equal(result, undefined, 'method return value');
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineGetter__/define-non-configurable.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 property exists and is not configurable
+info: |
+ [...]
+ 5. Perform ? DefinePropertyOrThrow(O, key, desc).
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineGetter__/define-non-configurable.js', t => {
+ t.plan(2);
+
+ const test = () => {
+ const noop = function() {};
+ const subject = Object.defineProperty({}, 'attr', { value: 1, configurable: false });
+
+ // 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__('attr', noop);
+ }, TypeError);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineGetter__/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/__defineGetter__/this-non-obj.js', t => {
+ t.plan(4);
+
+ const test = () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const __defineGetter__ = Object.prototype.__defineGetter__;
+ const noop = function() {};
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ t.equal(typeof __defineGetter__, 'function');
+
+ t.throws(() => {
+ __defineGetter__.call(undefined, key, noop);
+ }, TypeError);
+
+ t.throws(() => {
+ __defineGetter__.call(null, key, noop);
+ }, TypeError);
+
+ 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__/define-new.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 property does not exist
+info: |
+ [...]
+ 3. Let desc be PropertyDescriptor{[[Set]]: setter, [[Enumerable]]: true,
+ [[Configurable]]: true}.
+ 4. Let key be ? ToPropertyKey(P).
+ 5. Perform ? DefinePropertyOrThrow(O, key, desc).
+ 6. Return undefined.
+includes: [propertyHelper.js]
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineSetter__/define-new.js', t => {
+ t.plan(6);
+
+ const test = () => {
+ const subject = {};
+ const set = function() {};
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const result = subject.__defineSetter__('stringAcsr', set);
+
+ const desc = Object.getOwnPropertyDescriptor(subject, 'stringAcsr');
+
+ t.equal(desc.enumerable, true, 'descriptor should be enumerable');
+ t.equal(desc.configurable, true, 'descriptor should be configurable');
+
+ t.equal(desc.get, undefined, 'descriptor `get` method');
+ t.equal(desc.set, set, 'descriptor `set` method');
+ t.equal(desc.value, undefined, 'descriptor `value` property');
+
+ t.equal(result, undefined, 'method return value');
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineSetter__/define-non-configurable.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 property exists and is not configurable
+info: |
+ [...]
+ 5. Perform ? DefinePropertyOrThrow(O, key, desc).
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__defineSetter__/define-non-configurable.js', t => {
+ t.plan(2);
+
+ const test = () => {
+ const noop = function() {};
+ const subject = Object.defineProperty({}, 'attr', { value: 1, configurable: false });
+
+ // 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__('attr', noop);
+ }, TypeError);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__defineSetter__/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/__defineSetter__/this-non-obj.js', t => {
+ t.plan(4);
+
+ const test = () => {
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ const __defineSetter__ = Object.prototype.__defineSetter__;
+ const noop = function() {};
+ let toStringCount = 0;
+ const key = {
+ toString() {
+ toStringCount += 1;
+ }
+ };
+
+ t.equal(typeof __defineSetter__, 'function');
+
+ t.throws(
+ () => {
+ __defineSetter__.call(undefined, key, noop);
+ },
+ TypeError,
+ 'undefined'
+ );
+
+ t.throws(
+ () => {
+ __defineSetter__.call(null, key, noop);
+ },
+ 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/__lookupGetter__/lookup-own-data.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 defines a like-named data property
+info: |
+ [...]
+ 4. Repeat
+ a. Let desc be ? O.[[GetOwnProperty]](key).
+ b. If desc is not undefined, then
+ i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
+ ii. Return undefined.
+ c. Let O be ? O.[[GetPrototypeOf]]().
+ d. If O is null, return undefined.
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupGetter__/lookup-own-data.js', t => {
+ t.plan(1);
+
+ const test = () => {
+ // eslint-disable-next-line getter-return
+ const root = Object.defineProperty({}, 'target', { get() {} });
+ const desc = { value: null };
+ const subject = Object.create(root, { target: desc });
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(subject.__lookupGetter__('target'), undefined);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__lookupGetter__/lookup-proto-data.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 defines a like-named data property
+info: |
+ [...]
+ 4. Repeat
+ a. Let desc be ? O.[[GetOwnProperty]](key).
+ b. If desc is not undefined, then
+ i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
+ ii. Return undefined.
+ c. Let O be ? O.[[GetPrototypeOf]]().
+ d. If O is null, return undefined.
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupGetter__/lookup-proto-data.js', t => {
+ t.plan(1);
+
+ const test = () => {
+ // eslint-disable-next-line getter-return
+ const root = Object.defineProperty({}, 'target', { get() {} });
+ const desc = { value: null };
+ const intermediary = Object.create(root, { target: desc });
+ const subject = Object.create(intermediary);
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(subject.__lookupGetter__('target'), undefined);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__lookupSetter__/lookup-own-data.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 defines a like-named data property
+info: |
+ [...]
+ 4. Repeat
+ a. Let desc be ? O.[[GetOwnProperty]](key).
+ b. If desc is not undefined, then
+ i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
+ ii. Return undefined.
+ c. Let O be ? O.[[GetPrototypeOf]]().
+ d. If O is null, return undefined.
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupSetter__/lookup-own-data.js', t => {
+ t.plan(1);
+
+ const test = () => {
+ const root = Object.defineProperty({}, 'target', { set() {} });
+ const desc = { value: null };
+ const subject = Object.create(root, { target: desc });
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(subject.__lookupSetter__('target'), undefined);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

ADD
shim/test/test262/annexB/built-ins/Object/prototype/__lookupSetter__/lookup-proto-data.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 defines a like-named data property
+info: |
+ [...]
+ 4. Repeat
+ a. Let desc be ? O.[[GetOwnProperty]](key).
+ b. If desc is not undefined, then
+ i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
+ ii. Return undefined.
+ c. Let O be ? O.[[GetPrototypeOf]]().
+ d. If O is null, return undefined.
+---*/
+
+import test from 'tape';
+import Realm from '../../../../../../../src/realm';
+
+test('test262/annexB/built-ins/Object/prototype/__lookupSetter__/lookup-proto-data.js', t => {
+ t.plan(1);
+
+ const test = () => {
+ const root = Object.defineProperty({}, 'target', { set() {} });
+ const desc = { value: null };
+ const intermediary = Object.create(root, { target: desc });
+ const subject = Object.create(intermediary);
+
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ t.equal(subject.__lookupSetter__('target'), undefined);
+ };
+
+ const realm = Realm.makeRootRealm();
+ realm.global.t = t;
+ realm.global.eval(`(${test})()`);
+});

Simplify Accessors (DRY)