Revision: 5621
Author:   [email protected]
Date:     Mon Oct 28 23:24:18 2013 UTC
Log:      Whitelist/tame Typed Arrays and use them in 2D Canvas taming.
https://codereview.appspot.com/12801043

* Whitelist typed-array objects in SES:
  * ArrayBuffer
  * Int8Array
  * Uint8Array
  * Uint8ClampedArray
  * Int16Array
  * Uint16Array
  * Int32Array
  * Uint32Array
  * Float32Array
  * Float64Array
  * DataView
* Taming membrane copies such objects. Note that this copying
  breaks sharing of ArrayBuffers among ArrayBufferViews.
* ImageData's data property is now an Uint8ClampedArray as is standard
  rather than a plain array. Byte-by-byte copying is no longer used.
* Repair one DataView method throwing DOMException on Firefox and
  *Array.prototype.set throwing DOMException on Safari.
* Repair typed array prototypes appearing unfrozen on Safari.

[email protected], [email protected]

http://code.google.com/p/google-caja/source/detail?r=5621

Modified:
 /trunk/src/com/google/caja/plugin/domado.js
 /trunk/src/com/google/caja/plugin/taming-membrane.js
 /trunk/src/com/google/caja/ses/repairES5.js
 /trunk/src/com/google/caja/ses/startSES.js
 /trunk/src/com/google/caja/ses/whitelist.js
 /trunk/tests/com/google/caja/plugin/MainBrowserTest.java
 /trunk/tests/com/google/caja/plugin/test-scan-guest.js
 /trunk/tests/com/google/caja/plugin/test-taming-inout-guest.js

=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Mon Oct 28 18:56:46 2013 UTC
+++ /trunk/src/com/google/caja/plugin/domado.js Mon Oct 28 23:24:18 2013 UTC
@@ -41,7 +41,7 @@
  *
  * @author [email protected] (original Domita)
  * @author [email protected] (port to ES5)
- * @requires console
+ * @requires console, Uint8ClampedArray
  * @requires bridalMaker, cajaVM, cssSchema, lexCss, URI, unicode
* @requires parseCssDeclarations, sanitizeCssProperty, sanitizeCssSelectors
  * @requires html, html4, htmlSchema
@@ -4799,14 +4799,6 @@

         tamingClassTable.registerLazy('ImageData', function() {
           function TameImageData(imageData) {
-            // Since we can't interpose indexing, we can't wrap the
-            // CanvasPixelArray
- // so we have to copy the pixel data. This is horrible, bad, and
-            // awful.
- // TODO(kpreid): No longer true in ES5-land; we can interpose but - // not under ES5/3. Use proxies conditional on the same switch that
-            // controls liveness of node lists.
-
             TameImageDataConf.confide(this, taming);
             taming.permitUntaming(this);

@@ -4822,6 +4814,14 @@
               // putImageData
               privates.tamePixelArray = undefined;

+              privates.writeback = function() {
+ // This is invoked just before each putImageData to copy pixels
+                // back into the feral world
+                if (privates.tamePixelArray) {
+                  privates.feral.data.set(privates.tamePixelArray);
+                }
+              };
+
               Object.preventExtensions(privates);
             });
             Object.freeze(this);
@@ -4836,32 +4836,9 @@
             // inspecting the pixels.
             data: Props.ampGetter(function(privates) {
               if (!privates.tamePixelArray) {
-
-                var bareArray = privates.feral.data;
-                // TODO(kpreid): replace tamePixelArray with typed array
-
-                var length = bareArray.length;
-                var tamePixelArray = { // not frozen, user-modifiable
-                  // TODO: Investigate whether it would be an optimization
-                  // to make this an array with properties added.
-                  toString: innocuous(function() {
-                    return '[domado object CanvasPixelArray]';
-                  }),
-                  _d_canvas_writeback: innocuous(function() {
-                    // This is invoked just before each putImageData
-
-                    // TODO(kpreid): shouldn't be a public method (but is
-                    // harmless).
-
-                    for (var i = length-1; i >= 0; i--) {
-                      bareArray[+i] = tamePixelArray[+i];
-                    }
-                  })
-                };
-                for (var i = length-1; i >= 0; i--) {
-                  tamePixelArray[+i] = bareArray[+i];
-                }
-                privates.tamePixelArray = tamePixelArray;
+                // Creates copy containing no feral references
+                privates.tamePixelArray =
+                    new Uint8ClampedArray(privates.feral.data);
               }
               return privates.tamePixelArray;
             })
@@ -5204,10 +5181,7 @@
                     ' arguments';
               }
