Revision: 5712
Author:   [email protected]
Date:     Wed Feb 25 21:23:55 2015 UTC
Log: Backport typed array whitelisting (5621,5650,5684,5685,5695,5710) into es53 branch.
https://codereview.appspot.com/202280043

Typed array support in SES (r5621) had previously been excluded for the
simple reason that r5621 also includes changes to domado.js to use
typed arrays, but ES5/3 does not support typed arrays. In this change
we add the SES and taming membrane parts of typed array support only.

This change started as a merge
svn merge -c 5621,5684 ^/trunk
but contains additional edits, as well as manual backports of the
remainders of r5650, r5685, r5695, r5710 which did not previously
apply.

[email protected]


https://code.google.com/p/google-caja/source/detail?r=5712

Modified:
 /branches/es53
 /branches/es53/src/com/google/caja/plugin/taming-membrane.js
 /branches/es53/src/com/google/caja/ses/repairES5.js
 /branches/es53/src/com/google/caja/ses/startSES.js
 /branches/es53/src/com/google/caja/ses/whitelist.js
 /branches/es53/tests/com/google/caja/plugin/test-compare-modes.js
 /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js
 /branches/es53/tests/com/google/caja/plugin/test-taming-inout-guest.js

=======================================
--- /branches/es53/src/com/google/caja/plugin/taming-membrane.js Tue Feb 24 19:34:47 2015 UTC +++ /branches/es53/src/com/google/caja/plugin/taming-membrane.js Wed Feb 25 21:23:55 2015 UTC
@@ -15,7 +15,9 @@
 /**
  * Generic taming membrane implementation.
  *
- * @requires WeakMap
+ * @requires WeakMap, ArrayBuffer, Int8Array, Uint8Array, Uint8ClampedArray,
+ *    Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
+ *    Float64Array, DataView
  * @overrides window
  * @provides TamingMembrane
  */
@@ -96,11 +98,18 @@
     }
   }

-  // Given a builtin object "o" provided by either a guest or host frame,
-  // return a copy constructed in the taming frame. Return undefined if
- // "o" is not a builtin object. Note that we only call this function if we
-  // know that "o" is *not* a primitive.
-  function copyBuiltin(o) {
+  /**
+ * Given a builtin object "o" from either side of the membrane, return a copy + * constructed in the taming frame. Return undefined if "o" is not of a type + * handled here. Note that we only call this function if we know that "o" is
+   * *not* a primitive.
+   *
+ * This function handles only objects which we copy exactly once and reuse + * the copy (via tamesTo()) if the same object is met again. For objects which
+   * we copy every  time they are passed across the membrane, see
+   * copyTreatedMutable below.
+   */
+  function copyTreatedImmutable(o) {
     var t = void 0;
     // Object.prototype.toString is spoofable (as of ES6). Therefore, each
// branch of this switch must assume that o is not necessarily of the type
@@ -161,9 +170,78 @@
             t.name = '' + name;
             break;
         }
+        break;
     }
     return t;
   }
