Revision: 4492
Author: erights
Date: Sun Jun 5 11:15:21 2011
Log: Enhance es5shim.js to adapt to presence of bugs to be worked
around.
http://codereview.appspot.com/4547070
For each bug handled by es5shim, test and report for its presence, and
only work around a bug on those platforms where that bug is detected.
Now that we work around
https://bugzilla.mozilla.org/show_bug.cgi?id=637994 , we restore the
original getter logic to WeakMap.js, so the WeakMap emulation can
prevent identity stealing even on platforms suffering from that bug.
[email protected]
http://code.google.com/p/google-caja/source/detail?r=4492
Modified:
/trunk/src/com/google/caja/ses/WeakMap.js
/trunk/src/com/google/caja/ses/es5shim.js
=======================================
--- /trunk/src/com/google/caja/ses/WeakMap.js Fri May 13 17:46:28 2011
+++ /trunk/src/com/google/caja/ses/WeakMap.js Sun Jun 5 11:15:21 2011
@@ -35,7 +35,7 @@
* is the best we can do without VM support.
*
* <p>The API implemented here is approximately the API as implemented
- * in FF6.0a1 and agreed to MarkM, Andreas Gal, and Dave Herman,
+ * in FF6.0a1 and agreed to by MarkM, Andreas Gal, and Dave Herman,
* rather than the offially approved proposal page. TODO(erights):
* upgrade the ecmascript WeakMap proposal page to explain this API
* change and present to EcmaScript committee for their approval.
@@ -46,7 +46,7 @@
* what would be the hidden internal properties of a primitive
* implementation. Whereas the FF6.0a1 WeakMap.prototype methods
* require their {@code this} to be a genuine WeakMap instance (i.e.,
- * and object of {@code [[Class]]} "WeakMap}), since there is nothing
+ * an object of {@code [[Class]]} "WeakMap}), since there is nothing
* unforgeable about the pseudo-internal method names used here,
* nothing prevents these emulated prototype methods from being
* applied to non-WeakMaps with pseudo-internal methods of the same
@@ -107,19 +107,12 @@
* MUST either have different identities, or at least one of their
* identities MUST be {@code NO_IDENT}.
*
- * <p><b>Plan</b>: An identity is either a string or a const
- * function returning a mostly-unique string. The identity of an
- * object is always either NO_IDENT or such a function. The
- * egal-identity of the function itself is used to resolve
- * collisions on the string returned by the function. For now, if
- * the key is not an object (i.e., a primitive, null, or undefined),
- * then identity(key) throws a TypeError.
- *
- * <p><b>What's currently implemented</b>: An identity is either a
- * string or a frozen singleton array containing a mostly-unique
- * string. The identity of an object is always either NO_IDENT or
- * such an array. The egal-identity of the array itself is used to
- * resolve collisions on the string within the array.
+ * An identity is either a string or a const function returning a
+ * mostly-unique string. The identity of an object is always either
+ * NO_IDENT or such a function. The egal-identity of the function
+ * itself is used to resolve collisions on the string returned by
+ * the function. If the key is not an object (i.e., a primitive,
+ * null, or undefined), then identity(key) throws a TypeError.
*
* <p>When a map stores a key's identity rather than the key itself,
* the map does not cause the key to be retained. See the emulated
@@ -134,15 +127,14 @@
* chance, and
* <li>reasonably hiding the existence of this new property from
* most JavaScript code.
- * <li><b>Unimplemented plan</b> preventing <i>identity theft</i>,
- * where one object is created falsely claiming to have the
- * identity of another object.
+ * <li>Preventing <i>identity theft</i>, where one object is created
+ * falsely claiming to have the identity of another object.
* </ul>
* We do so by
* <ul>
* <li>Making the hidden property non-enumerable, so we need not
* worry about for-in loops or {@code Object.keys},
- * <li><b>Plan</b>: Making the hidden property an accessor property,
+ * <li>Making the hidden property an accessor property,
* where the hidden property's getter is the identity, and the
* value the getter returns is the mostly unique string.
* <li>monkey patching those reflective methods that would
@@ -161,40 +153,39 @@
* property. The property access operations also reveal the
* randomness provided by {@code Math.random}. This is not
* currently an issue but may become one if SES otherwise seeks
- * to hide Math.random.
+ * to hide {@code Math.random}.
* </ul>
* These are not easily fixed because they are primitive operations
- * which cannot be monkey patched. However, because (<b>Plan</b>)
- * we're representing the precious identity by the identity of the
+ * which cannot be monkey patched. However, because we're
+ * representing the precious identity by the identity of the
* property's getter rather than the value gotten, this identity
* itself cannot leak or be installed by the above non-transparent
* operations.
- *
- * <p>Due to <a href=
- * "https://bugzilla.mozilla.org/show_bug.cgi?id=637994">Bug:
- * Inherited accessor properties (sometimes?) reported as own
- * properties</a> we're reverting the logic of <a href=
-
* "http://code.google.com/p/es-lab/source/browse/trunk/src/ses/WeakMap.js"
- * >WeakMap.js</a> from getter based as in r493 to array-based as in
- * r491. This leaves us open to the identity theft attack, and so is
- * weaker than the security we actually require.
*/
function identity(key) {
+ var name;
+ function identGetter() { return name; }
if (key !== Object(key)) {
throw new TypeError('Not an object: ' + key);
}
- if (hop.call(key, 'ident___')) { return key.ident___; }
+ var desc = originalProps.getOwnPropertyDescriptor(key, 'ident___');
+ if (desc) { return desc.get; }
if (!originalProps.isExtensible(key)) { return NO_IDENT; }
- var name = 'hash:' + Math.random();
- var result = originalProps.freeze([name]);
+
+ name = 'hash:' + Math.random();
+ // If the following two lines a swapped, Safari WebKit Nightly
+ // Version 5.0.5 (5533.21.1, r87697) crashes.
+ // See https://bugs.webkit.org/show_bug.cgi?id=61758
+ originalProps.freeze(identGetter.prototype);
+ originalProps.freeze(identGetter);
defProp(key, 'ident___', {
- value: result,
- writable: false,
+ get: identGetter,
+ set: undefined,
enumerable: false,
configurable: false
});
- return result;
+ return identGetter;
}
/**
@@ -286,11 +277,12 @@
name = id;
id = key;
} else {
- name = id[0];
+ name = id();
}
var opt_ids = identities[name];
var i = opt_ids ? opt_ids.indexOf(id) : -1;
- return Object.freeze({
+ // Using original freeze is safe since this record can't escape.
+ return originalProps.freeze({
name: name,
id: id,
opt_ids: opt_ids,
=======================================
--- /trunk/src/com/google/caja/ses/es5shim.js Fri May 13 17:46:28 2011
+++ /trunk/src/com/google/caja/ses/es5shim.js Sun Jun 5 11:15:21 2011
@@ -17,18 +17,66 @@
/**
* @fileoverview Monkey patch almost ES5 platforms into a closer
* emulation of full <a href=
- * "http://code.google.com/p/es-lab/wiki/SecureableES5"
- * >secureable ES5</a>.
+ * "http://code.google.com/p/es-lab/wiki/SecureableES5" >secureable
+ * ES5</a>.
*
- * <p>On not-quite-ES5 platforms, some elements of these emulations
- * may lose SES safety, as enumerated in the comment on each
- * kludge-switch variable below. The platform must at least provide
- * Object.getOwnPropertyNames, because it cannot reasonably be
+ * <p>Qualifying platforms generally include all JavaScript platforms
+ * shown on <a href="http://kangax.github.com/es5-compat-table/"
+ * >ECMAScript 5 compatibility table</a> that implement {@code
+ * Object.getOwnPropertyNames}. At the time of this writing,
+ * qualifying browsers already include the latest released versions of
+ * Internet Explorer (9), Firefox (4), Chrome (11), and Safari
+ * (5.0.5), their corresponding standalone (e.g., server-side) JavaScript
+ * engines, and Rhino 1.73 and BESEN.
+ *
+ * <p>On such not-quite-ES5 platforms, some elements of these
+ * emulations may lose SES safety, as enumerated in the comment on
+ * each kludge-switch variable below. The platform must at least
+ * provide Object.getOwnPropertyNames, because it cannot reasonably be
* emulated.
+ *
+ * <p>This file is useful by itself, as it has no dependencies on the
+ * rest of SES. It creates no new global bindings, but merely repairs
+ * standard globals or standard elements reachable from standard
+ * globals. If the future-standard {@code WeakMap} global is present,
+ * as it is currently on FF7.0a1, then it will repair it in place. The
+ * one non-standard element that this file uses is {@code console.log}
+ * if present, in order to report the repairs it found necessary. If
+ * {@code console.log} is absent, then this file performs its repairs
+ * silently.
+ *
+ * <p>Generally, this file should be run as the first script in a
+ * JavaScript context (i.e. a browser frame), as it replies on other
+ * primordial objects and methods not yet being perturbed.
+ *
+ * TODO(erights): This file tries to protects itself from most
+ * post-initialization perturbation, by stashing the primordials it
+ * needs for later use, but this attempt is currently incomplete. For
+ * example, the method wrappers installed if {@code
+ * test_NEED_TO_WRAP_METHODS()} use the current binding of {@code
+ * Function.prototype.apply} to access the wrapped method. We need to
+ * revisit this when we support Confined-ES5, as a variant of SES in
+ * which the primordials are not frozen.
*/
(function() {
"use strict";
+ function log(str) {
+ if (typeof console !== 'undefined' && 'log' in console) {
+ // We no longer test (typeof console.log === 'function') since,
+ // on IE9 and IE10preview, in violation of the ES5 spec, it
+ // is callable but has typeof "object".
+ // TODO(erights): report to MS.
+ console.log(str);
+ }
+ }
+
+ if (!Object.getOwnPropertyNames) {
+ var complaint = 'Please upgrade to a JavaScript platform ' +
+ 'which implements Object.getOwnPropertyNames';
+ log(complaint);
+ throw new EvalError(complaint);
+ }
/////////////// KLUDGE SWITCHES ///////////////
@@ -40,17 +88,25 @@
// As these move forward, kludges can be removed until we simply
// rely on ES5.
+
/**
* Workaround for https://bugs.webkit.org/show_bug.cgi?id=55537
*
* <p>This kludge is safety preserving.
- *
- * <p>TODO(erights): Turning on this kludge is expensive, so we
- * should auto-detect at initialization time whether we need to on
- * this platform.
*/
- //var TOLERATE_MISSING_CALLEE_DESCRIPTOR = false;
- var TOLERATE_MISSING_CALLEE_DESCRIPTOR = true;
+ function test_MISSING_CALLEE_DESCRIPTOR() {
+ function foo(){}
+ if (Object.getOwnPropertyNames(foo).indexOf('callee') < 0) { return
false; }
+ if (foo.hasOwnProperty('callee')) {
+ log('New symptom: empty strict function has own callee');
+ } else {
+ log('Phantom callee on strict functions. ' +
+ 'See https://bugs.webkit.org/show_bug.cgi?id=55537');
+ }
+ return true;
+ }
+ var TOLERATE_MISSING_CALLEE_DESCRIPTOR =
test_MISSING_CALLEE_DESCRIPTOR();
+
/**
* Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=591846
@@ -63,16 +119,50 @@
*
* <p>This kludge is safety preserving.
*/
- //var REGEXP_CANT_BE_NEUTERED = false;
- var REGEXP_CANT_BE_NEUTERED = true;
+ function test_REGEXP_CANT_BE_NEUTERED() {
+ if (!RegExp.hasOwnProperty('leftContext')) { return false; }
+ var deletion;
+ try {
+ deletion = delete RegExp.leftContext;
+ } catch (err) {
+ log('Cannot delete ambient mutable RegExp.leftContext. ' +
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=591846');
+ return true;
+ }
+ if (!RegExp.hasOwnProperty('leftContext')) { return false; }
+ if (deletion) {
+ log('New symptom: Deletion of RegExp.leftContext failed. ' +
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=591846');
+ } else {
+ // strict delete should never return false, so if this happens
+ // it indicates an additional bug in strict delete.
+ log('A strict "delete RegExp.leftContext" returned false. ' +
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=591846');
+ }
+ return true;
+ }
+ var TOLERATE_REGEXP_CANT_BE_NEUTERED = test_REGEXP_CANT_BE_NEUTERED();
+
/**
* Work around for
http://code.google.com/p/google-caja/issues/detail?id=528
*
* <p>This kludge is safety preserving.
*/
- //var REGEXP_TEST_EXEC_UNSAFE = false;
- var REGEXP_TEST_EXEC_UNSAFE = true;
+ function test_REGEXP_TEST_EXEC_UNSAFE() {
+ (/foo/).test('xfoox');
+ var match = new RegExp('(.|\r|\n)*','').exec()[0];
+ if (match === 'undefined') { return false; }
+ if (match === 'xfoox') {
+ log('RegExp.exec leaks match globally. ' +
+ 'See http://code.google.com/p/google-caja/issues/detail?id=528');
+ } else {
+ log('New symptom: regExp.exec() does not match
against "undefined".');
+ }
+ return true;
+ }
+ var TOLERATE_REGEXP_TEST_EXEC_UNSAFE = test_REGEXP_TEST_EXEC_UNSAFE();
+
/**
* Workaround for https://bugs.webkit.org/show_bug.cgi?id=55736
@@ -88,13 +178,19 @@
* installs if needed do not actually provide the safety that the
* rest of SES relies on.
*/
- //var TOLERATE_MISSING_FREEZE_ETC = false;
- var TOLERATE_MISSING_FREEZE_ETC = true;
+ function test_MISSING_FREEZE_ETC() {
+ if ('freeze' in Object) { return false; }
+ log('Object.freeze is missing. ' +
+ 'See https://bugs.webkit.org/show_bug.cgi?id=55736');
+ return true;
+ }
+ var TOLERATE_MISSING_FREEZE_ETC = test_MISSING_FREEZE_ETC();
+
/**
- * Workaround for https://bugs.webkit.org/show_bug.cgi?id=26382.
+ * Workaround for https://bugs.webkit.org/show_bug.cgi?id=26382
*
- * As of this writing, the only major browser that does implement
+ * <p>As of this writing, the only major browser that does implement
* Object.getOwnPropertyNames but not Function.prototype.bind is
* Safari 5 (JavaScriptCore), including the current Safari beta
* 5.0.4 (5533.20.27, r84622).
@@ -105,38 +201,88 @@
*
* <p>See also https://bugs.webkit.org/show_bug.cgi?id=42371
*/
- //var TOLERATE_MISSING_BIND = false;
- var TOLERATE_MISSING_BIND = true;
+ function test_MISSING_BIND() {
+ if ('bind' in Function.prototype) { return false; }
+ log('Function.prototype.bind is missing. ' +
+ 'See https://bugs.webkit.org/show_bug.cgi?id=26382');
+ return true;
+ }
+ var TOLERATE_MISSING_BIND = test_MISSING_BIND();
+
/**
- * Workaround for an unfortunate oversight in the ES5 spec: Even if
+ * Workaround for
http://code.google.com/p/google-caja/issues/detail?id=1362
+ *
+ * <p>This is an unfortunate oversight in the ES5 spec: Even if
* Date.prototype is frozen, it is still defined to be a Date, and
* so has mutable state in internal properties that can be mutated
* by the primordial mutation methods on Date.prototype, such as
* {@code Date.prototype.setFullYear}.
*
- * <p>TODO(erights): find an appropriate venue to report this bug
- * and report it.
+ * <p>This kludge is safety preserving.
*/
- //var PATCH_MUTABLE_FROZEN_DATE_PROTO = false;
- var PATCH_MUTABLE_FROZEN_DATE_PROTO = true;
+ function test_MUTABLE_DATE_PROTO() {
+ try {
+ Date.prototype.setFullYear(1957);
+ } catch (err) {
+ if (err instanceof TypeError) { return false; }
+ log('New symptom: Mutating Date.prototype failed with ' + err);
+ return true;
+ }
+ var v = Date.prototype.getFullYear();
+ if (v !== v && typeof v === 'number') {
+ // NaN indicates we're probably ok.
+ return false;
+ }
+ if (v === 1957) {
+ log('Date.prototype is a global communication channel. ' +
+ 'See
http://code.google.com/p/google-caja/issues/detail?id=1362');
+ } else {
+ log('New symptom: Mutating Date.prototype did not throw');
+ }
+ return true;
+ }
+ var TOLERATE_MUTABLE_DATE_PROTO = test_MUTABLE_DATE_PROTO();
+
/**
- * Workaround for a bug in the current FF6.0a1 implementation: Even
- * if WeakMap.prototype is frozen, it is still defined to be a
- * WeakMap, and so has mutable state in internal properties that can
- * be mutated by the primordial mutation methods on
- * WeakMap.prototype, such as {@code WeakMap.prototype.set}.
+ * Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=656828
+ *
+ * <p>A bug in the current FF6.0a1 implementation: Even if
+ * WeakMap.prototype is frozen, it is still defined to be a WeakMap,
+ * and so has mutable state in internal properties that can be
+ * mutated by the primordial mutation methods on WeakMap.prototype,
+ * such as {@code WeakMap.prototype.set}.
+ *
+ * <p>This kludge is safety preserving.
*
* <p>TODO(erights): Update the ES spec page to reflect the current
- * agreement with Mozilla, and file a bug against the current Mozilla
- * implementation.
+ * agreement with Mozilla.
*/
- //var PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO = false;
- var PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO = true;
+ function test_MUTABLE_WEAKMAP_PROTO() {
+ if (typeof WeakMap !== 'function') { return false; }
+ var x = {};
+ try {
+ WeakMap.prototype.set(x, 86);
+ } catch (err) {
+ if (err instanceof TypeError) { return false; }
+ log('New symptom: Mutating WeakMap.prototype failed with ' + err);
+ return true;
+ }
+ var v = WeakMap.prototype.get(x);
+ if (v === 86) {
+ log('WeakMap.prototype is a global communication channel. ' +
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=656828');
+ } else {
+ log('New symptom: Mutating WeakMap.prototype did not throw');
+ }
+ return true;
+ }
+ var TOLERATE_MUTABLE_WEAKMAP_PROTO = test_MUTABLE_WEAKMAP_PROTO();
+
/**
- * <p>TODO(erights): isolate and report the V8 bug mentioned below.
+ * TODO(erights): isolate and report the V8 bug mentioned below.
*
* <p>This list of records represents the known occurrences of some
* non-isolated, and thus, not yet reported bug on Chrome/v8 only,
@@ -153,15 +299,103 @@
* absence of a [[Construct]] behavior, as specified for the Chapter
* 15 built-in methods. The installed wrapper relies on {@code
* Function.prototype.apply}, as inherited by original, obeying its
- * contract. TODO(erights): We need to revisit this when we support
- * Confined-ES5, as a variant of SES in which the primordials are
- * not frozen.
+ * contract.
*
* <p>Although we have not yet diagnosed the motivating bug, as far
* as we can tell, this kludge is safety preserving.
*/
- //var METHODS_TO_WRAP = [];
- var METHODS_TO_WRAP = [{base: Array.prototype, name: 'forEach'}];
+ function test_NEED_TO_WRAP_METHODS() {
+ if (!(/Chrome/).test(navigator.userAgent)) { return false; }
+ log('Workaround undiagnosed need to wrap some methods.');
+ return true;
+ }
+ var METHODS_TO_WRAP = [];
+ if (test_NEED_TO_WRAP_METHODS()) {
+ METHODS_TO_WRAP = [{base: Array.prototype, name: 'forEach'}];
+ }
+
+ /**
+ * TODO(erights): isolate and report the V8 bug mentioned below.
+ *
+ * <p>Sometimes, when trying to freeze an object containing an
+ * accessor property with a getter but no setter, Chrome fails with
+ * <blockquote>Uncaught TypeError: Cannot set property ident___ of
+ * #<Object> which has only a getter</blockquote>. So if necessary,
+ * this kludge overrides {@code Object.defineProperty} to always
+ * install a dummy setter in lieu of the absent one.
+ *
+ * <p>TODO(erights): We should also override {@code
+ * Object.getOwnPropertyDescriptor} to hide the presence of the
+ * dummy setter, and instead report an absent setter.
+ */
+ function test_NEEDS_DUMMY_SETTER() {
+ if (!(/Chrome/).test(navigator.userAgent)) { return false; }
+ log('Workaround undiagnosed need for dummy setter.');
+ return true;
+ }
+ var TOLERATE_NEEDS_DUMMY_SETTER = test_NEEDS_DUMMY_SETTER();
+
+ /**
+ * Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=637994
+ *
+ * <p>On Firefoxes at least 4 through 7.0a1, an inherited
+ * non-configurable accessor property appears to be an own property
+ * of all objects which inherit this accessor property.
+ *
+ * <p>Our workaround wraps hasOwnProperty, getOwnPropertyNames, and
+ * getOwnPropertyDescriptor to heuristically decide when an accessor
+ * property looks like it is apparently own because of this bug, and
+ * suppress reporting its existence.
+ *
+ * <p>However, it is not feasible to likewise wrap JSON.stringify,
+ * and this bug will cause JSON.stringify to be misled by inherited
+ * enumerable non-configurable accessor properties. To prevent this,
+ * we wrap defineProperty, freeze, and seal to prevent the creation
+ * of <i>enumerable</i> non-configurable accessor properties on
+ * those platforms with this bug.
+ *
+ * <p>A little known fact about JavaScript is that {@code
+ * Object.prototype.propertyIsEnumerable} actually tests whether a
+ * property is both own and enumerable. Assuming that our wrapping
+ * of defineProperty, freeze, and seal prevents the occurrence of an
+ * enumerable non-configurable accessor property, it should also
+ * prevent the occurrence of this bug for any enumerable property,
+ * and so we do not need to wrap propertyIsEnumerable.
+ *
+ * <p>This kludge seems to be safety preserving, but the issues are
+ * delicate and not well understood.
+ */
+ function test_ACCESSORS_INHERIT_AS_OWN() {
+ var base = {};
+ var derived = Object.create(base);
+ function getter() { return 'gotten'; }
+ Object.defineProperty(base, 'foo', {get: getter});
+ if (!derived.hasOwnProperty('foo') &&
+ Object.getOwnPropertyDescriptor(derived, 'foo') === undefined &&
+ Object.getOwnPropertyNames(derived).indexOf('foo') < 0) {
+ return false;
+ }
+ if (derived.hasOwnProperty('foo') &&
+ Object.getOwnPropertyDescriptor(derived, 'foo').get === getter &&
+ Object.getOwnPropertyNames(derived).indexOf('foo') >= 0) {
+ log('Accessor properties inherit as own properties. ' +
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=637994');
+ } else {
+ log('New symptom: ' +
+ 'Accessor properties partially inherit as own properties.');
+ }
+ Object.defineProperty(base, 'bar', {get: getter, configurable: true});
+ if (!derived.hasOwnProperty('bar') &&
+ Object.getOwnPropertyDescriptor(derived, 'bar') === undefined &&
+ Object.getOwnPropertyNames(derived).indexOf('bar') < 0) {
+ return true;
+ }
+ log('New symptom: ' +
+ 'Accessor properties inherit as own even if configurable.');
+ return true;
+ }
+ var TOLERATE_ACCESSORS_INHERIT_AS_OWN = test_ACCESSORS_INHERIT_AS_OWN();
+
//////////////// END KLUDGE SWITCHES ///////////
@@ -172,12 +406,9 @@
var defProp = Object.defineProperty;
var getPrototypeOf = Object.getPrototypeOf;
+
if (TOLERATE_MISSING_CALLEE_DESCRIPTOR) {
(function(realGOPN) {
- if (!realGOPN) {
- throw new EvalError('Please upgrade to a JavaScript platform ' +
- 'which implements Object.getOwnPropertyNames');
- }
Object.getOwnPropertyNames = function calleeFix(base) {
var result = realGOPN(base);
if (typeof base === 'function') {
@@ -191,7 +422,7 @@
})(Object.getOwnPropertyNames);
}
- if (REGEXP_CANT_BE_NEUTERED) {
+ if (TOLERATE_REGEXP_CANT_BE_NEUTERED) {
var UnsafeRegExp = RegExp;
var FakeRegExp = function FakeRegExp(pattern, flags) {
switch (arguments.length) {
@@ -211,7 +442,7 @@
RegExp = FakeRegExp;
}
- if (REGEXP_TEST_EXEC_UNSAFE) {
+ if (TOLERATE_REGEXP_TEST_EXEC_UNSAFE) {
var unsafeRegExpExec = RegExp.prototype.exec;
var unsafeRegExpTest = RegExp.prototype.test;
RegExp.prototype.exec = function fakeExec(specimen) {
@@ -221,6 +452,7 @@
return unsafeRegExpTest.call(this, String(specimen));
};
}
+
function patchMissingProp(base, name, missingFunc) {
if (!(name in base)) {
@@ -305,7 +537,8 @@
return mutableProtoPatcher;
}
- if (PATCH_MUTABLE_FROZEN_DATE_PROTO) {
+
+ if (TOLERATE_MUTABLE_DATE_PROTO) {
// Note: coordinate this list with maintenance of whitelist.js
['setYear',
'setTime',
@@ -325,11 +558,12 @@
'setUTCMilliseconds'].forEach(makeMutableProtoPatcher(Date, 'Date'));
}
- if (PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO && typeof WeakMap === 'function')
{
+ if (TOLERATE_MUTABLE_WEAKMAP_PROTO) {
// Note: coordinate this list with maintanence of whitelist.js
['set',
'delete'].forEach(makeMutableProtoPatcher(WeakMap, 'WeakMap'));
}
+
// Since the METHODS_TO_WRAP list may (and currently does)
// contain {base: Array.prototype, name:'forEach'}, we loop
@@ -343,5 +577,158 @@
};
})(r.base[r.name]);
}
+
+
+ if (TOLERATE_NEEDS_DUMMY_SETTER) {
+ (function() {
+ var defProp = Object.defineProperty;
+ var gopd = Object.getOwnPropertyDescriptor;
+ var freeze = Object.freeze;
+ var complained = false;
+
+ defProp(Object, 'defineProperty', {
+ value: function(base, name, desc) {
+ function dummySetter(newValue) {
+ if (name === 'ident___') {
+ // The setter for ident___ seems to be called during
+ // the built-in freeze, which indicates an
+ // undiagnosed bug. By the logic of initSES, it should
+ // be impossible to call the ident___ setter.
+ // TODO(erights): isolate and report this.
+ if (!complained) {
+ log('Undiagnosed call to setter for ident___');
+ complained = true;
+ }
+ //
+ // If the following debugger line is uncommented, then
+ // under the Chrome debugger, this crashes the page.
+ // TODO(erights): isolate and report this.
+ //
+ //debugger;
+ } else {
+ throw new TypeError('Cannot set ".' + name + '"');
+ }
+ }
+ freeze(dummySetter.prototype);
+ freeze(dummySetter);
+
+ var oldDesc = gopd(base, name);
+ var testBase = {};
+ if (oldDesc) {
+ defProp(testBase, name, oldDesc);
+ }
+ defProp(testBase, name, desc);
+ var fullDesc = gopd(testBase, name);
+
+ if ('get' in fullDesc && fullDesc.set === undefined) {
+ fullDesc.set = dummySetter;
+ }
+ return defProp(base, name, fullDesc);
+ }
+ });
+ })();
+ }
+
+
+ if (TOLERATE_ACCESSORS_INHERIT_AS_OWN) {
+ (function(){
+ // restrict these
+ var defProp = Object.defineProperty;
+ var freeze = Object.freeze;
+ var seal = Object.seal;
+
+ // preserve illusion
+ var hop = Object.prototype.hasOwnProperty;
+ var gopn = Object.getOwnPropertyNames;
+ var gopd = Object.getOwnPropertyDescriptor;
+
+ var complaint = 'Workaround for ' +
+ 'https://bugzilla.mozilla.org/show_bug.cgi?id=637994 ' +
+ ' prohibits enumerable non-configurable accessor properties.';
+
+ function isBadAccessor(derived, name) {
+ var desc = gopd(derived, name);
+ if (!desc || !('get' in desc)) { return false; }
+ var base = getPrototypeOf(derived);
+ if (!base) { return false; }
+ var superDesc = gopd(base, name);
+ if (!superDesc || !('get' in superDesc)) { return false; }
+ return (desc.get &&
+ !desc.configurable && !superDesc.configurable &&
+ desc.get === superDesc.get &&
+ desc.set === superDesc.set &&
+ desc.enumerable === superDesc.enumerable);
+ }
+
+ defProp(Object, 'defineProperty', {
+ value: function definePropertyWrapper(base, name, desc) {
+ var oldDesc = gopd(base, name);
+ var testBase = {};
+ if (oldDesc && !isBadAccessor(base, name)) {
+ defProp(testBase, name, oldDesc);
+ }
+ defProp(testBase, name, desc);
+ var fullDesc = gopd(testBase, name);
+
+ if ('get' in fullDesc &&
+ fullDesc.enumerable &&
+ !fullDesc.configurable) {
+ log(complaint);
+ throw new TypeError(complaint);
+ }
+ return defProp(base, name, fullDesc);
+ }
+ });
+
+ function ensureSealable(base) {
+ gopn(base).forEach(function(name) {
+ var desc = gopd(base, name);
+ if ('get' in desc && desc.enumerable) {
+ if (!desc.configurable) {
+ log('New symptom: "' + name + '" already non-configurable');
+ }
+ log(complaint);
+ throw new TypeError(complaint);
+ }
+ });
+ }
+
+ defProp(Object, 'freeze', {
+ value: function freezeWrapper(base) {
+ ensureSealable(base);
+ return freeze(base);
+ }
+ });
+
+ defProp(Object, 'seal', {
+ value: function sealWrapper(base) {
+ ensureSealable(base);
+ return seal(base);
+ }
+ });
+
+ defProp(Object.prototype, 'hasOwnProperty', {
+ value: function hasOwnPropertyWrapper(name) {
+ return hop.call(this, name) && !isBadAccessor(this, name);
+ }
+ });
+
+ defProp(Object, 'getOwnPropertyDescriptor', {
+ value: function getOwnPropertyDescriptorWrapper(base, name) {
+ if (isBadAccessor(base, name)) { return undefined; }
+ return gopd(base, name);
+ }
+ });
+
+ defProp(Object, 'getOwnPropertyNames', {
+ value: function getOwnPropertyNamesWrapper(base) {
+ return gopn(base).filter(function(name) {
+ return !isBadAccessor(base, name);
+ });
+ }
+ });
+
+ })();
+ }
})();