TameImageDataConf.amplify(tameImageData, function(imageDataPriv) {
-                var tamePixelArray = imageDataPriv.tamePixelArray;
-                if (tamePixelArray) {
-                  tamePixelArray._d_canvas_writeback();
-                }
+                imageDataPriv.writeback();
                 privates.feral.putImageData(imageDataPriv.feral,
                     dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
               });
=======================================
--- /trunk/src/com/google/caja/plugin/taming-membrane.js Mon Oct 14 20:51:59 2013 UTC +++ /trunk/src/com/google/caja/plugin/taming-membrane.js Mon Oct 28 23:24:18 2013 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
  */
@@ -153,11 +155,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;
     switch (Object.prototype.toString.call(o)) {
       case '[object Boolean]':
@@ -213,9 +222,57 @@
             t.name = '' + name;
             break;
         }
+        break;
     }
     return t;
   }
+
+  function copyArray(o, recursor) {
+    var copy = [];
+    for (var i = 0; i < o.length; i++) {
+      copy[i] = recursor(o[i]);
+    }
+    return Object.freeze(copy);
+  }
+
+  /**
+ * 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) {
+    if (Array.isArray(o)) {
+      // No tamesTo(...) for arrays; we copy across the membrane
+      return copyArray(o, recursor);
+    } else {
+      var t = undefined;
+      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.
+        case '[object ArrayBuffer]':
+          t = ArrayBuffer.prototype.slice.call(o, 0);
+          break;
+        case '[object Int8Array]': t = new Int8Array(o); break;
+        case '[object Uint8Array]': t = new Uint8Array(o); break;
+ case '[object Uint8ClampedArray]': t = new Uint8ClampedArray(o); break;
+        case '[object Int16Array]': t = new Int16Array(o); break;
+        case '[object Uint16Array]': t = new Uint16Array(o); break;
+        case '[object Int32Array]': t = new Int32Array(o); break;
+        case '[object Uint32Array]': t = new Uint32Array(o); break;
+        case '[object Float32Array]': t = new Float32Array(o); break;
+        case '[object Float64Array]': t = new 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
@@ -278,25 +335,6 @@
     feralByTame.set(t, f);
     schema.fix(f);
   }
-
-  /**
-   * Private utility functions to tame and untame arrays.
-   */
-  function tameArray(fa) {
-    var ta = [];
-    for (var i = 0; i < fa.length; i++) {
-      ta[i] = tame(fa[i]);
-    }
-    return Object.freeze(ta);
-  }
-
-  function untameArray(ta) {
-    var fa = [];
-    for (var i = 0; i < ta.length; i++) {
-      fa[i] = untame(ta[i]);
-    }
-    return Object.freeze(fa);
-  }

   function errGet(p) {
     return Object.freeze(function() {
@@ -318,23 +356,21 @@
       // Primitive value; tames to self
       return f;
     }
-    var ftype = typeof f;
-    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; }
+    t = copyTreatedMutable(f, tame);
+    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') {
       var ctor = directConstructor(f);
       if (ctor === 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);
@@ -440,7 +476,7 @@
       return applyFeralFunction(
           f,
           helper.USELESS,  // See notes on USELESS above
-          untameArray(arguments));
+          copyArray(arguments, untame));
     };
     addFunctionPropertyHandlers(f, t);
     preventExtensions(t);
@@ -456,11 +492,11 @@
         return applyFeralFunction(
             f,
             (void 0),
-            untameArray(arguments));
+            copyArray(arguments, untame));
       } else {
         // Call as a constructor
         var o = Object.create(fPrototype);
-        applyFeralFunction(f, o, untameArray(arguments));
+        applyFeralFunction(f, o, copyArray(arguments, untame));
         tameObjectWithMethods(o, this);
         tamesTo(o, this);
       }
@@ -511,7 +547,7 @@
       return applyFeralFunction(
           f,
           untame(this),
-          untameArray(arguments));
+          copyArray(arguments, untame));
     };
     addFunctionPropertyHandlers(f, t);
     preventExtensions(t);