+
+  /**
+ * Helper function; return a copy of a typed array object without depending on
+   * the typed array constructor to do it.
+   *
+   * This is needed, or at least reasonable caution, because typed array
+   * constructors have overloads that will share an ArrayBuffer with the
+   * provided value, rather than copying. In current specification and
+ * implementation it does not appear to be possible to create an object which
+   * exploits this, but we don't wish to rely on that invariant.
+   */
+  function copyTArray(ctor, o) {
+    // Get a copy of the relevant portion of the underlying buffer in a way
+    // which has no overloads and guarantees a copy.
+    var byteOffset = +o.byteOffset;
+    var byteLength = +o.byteLength;
+    var buffer = ArrayBuffer.prototype.slice.call(
+        o.buffer, o.byteOffset, o.byteOffset + o.byteLength);
+
+    return new ctor(buffer);
+  }
+
+  /**
+ * Given a builtin object "o" from either side of the membrane, return a copy + * constructed in the taming frame. Return undefined if "o" is not of a type + * handled here. Note that we only call this function if we know that "o" is
+   * *not* a primitive.
+   *
+ * This function handles only objects which should be copied every time they + * are passed across the membrane. For objects which we wish to copy at most
+   * once, see copyTreatedImmutable above.
+   */
+  function copyTreatedMutable(o, recursor) {
+    {  // dummy block for minimum difference with trunk
+      var t = undefined;
+ // Object.prototype.toString is spoofable (as of ES6). Therefore, each + // branch of this switch must assume that o is not necessarily of the type
+      // and defend against that. However, we consider it acceptable for a
+      // spoofing object to be copied as one of what it was spoofing, or to
+      // cause an error.
+      switch (Object.prototype.toString.call(o)) {
+ // Note that these typed array tamings break any buffer sharing, but
+        // that's in line with our general policy of copying.
+        //
+ // Note that we do not use privilegedAccess operations here, but that + // is okay because ES5/3 does not support typed arrays and so this code
+        // will never be reached.
+        case '[object ArrayBuffer]':
+ // ArrayBuffer.prototype.slice will always copy or throw TypeError
+          t = ArrayBuffer.prototype.slice.call(o, 0);
+          break;
+          case '[object Int8Array]': t = copyTArray(Int8Array, o); break;
+          case '[object Uint8Array]': t = copyTArray(Uint8Array, o); break;
+          case '[object Uint8ClampedArray]':
+              t = copyTArray(Uint8ClampedArray, o); break;
+          case '[object Int16Array]': t = copyTArray(Int16Array, o); break;
+ case '[object Uint16Array]': t = copyTArray(Uint16Array, o); break;
+          case '[object Int32Array]': t = copyTArray(Int32Array, o); break;
+ case '[object Uint32Array]': t = copyTArray(Uint32Array, o); break; + case '[object Float32Array]': t = copyTArray(Float32Array, o); break; + case '[object Float64Array]': t = copyTArray(Float64Array, o); break;
+        case '[object DataView]':
+          t = new DataView(recursor(o.buffer), o.byteOffset, o.byteLength);
+          break;
+      }
+      return t;
+    }
+  }

// This is a last resort for passing a safe "demilitarized zone" exception
   // across the taming membrane in cases where passing the actual thrown
