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);
+          });
+        }
+      });
+
+    })();
+  }

 })();

Reply via email to