@@ -554,7 +590,7 @@
           return applyFeralFunction(
               untame(self)[p],
               untame(self),
-              untameArray(arguments));
+              copyArray(arguments, untame));
         };
       });
     } else if (schema.grantAs.has(f, p, schema.grantTypes.READ)) {
@@ -619,13 +655,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.');
@@ -633,12 +666,13 @@
     if (!helper.isDefinedInCajaFrame(t)) {
       throw new TypeError('Host object leaked without being tamed');
     }
+    var ttype = typeof t;
     if (ttype === 'object') {
       var ctor = directConstructor(t);
       if (ctor === 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);
@@ -655,7 +689,7 @@
     // Untaming of *constructors* which are defined in Caja is unsupported.
     // We untame all functions defined in Caja as xo4a.
     return function(_) {
-      return applyTameFunction(t, tame(this), tameArray(arguments));
+      return applyTameFunction(t, tame(this), copyArray(arguments, tame));
     };
   }

=======================================
--- /trunk/src/com/google/caja/ses/repairES5.js Tue Sep 24 17:41:30 2013 UTC
+++ /trunk/src/com/google/caja/ses/repairES5.js Mon Oct 28 23:24:18 2013 UTC
@@ -37,8 +37,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;
@@ -848,6 +850,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
@@ -2748,6 +2763,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;
+    });
+  }

   ////////////////////// Repairs /////////////////////
   //
@@ -3182,8 +3286,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;
@@ -3318,6 +3420,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;
@@ -4336,6 +4501,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
+    }
   ];

   // UNSHIFT_IGNORES_SEALED
=======================================
--- /trunk/src/com/google/caja/ses/startSES.js  Wed Aug 28 17:45:51 2013 UTC
+++ /trunk/src/com/google/caja/ses/startSES.js  Mon Oct 28 23:24:18 2013 UTC
@@ -1657,6 +1657,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
=======================================
--- /trunk/src/com/google/caja/ses/whitelist.js Wed Sep  4 04:54:27 2013 UTC
+++ /trunk/src/com/google/caja/ses/whitelist.js Mon Oct 28 23:24:18 2013 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
@@ -441,6 +442,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: 'accessor',
+        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: 'accessor',
+        byteOffset: 'accessor',
+        byteLength: 'accessor',
+        length: 'accessor',
+        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: 'accessor',
+        byteOffset: 'accessor',
+        byteLength: 'accessor',
+        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
+      }
     }
   };
 })();
=======================================
--- /trunk/tests/com/google/caja/plugin/MainBrowserTest.java Tue Oct 8 16:46:28 2013 UTC +++ /trunk/tests/com/google/caja/plugin/MainBrowserTest.java Mon Oct 28 23:24:18 2013 UTC
@@ -34,7 +34,7 @@
   @Override
   protected int waitForCompletionTimeout() {
     if (entry.getLabel().startsWith("guest-scan-")) {
-      return 60000;     // msec
+      return 100000;     // msec
     } else {
       return super.waitForCompletionTimeout();
     }
=======================================
--- /trunk/tests/com/google/caja/plugin/test-scan-guest.js Tue Oct 8 16:46:28 2013 UTC +++ /trunk/tests/com/google/caja/plugin/test-scan-guest.js Mon Oct 28 23:24:18 2013 UTC
@@ -29,7 +29,8 @@
  *     HTMLTextAreaElement, HTMLVideoElement, HTMLButtonElement,
* Audio, Image, Option, XMLHttpRequest, Window, Document, Node, Element,
  *     Attr, Text, CSSStyleDeclaration, CanvasRenderingContext2D,
- *     CanvasGradient, ImageData, Location
+ *     CanvasGradient, ImageData, Location,
+ *     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) {
@@ -1253,9 +1259,103 @@
     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.
+    (function() { // hide specialized locals
+
+      argsByAnyFrame('ArrayBuffer', genNew(genSmallInteger));
+      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);
+      });
+
+      var typedArrayCall = G.tuple(G.value(CONSTRUCT), 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, doAllCalls
+            ? typedArrayCall
+            : genNew(G.value(0)));
+        obtainInstance.define(ctor, new ctor(3));
+        expectedUnfrozen.mark(ref);
+
+        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');
=======================================
--- /trunk/tests/com/google/caja/plugin/test-taming-inout-guest.js Wed Aug 14 04:56:12 2013 UTC +++ /trunk/tests/com/google/caja/plugin/test-taming-inout-guest.js Mon Oct 28 23:24:18 2013 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/groups/opt_out.

Reply via email to