@@ -204,7 +282,7 @@
     if ((f && tameByFeral.has(f)) || (t && feralByTame.has(t))) {
       var et = tameByFeral.get(f);
       var ef = feralByTame.get(t);
-      throw new TypeError('Attempt to multiply tame: ' + f +
+      throw new TypeError('Attempt to multiply tame: ' + f +
           (ef ? ' (already ' + (ef === f ? 'same' : ef) + ')' : '') +
           ' <-> ' + t +
           (et ? ' (already ' + (et === t ? 'same' : et) + ')' : ''));
@@ -266,23 +344,25 @@
       // Primitive value; tames to self
       return f;
     }
-    var ftype = typeof f;
+    var t = tameByFeral.get(f);
+    if (t) { return t; }
     if (Array.isArray(f)) {
       // No tamesTo(...) for arrays; we copy across the membrane
       return tameArray(f);
     }
-    var t = tameByFeral.get(f);
-    if (t) { return t; }
     if (feralByTame.has(f)) {
throw new TypeError('Tame object found on feral side of taming membrane: '
           + f + '. The membrane has previously been compromised.');
     }
+    var ftype = typeof f;
     if (ftype === 'object') {
+ t = copyTreatedMutable(f, tame); // after type test to avoid toxic fns
+      if (t) { return t; }
       var ctor = privilegedAccess.directConstructor(f);
       if (ctor === privilegedAccess.BASE_OBJECT_CONSTRUCTOR) {
         t = preventExtensions(tameRecord(f));
       } else {
-        t = copyBuiltin(f);
+        t = copyTreatedImmutable(f);
         if (t === void 0) {
           if (ctor === void 0) {
             throw new TypeError('Cannot determine ctor of: ' + f);
@@ -565,13 +645,10 @@
       // Primitive value; untames to self
       return t;
     }
-    var ttype = typeof t;
-    if (Array.isArray(t)) {
-      // No tamesTo(...) for arrays; we copy across the membrane
-      return untameArray(t);
-    }
     var f = feralByTame.get(t);
     if (f) { return f; }
+    f = copyTreatedMutable(t, untame);
+    if (f) { return f; }
     if (tameByFeral.has(t)) {
throw new TypeError('Feral object found on tame side of taming membrane: '
           + t + '. The membrane has previously been compromised.');
@@ -579,12 +656,17 @@
     if (!privilegedAccess.isDefinedInCajaFrame(t)) {
       throw new TypeError('Host object leaked without being tamed');
     }
+    if (Array.isArray(t)) {
+      // No tamesTo(...) for arrays; we copy across the membrane
+      return untameArray(t);
+    }
+    var ttype = typeof t;
     if (ttype === 'object') {
       var ctor = privilegedAccess.directConstructor(t);
       if (ctor === privilegedAccess.BASE_OBJECT_CONSTRUCTOR) {
         f = untameCajaRecord(t);
       } else {
-        f = copyBuiltin(t);
+        f = copyTreatedImmutable(t);
         if (f === void 0) {
           throw new TypeError(
               'Untaming of guest constructed objects unsupported: ' + t);
@@ -660,7 +742,7 @@
     reTamesTo: reTamesTo,
     hasTameTwin: hasTameTwin,
     hasFeralTwin: hasFeralTwin,
-
+
// Any code which bypasses the membrane (e.g. in order to provide its own // tame twins, as Domado does) must also filter exceptions resulting from
     // control flow crossing the membrane.
=======================================
--- /branches/es53/src/com/google/caja/ses/repairES5.js Tue Feb 24 19:34:47 2015 UTC +++ /branches/es53/src/com/google/caja/ses/repairES5.js Wed Feb 25 21:23:55 2015 UTC
@@ -36,8 +36,10 @@
  *
  * @author Mark S. Miller
  * @requires ___global_test_function___, ___global_valueOf_function___
- * @requires JSON, navigator, this, eval, document
- * @overrides ses, RegExp, WeakMap, Object, parseInt, repairES5Module
+ * @requires JSON, eval, this
+ * @requires navigator, document, DOMException
+ * @overrides ses, repairES5Module
+ * @overrides RegExp, WeakMap, Object, parseInt
  */
 var RegExp;
 var ses;
@@ -818,6 +820,19 @@
   // tests are rerun to see how they were effected by these repair
   // attempts. Finally, we report what happened.

+  // Certain tests cannot operate without freezing primordial objects;
+  // they must therefore be run in separate frames with fresh
+  // primordials. Since the repairs will not have been performed in
+  // those frames, we use these flags to have the tests explicitly
+  // perform those repairs.
+  //
+  // TODO(kpreid): Figure out a better design for solving this problem.
+  // For example, it would be good to generically run the relevant tests
+  // after startSES has frozen everything and abort otherwise (this is
+  // done as a special case for FREEZING_BREAKS_PROTOTYPES only).
+  var repair_FREEZING_BREAKS_PROTOTYPES_wasApplied = false;
+  var repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN_wasApplied = false;
+
   /**
    * If {@code Object.getOwnPropertyNames} is missing, we consider
    * this to be an ES3 browser which is unsuitable for attempting to
@@ -2811,6 +2826,95 @@
       delete ses.CANT_SAFELY_VERIFY_SYNTAX_canary;
     }
   }
+
+  var typedArrayNames = [
+    'Int8Array',
+    'Uint8Array',
+    'Uint8ClampedArray',
+    'Int16Array',
+    'Uint16Array',
+    'Int32Array',
+    'Uint32Array',
+    'Float32Array',
+    'Float64Array'
+  ];
+
+  function test_TYPED_ARRAYS_THROW_DOMEXCEPTION() {
+    if (global.DataView === undefined) { return false; }
+    if (global.DOMException === undefined) { return false; }
+    function subtest(f) {
+      try {
+        f();
+      } catch (e) {
+        if (e instanceof DOMException) {
+          return true;
+        } else if (e instanceof Error && !(e instanceof DOMException)) {
+          return false;
+        } else {
+          return 'Exception from ' + f + ' of unexpected type: ' + e;
+        }
+      }
+      return f + ' did not throw';
+    };
+    return [
+ function() { new global.Int8Array(1).set(new global.Int8Array(), 10); }, + function() { new global.DataView(new global.ArrayBuffer(1)).getInt8(-1); }
+    ].some(subtest);
+  }
+
+  /**
+ * Observed on Safari 6.0.5 (8536.30.1): frozen typed array prototypes report
+   * their properties as writable.
+   */
+  function test_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN() {
+    // note: cannot test without frames
+    return inTestFrame(function(window) {
+ // Apply the repair which should fix the problem to the testing frame. + // TODO(kpreid): Design a better architecture to handle cases like this
+      // than one-off state flags.
+      if (repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN_wasApplied) {
+        // optional argument not supplied by normal repair process
+        repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN(window);
+      }
+
+      var fail = false;
+      typedArrayNames.forEach(function(ctorName) {
+        var ctor = window[ctorName];
+        if (!ctor) { return; }
+        var proto = ctor.prototype;
+
+        window.Object.freeze(proto);
+        if (!window.Object.isFrozen(proto)) {
+          fail = true;
+          return;
+        }
+
+        window.Object.getOwnPropertyNames(proto, function(prop) {
+          if (typeof fail === 'string') { return; }
+
+          // check attributes
+          var desc = window.Object.getOwnPropertyDescriptor(proto, prop);
+          if (!desc.configurable && desc.writable) {
+            fail = true;
+          } else if (!desc.configurable && !desc.writable) {
+            // correct result
+          } else {
+            fail = 'Unexpected property attributes for ' + ctorName + '.' +
+                prop;
+            return;
+          }
+
+          // check actual writability
+          try { proto[prop] = 9; } catch (e) {}
+          if (proto[prop] !== desc.value) {
+ fail = 'Unexpected actual writability of ' + ctorName + '.' + prop;
+            return;
+          }
+        });
+      });
+      return fail;
+    });
+  }

   /**
    * Detects
@@ -3307,8 +3411,6 @@
   // error message is matched elsewhere (for tighter bounds on catch)
   var NO_CREATE_NULL =
       'Repaired Object.create can not support Object.create(null)';
-  // flag used for the test-of-repair which cannot operate normally.
-  var repair_FREEZING_BREAKS_PROTOTYPES_wasApplied = false;
   // optional argument is used for the test-of-repair
   function repair_FREEZING_BREAKS_PROTOTYPES(opt_Object) {
     var baseObject = opt_Object || Object;
@@ -3421,6 +3523,69 @@
       // No known repairs under these conditions
     }
   }
+
+  function repair_TYPED_ARRAYS_THROW_DOMEXCEPTION() {
+    var protos = typedArrayNames.map(
+        function(ctorName) { return global[ctorName].prototype; });
+    protos.push(global.DataView.prototype);
+    protos.forEach(function(proto) {
+      Object.getOwnPropertyNames(proto).forEach(function(prop) {
+        if (/^[gs]et/.test(prop)) {
+          var origMethod = proto[prop];
+          proto[prop] = function exceptionAdapterWrapper(var_args) {
+            try {
+              origMethod.apply(this, arguments);
+            } catch (e) {
+              if (e instanceof DOMException) {
+                throw new RangeError(e.message);
+              }
+            }
+          };
+        }
+      });
+    });
+  }
+
+  function repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN(opt_global) {
+    var targetGlobal = opt_global || global;
+    var typedArrayProtos = targetGlobal.Object.freeze(typedArrayNames.map(
+        function(ctorName) { return targetGlobal[ctorName].prototype; }));
+
+    var isFrozen = targetGlobal.Object.isFrozen;
+ var getOwnPropertyDescriptor = targetGlobal.Object.getOwnPropertyDescriptor;
+
+ Object.defineProperty(targetGlobal.Object, 'getOwnPropertyDescriptor', {
+      configurable: true,
+      writable: true,  // allow other repairs to stack on
+ value: function getOwnPropertyDescriptor_typedArrayPatch(object, prop) {
+        var desc = getOwnPropertyDescriptor(object, prop);
+        if (desc && typedArrayProtos.indexOf(object) !== -1 &&
+            'value' in desc && ses._primordialsHaveBeenFrozen) {
+ // If it is one of the typed array prototypes then it will have been
+          // frozen by startSES.
+          desc.writable = false;
+        }
+        return desc;
+      }
+    });
+
+    Object.defineProperty(targetGlobal.Object, 'isFrozen', {
+      configurable: true,
+      writable: true,  // allow other repairs to stack on
+      value: function isFrozen_typedArrayPatch(object) {
+ // If it is one of the typed array prototypes then it will have been
+        // frozen by startSES.
+        var v = typedArrayProtos.indexOf(object) !== -1;
+        return isFrozen(object) || (v && ses._primordialsHaveBeenFrozen);
+      }
+    });
+
+ // isSealed does not need repair as it already gives the correct answer.
+
+    if (targetGlobal === global) {
+      repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN_wasApplied = true;
+    }
+  }

   function repair_GLOBAL_LEAKS_FROM_ARRAY_METHODS() {
     var object = Array.prototype;
@@ -4608,6 +4773,32 @@
       sections: ['15.3.2.1'],
       tests: []
     },
+    {
+      id: 'TYPED_ARRAYS_THROW_DOMEXCEPTION',
+      description: 'Typed Array operations throw DOMException',
+      test: test_TYPED_ARRAYS_THROW_DOMEXCEPTION,
+      repair: repair_TYPED_ARRAYS_THROW_DOMEXCEPTION,
+ // indirectly unsafe: DOMException is poisonous to WeakMaps on FF, so we + // choose not to expose it, and un-whitelisted types do not get frozen by
+      // startSES and are therefore global mutable state.
+      preSeverity: severities.NOT_OCAP_SAFE,
+      canRepair: true,
+      urls: [],  // TODO(kpreid): file bugs if appropriate
+      sections: ['13.2.3'],
+      tests: []  // hopefully will be in ES6 tests
+    },
+    {
+      id: 'TYPED_ARRAY_PROTOS_LOOK_UNFROZEN',
+      description: 'Typed Array prototypes look unfrozen',
+      test: test_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN,
+      repair: repair_TYPED_ARRAY_PROTOS_LOOK_UNFROZEN,
+      preSeverity: severities.SAFE_SPEC_VIOLATION,
+      canRepair: true,
+      urls: [],  // TODO(kpreid): file bugs if appropriate
+          // appears on Safari only
+      sections: ['15.2.3.9', '15.2.3.12'],
+      tests: []  // hopefully will be in ES6 tests
+    },
     {
       id: 'NESTED_STRICT_FUNCTIONS_LEAK',
       description: 'Strict nested functions leak from block scope',
=======================================
--- /branches/es53/src/com/google/caja/ses/startSES.js Thu Dec 18 19:31:03 2014 UTC +++ /branches/es53/src/com/google/caja/ses/startSES.js Wed Feb 25 21:23:55 2015 UTC
@@ -1707,6 +1707,11 @@
   // can skip it for non-defensive frames that must only be confined.
   cajaVM.def(sharedImports);

+  // Internal communication back to repairES5 repairs that need to know if
+ // things have been frozen. TODO(kpreid): Consider making this more specific + // (identifying the actually frozen objects) if that doesn't cost too much.
+  ses._primordialsHaveBeenFrozen = true;
+
// The following objects are ambiently available via language constructs, and // therefore if we did not clean and defend them we have a problem. This is // defense against mistakes in modifying the whitelist, not against browser
=======================================
--- /branches/es53/src/com/google/caja/ses/whitelist.js Tue Feb 24 19:34:47 2015 UTC +++ /branches/es53/src/com/google/caja/ses/whitelist.js Wed Feb 25 21:23:55 2015 UTC
@@ -102,6 +102,7 @@
   if (!ses) { ses = {}; }

   var t = true;
+  var TypedArrayWhitelist;  // defined and used below
   ses.whitelist = {
     cajaVM: {                        // Caja support
// This object is present here only to make it itself processed by the
@@ -449,6 +450,62 @@
     JSON: {
       parse: t,
       stringify: t
+    },
+ ArrayBuffer: { // Khronos Typed Arrays spec; ops are safe
+      length: t,  // does not inherit from Function.prototype on Chrome
+      name: t,  // ditto
+      isView: t,
+      prototype: {
+        byteLength: 'maybeAccessor',
+        slice: t
+      }
+    },
+    Int8Array: TypedArrayWhitelist = {  // Typed Arrays spec
+      length: t,  // does not inherit from Function.prototype on Chrome
+      name: t,  // ditto
+      BYTES_PER_ELEMENT: t,
+      prototype: {
+        buffer: 'maybeAccessor',
+        byteOffset: 'maybeAccessor',
+        byteLength: 'maybeAccessor',
+        length: 'maybeAccessor',
+        BYTES_PER_ELEMENT: t,
+        set: t,
+        subarray: t
+      }
+    },
+    Uint8Array: TypedArrayWhitelist,
+    Uint8ClampedArray: TypedArrayWhitelist,
+    Int16Array: TypedArrayWhitelist,
+    Uint16Array: TypedArrayWhitelist,
+    Int32Array: TypedArrayWhitelist,
+    Uint32Array: TypedArrayWhitelist,
+    Float32Array: TypedArrayWhitelist,
+    Float64Array: TypedArrayWhitelist,
+    DataView: {                      // Typed Arrays spec
+      length: t,  // does not inherit from Function.prototype on Chrome
+      name: t,  // ditto
+      prototype: {
+        buffer: 'maybeAccessor',
+        byteOffset: 'maybeAccessor',
+        byteLength: 'maybeAccessor',
+        getInt8: t,
+        getUint8: t,
+        getInt16: t,
+        getUint16: t,
+        getInt32: t,
+        getUint32: t,
+        getFloat32: t,
+        getFloat64: t,
+        setInt8: t,
+        setUint8: t,
+        setInt16: t,
+        setUint16: t,
+        setInt32: t,
+        setUint32: t,
+        setFloat32: t,
+        setFloat64: t
+      }
     }
   };
 })();
=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-compare-modes.js Wed Aug 14 04:56:12 2013 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-compare-modes.js Wed Feb 25 21:23:55 2015 UTC
@@ -225,6 +225,19 @@
         parse: ON_ALL,
         stringify: ON_ALL
       },
+
+      // Features only supported on SES
+      ArrayBuffer: ON_SES,
+      DataView: ON_SES,
+      Float32Array: ON_SES,
+      Float64Array: ON_SES,
+      Int8Array: ON_SES,
+      Int16Array: ON_SES,
+      Int32Array: ON_SES,
+      Uint8Array: ON_SES,
+      Uint8ClampedArray: ON_SES,
+      Uint16Array: ON_SES,
+      Uint32Array: ON_SES,

       cajaVM: {
         // evaluation operations -- not supported in ES5/3
=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js Thu Dec 18 19:31:03 2014 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-scan-guest.js Wed Feb 25 21:23:55 2015 UTC
@@ -29,7 +29,8 @@
  *     HTMLTextAreaElement, HTMLVideoElement, HTMLButtonElement,
* Audio, Image, Option, XMLHttpRequest, Window, Document, Node, Element,
  *     Attr, Text, CSSStyleDeclaration, CanvasRenderingContext2D,
- *     CanvasGradient, ImageData, Location, TouchList
+ *     CanvasGradient, ImageData, Location, TouchList,
+ *     ArrayBuffer, Int8Array, DataView
  * @overrides window
  */

@@ -351,6 +352,11 @@
     var genAccessorSet = genMethod(G.value(cajaVM.def({
       toString: function() { return '<setter garbage>'; }
     })));
+    function genInstance(ctor) {
+      return G.lazyValue(function() {
+        return obtainInstance(ctor);
+      });
+    }
     /** Add third value-callback to an arguments generator */
     function annotate(calls, callback) {
       return G.apply(function (call) {
@@ -1322,9 +1328,105 @@
     obtainInstance.define(CanvasGradient,
document.createElement('canvas').getContext('2d').createLinearGradient(
             0, 1, 2, 3));
-    obtainInstance.define(ImageData,
-        document.createElement('canvas').getContext('2d').createImageData(
-            2, 2));
+    obtainInstance.define(ImageData, (function() {
+ var v = document.createElement('canvas').getContext('2d').createImageData(
+          2, 2);
+      // v.data is an unfrozen native Uint8ClampedArray
+      expectedUnfrozen.setByIdentity(v.data);
+      expectedUnfrozen.setByIdentity(v.data.buffer);
+      return v;
+    }()));
+
+    // Combined Typed Array processing
+    // TODO(kpreid): Reorder everything so that args, obtainInstance, and
+    // expectedUnfrozen are done together for all types.
+    if (inES5Mode) (function() {
+
+      argsByAnyFrame('ArrayBuffer', genNew(genSmallInteger));
+      argsByAnyFrame('ArrayBuffer.isView', genMethod(G.any(
+          genObject, genInstance(Int8Array))));
+      argsByAnyFrame('ArrayBuffer.prototype.slice',
+          freshResult(genMethod(genSmallInteger, genSmallInteger)));
+      obtainInstance.define(ArrayBuffer, new ArrayBuffer(3));
+      expectedUnfrozen.setByConstructor(ArrayBuffer, true);
+      expectedUnfrozen.setByConstructor(
+          simpleEval(tamingEnv, 'ArrayBuffer'), true);
+
+      forEachFrame('Int8Array', function(arrayCtor) {
+        // inert ctor we need to note
+        var ArrayBufferView =
+            Object.getPrototypeOf(arrayCtor.prototype).constructor;
+        if (ArrayBufferView === Object) return;
+        var refArrayBufferView = Ref.is(ArrayBufferView);
+        functionArgs.set(refArrayBufferView, genNew());
+        expectedAlwaysThrow.mark(refArrayBufferView);
+      });
+
+ // PLAIN_CALL is needed on Firefox because it allows calling these ctors
+      var typedArrayCall = G.tuple(G.value(CONSTRUCT, PLAIN_CALL), G.any(
+          G.tuple(genSmallInteger),
+          G.tuple(genInstance(Int8Array)),
+          G.tuple(genNumbers(2)),
+          genConcat(
+            G.tuple(genInstance(ArrayBuffer)),
+            genNumbers(2))));
+      function setupTypedArray(name, doAllCalls) {
+        var ref = RefAnyFrame(name);
+        var ctor = window[name];
+
+        functionArgs.set(ref, freshResult(doAllCalls
+            ? typedArrayCall
+            : genAllCall(G.value(0))));
+        obtainInstance.define(ctor, new ctor(3));
+
+        argsByAnyFrame(name + '.prototype.set', doAllCalls
+            ? G.any(
+                genMethod(genInstance(Int8Array), genSmallInteger),
+                genMethod(G.value([1, 2, 3]), genSmallInteger))
+            : genMethod(G.value([1, 2, 3]), G.value(0)));
+        argsByAnyFrame(name + '.prototype.subarray', freshResult(
+            doAllCalls
+              ? genMethod(genSmallInteger, genSmallInteger)
+              : genMethod(G.value(1), G.value(2))));
+      }
+ // To save on scan time, we only fully exercise some of the array types + // (chosen for coverage of different cases: 1-byte, clamped, endianness,
+      // floats).
+      setupTypedArray('Int8Array', true);
+      setupTypedArray('Uint8Array', false);
+      setupTypedArray('Uint8ClampedArray', true);
+      setupTypedArray('Int16Array', false);
+      setupTypedArray('Uint16Array', false);
+      setupTypedArray('Int32Array', false);
+      setupTypedArray('Uint32Array', true);
+      setupTypedArray('Float32Array', false);
+      setupTypedArray('Float64Array', true);
+
+      argsByAnyFrame('DataView', genNew(
+          genInstance(ArrayBuffer), genSmallInteger, genSmallInteger));
+      obtainInstance.define(DataView, new DataView(new ArrayBuffer(8)));
+      expectedUnfrozen.setByConstructor(DataView, true);
+      var get8 = genMethod(genSmallInteger);
+      argsByAnyFrame('DataView.prototype.getInt8', get8);
+      argsByAnyFrame('DataView.prototype.getUint8', get8);
+      var getWide = genMethod(genSmallInteger, genBoolean);
+      argsByAnyFrame('DataView.prototype.getInt16', getWide);
+      argsByAnyFrame('DataView.prototype.getUint16', getWide);
+      argsByAnyFrame('DataView.prototype.getInt32', getWide);
+      argsByAnyFrame('DataView.prototype.getUint32', getWide);
+      argsByAnyFrame('DataView.prototype.getFloat32', getWide);
+      argsByAnyFrame('DataView.prototype.getFloat64', getWide);
+      var set8 = genMethod(genSmallInteger, genNumber);
+      argsByAnyFrame('DataView.prototype.setInt8', set8);
+      argsByAnyFrame('DataView.prototype.setUint8', set8);
+      var setWide = genMethod(genSmallInteger, genNumber, genBoolean);
+      argsByAnyFrame('DataView.prototype.setInt16', setWide);
+      argsByAnyFrame('DataView.prototype.setUint16', setWide);
+      argsByAnyFrame('DataView.prototype.setInt32', setWide);
+      argsByAnyFrame('DataView.prototype.setUint32', setWide);
+      argsByAnyFrame('DataView.prototype.setFloat32', setWide);
+      argsByAnyFrame('DataView.prototype.setFloat64', setWide);
+    })();

     var tamingFeralWin = directAccess.evalInTamingFrame('window');
     var guestFeralWin = directAccess.evalInGuestFrame('window');
=======================================
--- /branches/es53/tests/com/google/caja/plugin/test-taming-inout-guest.js Tue Feb 24 19:34:47 2015 UTC +++ /branches/es53/tests/com/google/caja/plugin/test-taming-inout-guest.js Wed Feb 25 21:23:55 2015 UTC
@@ -175,6 +175,57 @@
   pass('testGuestConstructedObjectMethods');
 });

+jsunitRegisterIf(
+    typeof ArrayBuffer !== 'undefined',
+    'testGuestTypedArrays',
+    function testGuestTypedArrays() {
+ // Note: We haven't written a corresponding guest-to-host test, because the + // handling of typed arrays is fully symmetric (copyUnmemoized) and has no
+  // reason to behave differently.
+
+  var buf = new Uint8Array([1, 2, 3]).buffer;
+  tamedApi.tamedHostPureFunction(
+ 'assertTrue("value " + a, a instanceof frame.imports.ArrayBuffer);', buf);
+  tamedApi.tamedHostPureFunction(
+      'assertEquals("buffer length", 3, a.byteLength);', buf);
+  tamedApi.tamedHostPureFunction(
+      'assertEquals("buffer element", 2, new Uint8Array(a)[1]);', buf);
+
+  // Testing array type and also specifically the lack of buffer sharing.
+  var a = new Uint8Array([1, 2, 3]);
+  var b = new Uint8Array(a.buffer);
+  tamedApi.tamedHostPureFunction(
+ 'assertTrue("array " + a, a instanceof frame.imports.Uint8Array);', a, b);
+  tamedApi.tamedHostPureFunction(
+      'assertNotEquals("array buffer", a.buffer, b.buffer);', a, b);
+  tamedApi.tamedHostPureFunction('a[0] = 99;', a);
+  // no persistent mutation and sharing
+  tamedApi.tamedHostPureFunction(
+      'assertEquals("not mutated", 2, a[0] + b[0]);', a, b);
+  assertEquals('guest not mutated', 1, a[0]);
+
+  // DataView
+  var array = new Uint8Array([0, 0, 0, 1, 0, 1]);
+  var vbuf = array.buffer;
+  var view = new DataView(vbuf, 1, 4);
+  tamedApi.tamedHostPureFunction(
+    'assertTrue("view: " + a, a instanceof frame.imports.DataView);',
+    view, vbuf);
+  tamedApi.tamedHostPureFunction(
+    'assertNotEquals("equality", a.buffer, b);', view, vbuf);
+ tamedApi.tamedHostPureFunction('assertEquals("bo", 1, a.byteOffset);', view); + tamedApi.tamedHostPureFunction('assertEquals("bl", 4, a.byteLength);', view);
+  tamedApi.tamedHostPureFunction(
+    'assertEquals("value", 256, a.getUint32(0));', view);
+  tamedApi.tamedHostPureFunction(
+    'a.setUint32(0, 10);', view);
+  assertEquals('mutation check 1', 1, array[3]);
+  assertEquals('mutation check 2', 0, array[4]);
+
+  pass('testGuestTypedArrays');
+});
+
+
 jsunitRegister('testMembraneViolation',
                function testMembraneViolation() {
   expectFailure(function() {

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to