Loading...

google-caja-discuss@googlegroups.com

[Prev] Thread [Next]  |  [Prev] Date [Next]

[Caja] [google-caja] r4779 committed - AMDLoader in SES and other SES refactoring... google-caja Mon Feb 13 15:00:50 2012

Revision: 4779
Author:   jasvir
Date:     Mon Feb 13 14:48:15 2012
Log:      AMDLoader in SES and other SES refactoring
http://codereview.appspot.com/5648043

* refactored startSES.js to separate wrapping from compilation, enabling other
ways to recombine them, like script injection (see below).

* compileExprLater.js -- Implemented a compileExprLater abstraction, which is
like cajaVM.compileExpr, except that it returns a promise which
eventually becomes the compiled expr function. This abstraction enables future
interoperation with ES5/3, where server-side translation
requires an asynchrony barrier. The compileExprLater, if it finds itself in a
browser environment, uses script injection rather than "eval",
avoiding yet more stratification bugs in Chrome's debugger.

* refactored explicit.html to use compileExprLater, to load makeSimpleAMDLoader
outside the SES TCB. It is therefore now also multiply
instantiable and debuggable.

* refactored makeSimpleAMDLoader.js to make use of compileExprLater to load AMD
modules, making them also debuggable under the
Chrome debugger.

* repairES5.js and startSES.js -- The big additional feature in this CL is to
"support override by assignment in SES", enabling SES to accept
far more legacy best practice JS code.

* repairES5.js -- More repairs

* debug.js -- A start on being able to report stack traces for errors while
still denying untrusted code access to these errors, all without
translation. All platforms should be safe, but the stack capture works only on
Chrome.

* refactored startSES.js so all freezing of primordials is gathered together, as
a step toward supporting confined SES (sacrificing
defensiveness in exchange for greater legacy compatibility).

[EMAIL PROTECTED]

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

Added:
 /trunk/src/com/google/caja/ses/compileExprLater.js
 /trunk/src/com/google/caja/ses/debug.js
Modified:
 /trunk/build.xml
 /trunk/src/com/google/caja/ses/StringMap.js
 /trunk/src/com/google/caja/ses/WeakMap.js
 /trunk/src/com/google/caja/ses/amdTest.js
 /trunk/src/com/google/caja/ses/amdTest1.js
 /trunk/src/com/google/caja/ses/amdTest3.js
 /trunk/src/com/google/caja/ses/amdTestDir/amdTest2.js
 /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js
 /trunk/src/com/google/caja/ses/detect.js
 /trunk/src/com/google/caja/ses/ejectorsGuardsTrademarks.js
 /trunk/src/com/google/caja/ses/explicit.html
 /trunk/src/com/google/caja/ses/hookupSES.js
 /trunk/src/com/google/caja/ses/hookupSESPlus.js
 /trunk/src/com/google/caja/ses/logger.js
 /trunk/src/com/google/caja/ses/makeFarResourceMaker.js
 /trunk/src/com/google/caja/ses/makeQ.js
 /trunk/src/com/google/caja/ses/makeSimpleAMDLoader.js
 /trunk/src/com/google/caja/ses/repairES5.js
 /trunk/src/com/google/caja/ses/startSES.js
 /trunk/src/com/google/caja/ses/useHTMLLogger.js
 /trunk/src/com/google/caja/ses/whitelist.js

=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/ses/compileExprLater.js Mon Feb 13 14:48:15 2012
@@ -0,0 +1,139 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Makes a "compileExprLater" function which acts like
+ * "cajaVM.compileExpr", except that it returns a promise for the
+ * outcome of attempting to compile the argument expression.
+ *
+ * //provides ses.compileExprLater
+ * @author Mark S. Miller
+ * @overrides ses
+ * @requires Q, cajaVM, document, URI
+ */
+
+
+/* See
+http://webreflection.blogspot.com/2011/08/simulate-script-injection-via-data-uri.html
+*/
+
+var ses;
+
+(function() {
+   "use strict";
+
+   if (ses && !ses.ok()) { return; }
+
+   /**
+    * This implementation works and satisfies the semantics, but
+    * bottoms out in the built-in, which currently does not work well
+    * with the Chrome debugger.
+    *
+    * <p>Since SES is independent of the hosting environment, we
+    * feature test on the global named "document". If it is absent,
+    * then we fall back to this implementation which works in any
+    * standard ES5 environment.
+    *
+    * <p>Eventually, we should check whether we're in an ES5/3
+    * environment, in which case our choice for compileExprLater would
+    * be one that sends the expression back up the server to be
+    * cajoled.
+    */
+   function compileExprLaterFallback(exprSrc, opt_sourcePosition) {
+     // Coercing an object to a string may observably run code, so do
+     // this now rather than in any later turn.
+     exprSrc = ''+exprSrc;
+
+     return Q(cajaVM).send('compileExpr', exprSrc, opt_sourcePosition);
+   }
+
+   if (typeof document === 'undefined') {
+     ses.compileExprLater = compileExprLaterFallback;
+     return;
+   }
+
+   var resolvers = [];
+   var lastResolverTicket = -1;
+
+   function getResolverTicket(resolver) {
+     ++lastResolverTicket;
+     resolvers[lastResolverTicket] = resolver;
+     return lastResolverTicket;
+   }
+
+   ses.redeemResolver = function(i) {
+     var resolver = resolvers[i];
+     delete resolvers[i];
+     return resolver;
+   };
+
+   /**
+    *
+    */
+   function compileLaterInScript(exprSrc, opt_sourceUrl) {
+
+     var result = Q.defer();
+
+     // The portion of the pattern in compileExpr which is appropriate
+     // here as well.
+     var wrapperSrc = ses.securableWrapperSrc(exprSrc, opt_sourceUrl);
+     var freeNames = ses.atLeastFreeVarNames(exprSrc);
+
+     var head = document.getElementsByTagName("head")[0];
+     var script = document.createElement("script");
+     head.insertBefore(script, head.lastChild);
+
+     var resolverTicket = getResolverTicket(result.resolve);
+
+     var scriptSrc = 'ses.redeemResolver(' + resolverTicket + ')(' +
+       'Object.freeze(ses.makeCompiledExpr(' + wrapperSrc + ',\n' +
+       // Freenames consist solely of identifier characters (\w|\$)+
+       // which do not need to be escaped further
+       '["' + freeNames.join('", "') + '"])));';
+
+     if (opt_sourceUrl) {
+       if (URI && URI.parse) {
+         var parsed = URI.parse(String(opt_sourceUrl));
+         parsed = null === parsed ? null : parsed.toString();
+
+ // Note we could try to encode these characters or search specifically + // for */ as a pair of characters but since it is for debugging only
+         // choose to avoid
+         if (null !== parsed &&
+             parsed.indexOf("<") < 0 &&
+             parsed.indexOf(">") < 0 &&
+             parsed.indexOf("*") < 0) {
+           scriptSrc = '/* from ' + parsed + ' */ ' + scriptSrc;
+         }
+       }
+     }
+
+     // TODO(erights): It seems that on Chrome at least, the injected
+     // script actually executes synchronously *now*. Is this
+     // generally true? If so, perhaps we can even make synchronous
+     // eval debuggable? Is such synchronous eval ok for the use case
+     // here, or do we need to postpone this to another turn just in
+     // case?
+     script.appendChild(document.createTextNode(scriptSrc));
+
+     function deleteScriptNode() { script.parentNode.removeChild(script); }
+
+     Q(result.promise).when(deleteScriptNode, deleteScriptNode).end();
+
+     return result.promise;
+   }
+
+   ses.compileExprLater = compileLaterInScript;
+
+ })();
=======================================
--- /dev/null
+++ /trunk/src/com/google/caja/ses/debug.js     Mon Feb 13 14:48:15 2012
@@ -0,0 +1,217 @@
+// Copyright (C) 2011 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview An optional part of the SES initialization process
+ * that saves potentially valuable debugging aids on the side before
+ * startSES.js would remove these, and adds a debugging API which uses
+ * these without compromising SES security.
+ *
+ * <p>NOTE: The currently exposed debugging API is far from
+ * settled. This module is currently in an exploratory phase.
+ *
+ * <p>Meant to be run sometime after repairs are done and a working
+ * WeakMap is available, but before startSES.js. initSES.js includes
+ * this. initSESPlus.js does not.
+ *
+ * //provides ses.UnsafeError,
+ * //provides ses.getCWStack ses.stackString ses.getStack
+ * @author Mark S. Miller
+ * @requires WeakMap, this
+ * @overrides Error, ses, debugModule
+ */
+
+var Error;
+var ses;
+
+(function debugModule(global) {
+   "use strict";
+
+
+   /**
+    * Save away the original Error constructor as ses.UnsafeError and
+    * make it otherwise unreachable. Replace it with a reachable
+    * wrapping constructor with the same standard behavior.
+    *
+    * <p>When followed by the rest of SES initialization, the
+    * UnsafeError we save off here is exempt from whitelist-based
+    * extra property removal and primordial freezing. Thus, we can
+    * use any platform specific APIs defined on Error for privileged
+    * debugging operations, unless explicitly turned off below.
+    */
+   var UnsafeError = Error;
+   ses.UnsafeError = Error;
+   function FakeError(message) {
+     return UnsafeError(message);
+   }
+   FakeError.prototype = UnsafeError.prototype;
+   FakeError.prototype.constructor = FakeError;
+
+   Error = FakeError;
+
+   /**
+    * Should be a function of an argument object (normally an error
+    * instance) that returns the stack trace associated with argument
+    * in Causeway format.
+    *
+    * <p>See http://wiki.erights.org/wiki/Causeway_Platform_Developer
+    *
+    * <p>Currently, there is no one portable technique for doing
+    * this. So instead, each platform specific branch of the if below
+    * should assign something useful to getCWStack.
+    */
+   ses.getCWStack = function uselessGetCWStack(err) { return void 0; };
+
+   if ('captureStackTrace' in UnsafeError) {
+     (function() {
+       // Assuming http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+       // So this section is v8 specific.
+
+       UnsafeError.prepareStackTrace = function(err, sst) {
+         ssts.set(err, sst);
+         return void 0;
+       };
+
+       var unsafeCaptureStackTrace = UnsafeError.captureStackTrace;
+
+       // TODO(erights): This seems to be write only. Can this be made
+       // safe enough to expose to untrusted code?
+       UnsafeError.captureStackTrace = function(obj, opt_MyError) {
+         var wasFrozen = Object.isFrozen(obj);
+         var stackDesc = Object.getOwnPropertyDescriptor(obj, 'stack');
+         try {
+           var result = unsafeCaptureStackTrace(obj, opt_MyError);
+           var ignore = obj.stack;
+           return result;
+         } finally {
+           if (wasFrozen && !Object.isFrozen(obj)) {
+             if (stackDesc) {
+               Object.defineProperty(obj, 'stack', stackDesc);
+             } else {
+               delete obj.stack;
+             }
+             Object.freeze(obj);
+           }
+         }
+       };
+
+       var ssts = WeakMap(); // error -> sst
+
+       /**
+        * Returns a stack in Causeway format.
+        *
+        * <p>Based on
+ * http://code.google.com/p/causeway/source/browse/trunk/src/js/com/teleometry/causeway/purchase_example/workers/makeCausewayLogger.js
+        */
+       function getCWStack(err) {
+         var sst = ssts.get(err);
+         if (sst === void 0 && err instanceof Error) {
+           // We hope it triggers prepareStackTrace
+           var ignore = err.stack;
+           sst = ssts.get(err);
+         }
+         if (sst === void 0) { return void 0; }
+
+         return { calls: sst.map(function(frame) {
+           return {
+             name: '' + (frame.getFunctionName() ||
+                         frame.getMethodName() || '?'),
+             source: '' + (frame.getFileName() || '?'),
+             span: [ [ frame.getLineNumber(), frame.getColumnNumber() ] ]
+           };
+         })};
+       };
+       ses.getCWStack = getCWStack;
+     })();
+
+   } else if (global.opera) {
+     (function() {
+       // Since pre-ES5 browsers are disqualified, we can assume a
+       // minimum of Opera 11.60.
+     })();
+
+
+   } else if (new Error().stack) {
+     (function() {
+       var FFFramePattern = (/^([EMAIL PROTECTED])@(.*?):?(\d*)$/);
+
+       // stacktracejs.org suggests that this indicates FF. Really?
+       function getCWStack(err) {
+         var stack = err.stack;
+         if (!stack) { return void 0; }
+         var lines = stack.split('\n');
+         var frames = lines.map(function(line) {
+           var match = FFFramePattern.exec(line);
+           if (match) {
+             return {
+               name: match[1].trim() || '?',
+               source: match[2].trim() || '?',
+               span: [[+match[3]]]
+             };
+           } else {
+             return {
+               name: line.trim() || '?',
+               source: '?',
+               span: []
+             };
+           }
+         });
+         return { calls: frames };
+       }
+
+       ses.getCWStack = getCWStack;
+     })();
+
+   } else {
+     (function() {
+       // Including Safari and IE10.
+     })();
+
+   }
+
+   /**
+    * Turn a Causeway stack into a v8-like stack traceback string.
+    */
+   function stackString(cwStack) {
+     if (!cwStack) { return void 0; }
+     var calls = cwStack.calls;
+
+     var result = calls.map(function(call) {
+
+       var spanString = call.span.map(function(subSpan) {
+         return subSpan.join(':');
+       }).join('::');
+       if (spanString) { spanString = ':' + spanString; }
+
+       return '  at ' + call.name + ' (' + call.source + spanString + ')';
+
+     });
+     return result.join('\n');
+   };
+   ses.stackString = stackString;
+
+   /**
+    * Return the v8-like stack traceback string associated with err.
+    */
+   function getStack(err) {
+     if (err !== Object(err)) { return void 0; }
+     var cwStack = ses.getCWStack(err);
+     if (!cwStack) { return void 0; }
+     var result = ses.stackString(cwStack);
+     if (err instanceof Error) { result = err + '\n' + result; }
+     return result;
+   };
+   ses.getStack = getStack;
+
+ })(this);
=======================================
--- /trunk/build.xml    Mon Jan 30 12:14:52 2012
+++ /trunk/build.xml    Mon Feb 13 14:48:15 2012
@@ -874,6 +874,7 @@
       <input file="${src.caja}/ses/logger.js"/>
       <input file="${src.caja}/ses/repairES5.js"/>
       <input file="${src.caja}/ses/WeakMap.js"/>
+      <input file="${src.caja}/ses/debug.js"/>
       <input file="${src.caja}/ses/StringMap.js"/>
       <input file="${src.caja}/ses/whitelist.js"/>
       <input file="${src.caja}/ses/atLeastFreeVarNames.js"/>
@@ -986,6 +987,8 @@
       <input file="${src.caja}/ses/logger.js"/>
       <input file="${src.caja}/ses/repairES5.js"/>
       <input file="${src.caja}/ses/WeakMap.js"/>
+      <input file="${src.caja}/ses/debug.js"/>
+      <input file="${src.caja}/ses/StringMap.js"/>
       <input file="${src.caja}/ses/whitelist.js"/>
       <input file="${src.caja}/ses/atLeastFreeVarNames.js"/>
       <input file="${src.caja}/ses/startSES.js"/>
@@ -993,6 +996,17 @@
       <input file="${src.caja}/ses/hookupSES.js"/>
       <input file="${src.caja}/ses/hookupSESPlus.js"/>

+      <input file="${src.caja}/ses/makeQ.js"/>
+      <input file="${src.caja}/ses/makeFarResourceMaker.js"/>
+      <input file="${src.caja}/ses/compileExprLater.js"/>
+      <input file="${src.caja}/ses/detect.js"/>
+
+      <input file="${src.caja}/ses/makeSimpleAMDLoader.js"/>
+      <input file="${src.caja}/ses/amdTest.js"/>
+      <input file="${src.caja}/ses/amdTest1.js"/>
+      <input file="${src.caja}/ses/amdTestDir/amdTest2.js"/>
+      <input file="${src.caja}/ses/amdTest3.js"/>
+
       <input file="${src.caja}/plugin/bridal.js"/>
       <input file="${lib.caja}/plugin/css-defs.js"/>
       <input file="${src.caja}/plugin/csslexer.js"/>
=======================================
--- /trunk/src/com/google/caja/ses/StringMap.js Wed Dec 14 14:11:00 2011
+++ /trunk/src/com/google/caja/ses/StringMap.js Mon Feb 13 14:48:15 2012
@@ -17,39 +17,46 @@
  *
  * @author Mark S. Miller
  * @author Jasvir Nagra
- * @requires cajaVM
- * @provides StringMap
+ * @overrides StringMap
  */

-function StringMap() {
-
-  function assertString(x) {
-    if ('string' !== typeof(x)) {
-      throw new TypeError('Not a string: ' + String(x));
-    }
-    return x;
-  }
-
-  var def;
-  if ('undefined' !== typeof cajaVM) {
-    def = cajaVM.def;
-  } else {
-    def = Object.freeze;
-  }
-
-  var objAsMap = Object.create(null);
-  return def({
-    get: function(key) {
-        return objAsMap[assertString(key) + '$'];
-      },
-    set: function(key, value) {
-        objAsMap[assertString(key) + '$'] = value;
-      },
-    has: function(key) {
-        return (assertString(key) + '$') in objAsMap;
-      },
-    'delete': function(key) {
-        return delete objAsMap[assertString(key) + '$'];
-      }
-  });
-}
+var StringMap;
+
+(function() {
+   "use strict";
+
+   var create = Object.create;
+   var freeze = Object.freeze;
+   function constFunc(func) {
+     func.prototype = null;
+     return freeze(func);
+   }
+
+   function assertString(x) {
+     if ('string' !== typeof(x)) {
+       throw new TypeError('Not a string: ' + String(x));
+     }
+     return x;
+   }
+
+   StringMap = function StringMap() {
+
+     var objAsMap = create(null);
+
+     return freeze({
+       get: constFunc(function(key) {
+         return objAsMap[assertString(key) + '$'];
+       }),
+       set: constFunc(function(key, value) {
+         objAsMap[assertString(key) + '$'] = value;
+       }),
+       has: constFunc(function(key) {
+         return (assertString(key) + '$') in objAsMap;
+       }),
+       'delete': constFunc(function(key) {
+         return delete objAsMap[assertString(key) + '$'];
+       })
+     });
+   };
+
+ })();
=======================================
--- /trunk/src/com/google/caja/ses/WeakMap.js   Fri Oct 21 18:37:00 2011
+++ /trunk/src/com/google/caja/ses/WeakMap.js   Mon Feb 13 14:48:15 2012
@@ -302,11 +302,18 @@


   function constFunc(func) {
-    Object.freeze(func.prototype);
+    func.prototype = null;
     return Object.freeze(func);
   }
+
+  // Right now (12/25/2012) the histogram supports the current
+  // representation. We should check this occasionally, as a true
+  // constant time representation is easy.
+  // var histogram = [];

   WeakMap = function() {
+    // We are currently (12/25/2012) never encountering any prematurely
+    // non-extensible keys.
     var keys = []; // brute force for prematurely non-extensible keys.
     var vals = []; // brute force for corresponding values.

@@ -342,6 +349,8 @@
         if (i >= 0) {
           hr.vals[i] = value;
         } else {
+//          i = hr.gets.length;
+//          histogram[i] = (histogram[i] || 0) + 1;
           hr.gets.push(get___);
           hr.vals.push(value);
         }
=======================================
--- /trunk/src/com/google/caja/ses/amdTest.js   Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/amdTest.js   Mon Feb 13 14:48:15 2012
@@ -20,5 +20,9 @@

 define(['amdTest1', 'amdTestDir/amdTest2', 'amdTest3'],
 function(amdTest1,              amdTest2,   amdTest3) {
-  return amdTest1 + amdTest2 + amdTest3;
+  "use strict";
+
+  // debugger; // See if we can step into amdTest3.js
+  var text3 = amdTest3();
+  return amdTest1 + amdTest2 + text3;
 });
=======================================
--- /trunk/src/com/google/caja/ses/amdTest1.js  Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/amdTest1.js  Mon Feb 13 14:48:15 2012
@@ -19,5 +19,7 @@
  */

 define('amdTest1', [], function() {
+  "use strict";
+
   return 'this';
 });
=======================================
--- /trunk/src/com/google/caja/ses/amdTest3.js  Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/amdTest3.js  Mon Feb 13 14:48:15 2012
@@ -14,10 +14,20 @@

 /**
  * @fileoverview Trivial test of simple AMD loader.
- * Tests anon case. No dependencies.
- * @requires define
+ * Tests anon case. No dependencies. With Axel's CommonJS Adapter
+ * boilerplate. See http://www.2ality.com/2011/11/module-gap.html
+ *
+ * @requires define, require
+ * @overrides module
  */

+({ define: typeof define === "function" ?
+    define :
+    function(A,F) { module.exports = F.apply(null, A.map(require)); }}).
 define([], function() {
-  return 'test';
+  "use strict";
+
+  return function() {
+    return 'test';
+  };
 });
=======================================
--- /trunk/src/com/google/caja/ses/amdTestDir/amdTest2.js Mon Dec 19 23:54:05 2011 +++ /trunk/src/com/google/caja/ses/amdTestDir/amdTest2.js Mon Feb 13 14:48:15 2012
@@ -19,5 +19,7 @@
  */

 define('amdTestDir/amdTest2', [], function() {
+  "use strict";
+
   return ' is a ';
 });
=======================================
--- /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js Mon Jan 9 17:07:50 2012 +++ /trunk/src/com/google/caja/ses/atLeastFreeVarNames.js Mon Feb 13 14:48:15 2012
@@ -20,6 +20,7 @@
  * <p>Assumes only ES3. Compatible with ES5, ES5-strict, or
  * anticipated ES6.
  *
+ * // provides ses.atLeastFreeVarNames
  * @author Mark S. Miller
  * @requires StringMap
  * @overrides ses, atLeastFreeVarNamesModule
@@ -79,6 +80,10 @@
    * http://es-lab.googlecode.com/svn/trunk/src/parser/unicode.js
    *
    * <p>This is only a temporary development hack. TODO(erights): fix.
+   *
+   * If this regexp is changed compileExprLater.js should be checked for
+   * correct escaping of freeNames.
+   *
    */
   function SHOULD_MATCH_IDENTIFIER() { return (/(\w|\$)+/g); }

=======================================
--- /trunk/src/com/google/caja/ses/detect.js    Wed Aug 31 22:32:26 2011
+++ /trunk/src/com/google/caja/ses/detect.js    Mon Feb 13 14:48:15 2012
@@ -1,6 +1,14 @@
// Derived from http://www.quirksmode.org/js/detect.html by Peter-Paul Koch.
 // License terms at http://www.quirksmode.org/about/copyright.html

+/**
+ * @fileoverview Tries to figure out what browser we're on.
+ *
+ * @author Peter-Paul Koch, with modifications by Mark S. Miller
+ * @provides BrowserDetect
+ * @requires navigator, window
+ */
+
 var BrowserDetect;

 (function() {
=======================================
--- /trunk/src/com/google/caja/ses/ejectorsGuardsTrademarks.js Fri Oct 21 18:37:00 2011 +++ /trunk/src/com/google/caja/ses/ejectorsGuardsTrademarks.js Mon Feb 13 14:48:15 2012
@@ -20,6 +20,7 @@
  *
  * <p>Assumes ES5. Compatible with ES5-strict.
  *
+ * // provides ses.ejectorsGuardsTrademarks
  * @author [EMAIL PROTECTED]
  * @requires WeakMap, cajaVM
  * @overrides ses, ejectorsGuardsTrademarksModule
@@ -30,19 +31,6 @@
   "use strict";

   ses.ejectorsGuardsTrademarks = function ejectorsGuardsTrademarks() {
-
-    /**
-     * Returns a new object whose only utility is its identity and (for
-     * diagnostic purposes only) its name.
-     */
-    function Token(name) {
-      name = '' + name;
-      return cajaVM.def({
-        toString: function tokenToString() {
-          return name;
-        }
-      });
-    }

     /**
      * During the call to [EMAIL PROTECTED] ejectorsGuardsTrademarks}, [EMAIL 
PROTECTED]
@@ -53,30 +41,25 @@
      *
      * <p>Instead, we define here some conveniences for freezing just
      * enough without prematurely freezing primodial objects
-     * transitively reachable from these, like [EMAIL PROTECTED]
-     * Function.prototype}. The [EMAIL PROTECTED] freezeFuncion} function will
-     * freeze a function and (if present) its prototype.
+     * transitively reachable from these.
      */
-    function freezeFunction(func) {
-      if (func.prototype) { Object.freeze(func.prototype); }
-      return Object.freeze(func);
-    }
+    var freeze = Object.freeze;
+    var constFunc = cajaVM.constFunc;
+

     /**
-     * The [EMAIL PROTECTED] freezeObjectRecord} takes as argument a record
-     * assumed to hold some methods in its properties.
-     *
-     * <p>[EMAIL PROTECTED] freezeObjectRecord} freezes the record and freezes
-     * (using [EMAIL PROTECTED] freezeFunction}) any functions found in own
-     * properties of that record.
+     * Returns a new object whose only utility is its identity and (for
+     * diagnostic purposes only) its name.
      */
-    function freezeObjectRecord(record) {
-      Object.getOwnPropertyNames(record).forEach(function(name) {
-        var val = record[name];
-        if (typeof val === 'function') { freezeFunction(val); }
+    function Token(name) {
+      name = '' + name;
+      return freeze({
+        toString: constFunc(function tokenToString() {
+          return name;
+        })
       });
-      return Object.freeze(record);
-    }
+    }
+

////////////////////////////////////////////////////////////////////////
     // Ejectors
@@ -134,7 +117,7 @@
           throw token;
         }
       }
-      cajaVM.def(ejector);
+      constFunc(ejector);
       try {
         try {
           return attemptFunc(ejector);
@@ -174,7 +157,7 @@
       var boxValues = new WeakMap(true); // use key lifetime

       function seal(value) {
-        var box = {};
+        var box = freeze({});
         boxValues.set(box, value);
         return box;
       }
@@ -189,10 +172,10 @@
           return result[0];
         }
       }
-      return cajaVM.def({
-        seal: seal,
-        unseal: unseal,
-        optUnseal: optUnseal
+      return freeze({
+        seal: constFunc(seal),
+        unseal: constFunc(unseal),
+        optUnseal: constFunc(optUnseal)
       });
     }

@@ -217,8 +200,8 @@
     function makeTrademark(typename, table) {
       typename = '' + typename;

-      var stamp = freezeObjectRecord({
-        toString: function() { return typename + 'Stamp'; }
+      var stamp = freeze({
+        toString: constFunc(function() { return typename + 'Stamp'; })
       });

       stampers.set(stamp, function(obj) {
@@ -226,16 +209,18 @@
         return obj;
       });

-      return freezeObjectRecord({
-        toString: function() { return typename + 'Mark'; },
+      return freeze({
+        toString: constFunc(function() { return typename + 'Mark'; }),
         stamp: stamp,
-        guard: freezeObjectRecord({
-          toString: function() { return typename + 'T'; },
-          coerce: function(specimen, opt_ejector) {
-            if (table.get(specimen)) { return specimen; }
-            eject(opt_ejector,
- 'Specimen does not have the "' + typename + '" trademark');
-          }
+        guard: freeze({
+          toString: constFunc(function() { return typename + 'T'; }),
+          coerce: constFunc(function(specimen, opt_ejector) {
+            if (!table.get(specimen)) {
+              eject(opt_ejector,
+ 'Specimen does not have the "' + typename + '" trademark');
+            }
+            return specimen;
+          })
         })
       });
     }
@@ -279,7 +264,6 @@
     function Trademark(typename) {
       var result = makeTrademark(typename, new WeakMap());
       stampers.get(GuardStamp)(result.guard);
-      cajaVM.def(result);
       return result;
     };

@@ -311,7 +295,7 @@
         // user-implementable auditing protocol.
         stampers.get(stamps[i])(record);
       }
-      return Object.freeze(record);
+      return freeze(record);
     };

////////////////////////////////////////////////////////////////////////
@@ -339,11 +323,11 @@
     function passesGuard(g, specimen) {
       g = GuardT.coerce(g); // failure throws rather than ejects
       return callWithEjector(
-        Object.freeze(function(opt_ejector) {
+        constFunc(function(opt_ejector) {
           g.coerce(specimen, opt_ejector);
           return true;
         }),
-        Object.freeze(function(ignored) {
+        constFunc(function(ignored) {
           return false;
         })
       );
@@ -360,30 +344,30 @@
      */
     function makeTableGuard(table, typename, errorMessage) {
       var g = {
-        toString: function() { return typename + 'T'; },
-        coerce: function(specimen, opt_ejector) {
-          if (table.get(specimen)) { return specimen; }
-          eject(opt_ejector, errorMessage);
-        }
+        toString: constFunc(function() { return typename + 'T'; }),
+        coerce: constFunc(function(specimen, opt_ejector) {
+          if (!table.get(specimen)) { eject(opt_ejector, errorMessage); }
+          return specimen;
+        })
       };
       stamp([GuardStamp], g);
-      return cajaVM.def(g);
+      return freeze(g);
     }

////////////////////////////////////////////////////////////////////////
     // Exporting
////////////////////////////////////////////////////////////////////////

-    return freezeObjectRecord({
-      callWithEjector: callWithEjector,
-      eject: eject,
-      makeSealerUnsealerPair: makeSealerUnsealerPair,
+    return freeze({
+      callWithEjector: constFunc(callWithEjector),
+      eject: constFunc(eject),
+      makeSealerUnsealerPair: constFunc(makeSealerUnsealerPair),
       GuardT: GuardT,
-      makeTableGuard: makeTableGuard,
-      Trademark: Trademark,
-      guard: guard,
-      passesGuard: passesGuard,
-      stamp: stamp
+      makeTableGuard: constFunc(makeTableGuard),
+      Trademark: constFunc(Trademark),
+      guard: constFunc(guard),
+      passesGuard: constFunc(passesGuard),
+      stamp: constFunc(stamp)
     });
   };
 })();
=======================================
--- /trunk/src/com/google/caja/ses/explicit.html        Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/explicit.html        Mon Feb 13 14:48:15 2012
@@ -46,8 +46,8 @@
   ses.maxAcceptableSeverityName = 'NEW_SYMPTOM';
 </script>
 <!--
-The <script src=... tags below, from "logger.js" to "hookupSES.js", is
-  equivalent to <script src="initSES.js"></script>, but is more
+The <script src=... tags below, from "logger.js" to "hookupSESPlus.js", is
+  equivalent to <script src="initSESPlus.js"></script>, but is more
   pleasant to debug.

 The <script src="useHTMLLogger.js"> and the inline script above advises
@@ -57,12 +57,14 @@
 <script src="logger.js"></script>
 <script src="repairES5.js"></script>
 <script src="WeakMap.js"></script>
+<script src="debug.js"></script>
 <script src="StringMap.js"></script>
 <script src="whitelist.js"></script>
 <script src="atLeastFreeVarNames.js"></script>
 <script src="startSES.js"></script>
 <script src="ejectorsGuardsTrademarks.js"></script>
 <script src="hookupSESPlus.js"></script>
+<script src="../plugin/uri.js"></script>

 <script>
   var amdLoaderTest = gebi('amdLoaderTest');
@@ -82,7 +84,7 @@
       appendText(amdLoaderTest, 'cancelled');
       return;
     }
-
+
     var imports = cajaVM.makeImports();
     cajaVM.copyToImports(imports, {window: 6});

@@ -128,59 +130,83 @@

 <script src="makeQ.js"></script>
 <script src="makeFarResourceMaker.js"></script>
+<script src="compileExprLater.js"></script>
 <script>
-  function mySetTimeout(func, millis) {
-    return setTimeout(function() {
-      try {
-        func();
-      } catch (reason) {
-        console.log('uncaught: ', reason);
-      }
-    }, millis);
-  }
-  var Q = makeQ(mySetTimeout);
-
-  var makeTextResource = makeFarResourceMaker();
-
- var makeSimpleAMDLoaderFileP = makeTextResource('./makeSimpleAMDLoader.js');
-  var makeSimpleAMDLoaderSrcP = makeSimpleAMDLoaderFileP.get();
-  var imports = cajaVM.makeImports();
-  imports.Q = Q;
- var makeSimpleAMDLoaderP = Q(makeSimpleAMDLoaderSrcP).when(function(src) {
-    cajaVM.compileModule(src)(imports);
-    return imports.makeSimpleAMDLoader;
-  });
-
-
-  /**
-   * What we should do is accept any legal module identifier according
-   * to the AMD definition and encode it into a legal URL. Instead,
-   * for now, we just conservatively accept an ascii subset of both
-   * that does not need any encoding.
-   *
-   * See https://github.com/amdjs/amdjs-api/wiki/AMD#wiki-define-id-notes
-   */
-  var isModuleId = Object.freeze(/^[a-zA-Z0-9_/]*$/);
-
-  function fetch(id) {
-    if (!isModuleId.test(id)) {
-      throw new Error('illegal module id: ' + id);
-    }
-    return makeTextResource('./' + id + '.js').get();
-  }
-
-  var moduleMap = StringMap();
-  moduleMap.set('Q', Q);
-  var loaderP = Q(makeSimpleAMDLoaderP).send(void 0, fetch, moduleMap);
-  var amdTestP = Q(loaderP).send(void 0, 'amdTest');
-  Q(amdTestP).when(function(amdTest) {
-    var amdTestStatus = amdTest === 'this is a test' ?
-      'succeeded' : 'failed, unexpected: ' + amdTest;
-    appendText(amdLoaderTest, amdTestStatus);
-  }, function(reason) {
-    appendText(amdLoaderTest, 'failed: ' + reason);
-  }).end();
-
+  var Q;
+  (function() {
+    "use strict";
+
+    if (!ses.ok()) { return; }
+
+    /* TODO(erights): Extract the reusable boilerplate below into a
+       reusable abstraction. */
+
+    function mySetTimeout(func, millis) {
+      return setTimeout(function() {
+        try {
+          func();
+        } catch (reason) {
+          ses.logger.log('uncaught: ', reason);
+        }
+      }, millis);
+    }
+    Q = ses.makeQ(mySetTimeout);
+
+    var makeTextResource = ses.makeFarResourceMaker();
+
+    var url = './makeSimpleAMDLoader.js';
+    var makeSimpleAMDLoaderFileP = makeTextResource(url);
+    var makeSimpleAMDLoaderSrcP = makeSimpleAMDLoaderFileP.get();
+
+    /* We really are evaluating makeSimpleAMDModuleLoader with least
+       authority. Even though there's a global compileExprLater, if
+       you don't provide it here, makeSimpleAMDModuleLoader will
+       fail. */
+    var imports = cajaVM.makeImports();
+    cajaVM.copyToImports(imports, {
+      Q: Q,
+      compileExprLater: ses.compileExprLater
+    });
+
+ var makeSimpleAMDLoaderP = Q(makeSimpleAMDLoaderSrcP).when(function(src) {
+      var exprSrc = '(function() {' + src + '}).call(this)';
+      var compiledExprP = ses.compileExprLater(exprSrc, url);
+      return Q(compiledExprP).when(function(compiledExpr) {
+        compiledExpr(imports);
+        return imports.makeSimpleAMDLoader;
+      });
+    });
+
+
+    /**
+     * What we should do is accept any legal module identifier according
+     * to the AMD definition and encode it into a legal URL. Instead,
+     * for now, we just conservatively accept an ascii subset of both
+     * that does not need any encoding.
+     *
+     * See https://github.com/amdjs/amdjs-api/wiki/AMD#wiki-define-id-notes
+     */
+    var isModuleId = Object.freeze(/^[a-zA-Z0-9_/]*$/);
+
+    function fetch(id) {
+      if (!isModuleId.test(id)) {
+        throw new Error('illegal module id: ' + id);
+      }
+      return makeTextResource('./' + id + '.js').get();
+    }
+
+    var moduleMap = StringMap();
+    moduleMap.set('Q', Q);
+    var loaderP = Q(makeSimpleAMDLoaderP).send(void 0, fetch, moduleMap);
+    var amdTestP = Q(loaderP).send(void 0, 'amdTest');
+    Q(amdTestP).when(function(amdTest) {
+      var amdTestStatus = amdTest === 'this is a test' ?
+        'succeeded' : 'failed, unexpected: ' + amdTest;
+      appendText(amdLoaderTest, amdTestStatus);
+    }, function(reason) {
+      appendText(amdLoaderTest, 'failed: ' + reason);
+    }).end();
+  })();
 </script>

 <p>
@@ -189,14 +215,12 @@
 on <span id="browserOS">unknown</span>
 <script src="detect.js"></script>
 <script>
-(function(){
-  "use strict"
-  gebi('browserName').textContent = BrowserDetect.browser;
-  gebi('browserVersion').textContent = BrowserDetect.version;
-  gebi('browserOS').textContent = BrowserDetect.OS;
-})();
+  (function(){
+    "use strict"
+    gebi('browserName').textContent = BrowserDetect.browser;
+    gebi('browserVersion').textContent = BrowserDetect.version;
+    gebi('browserOS').textContent = BrowserDetect.OS;
+  })();
 </script>
-
-
 </body>
 </html>
=======================================
--- /trunk/src/com/google/caja/ses/hookupSES.js Fri Oct 21 18:37:00 2011
+++ /trunk/src/com/google/caja/ses/hookupSES.js Mon Feb 13 14:48:15 2012
@@ -38,6 +38,6 @@
                  function () { return {}; });
   } catch (err) {
     ses.updateMaxSeverity(ses.severities.NOT_SUPPORTED);
-    ses.logger.error('hookupSES failed with: ' + err);
+    ses.logger.error('hookupSES failed with: ', err);
   }
 })(this);
=======================================
--- /trunk/src/com/google/caja/ses/hookupSESPlus.js     Fri Oct 21 18:37:00 2011
+++ /trunk/src/com/google/caja/ses/hookupSESPlus.js     Mon Feb 13 14:48:15 2012
@@ -38,6 +38,6 @@
                  ses.ejectorsGuardsTrademarks);
   } catch (err) {
     ses.updateMaxSeverity(ses.severities.NOT_SUPPORTED);
-    ses.logger.error('hookupSESPlus failed with: ' + err);
+    ses.logger.error('hookupSESPlus failed with: ', err);
   }
 })(this);
=======================================
--- /trunk/src/com/google/caja/ses/logger.js    Fri Oct 21 18:37:00 2011
+++ /trunk/src/com/google/caja/ses/logger.js    Mon Feb 13 14:48:15 2012
@@ -26,12 +26,19 @@
  * <p>The [EMAIL PROTECTED] ses.logger} API consists of
  * <dl>
  *   <dt>log, info, warn, and error methods</dt>
- *     <dd>each of which take a
- *         string, and which should display this string associated with
- *         that severity level. If no [EMAIL PROTECTED] ses.logger} already
- *         exists, the default provided here forwards to the pre-existing
- * global [EMAIL PROTECTED] console} if one exists. Otherwise, all for of these
- *         do nothing.</dd>
+ *     <dd>each of which take a list of arguments which should be
+ *         stringified and appended together. The logger should
+ *         display this string associated with that severity level. If
+ *         any of the arguments has an associated stack trace
+ *         (presumably Error objects), then the logger <i>may</i> also
+ *         show this stack trace. If no [EMAIL PROTECTED] ses.logger} already
+ *         exists, the default provided here forwards to the
+ *         pre-existing global [EMAIL PROTECTED] console} if one
+ *         exists. Otherwise, all four of these do nothing. If we
+ *         default to forwarding to the pre-existing [EMAIL PROTECTED] 
console} ,
+ *         we prepend an empty string as first argument since we do
+ *         not want to obligate all loggers to implement the console's
+ *         "%" formatting. </dd>
  *   <dt>classify(postSeverity)</dt>
  *     <dd>where postSeverity is a severity
  *         record as defined by [EMAIL PROTECTED] ses.severities} in
@@ -110,6 +117,7 @@
  * <p>Assumes only ES3. Compatible with ES5, ES5-strict, or
  * anticipated ES6.
  *
+ * //provides ses.logger
  * @author Mark S. Miller
  * @requires console
  * @overrides ses, loggerModule
@@ -122,6 +130,11 @@

   var logger;
   function logNowhere(str) {}
+
+  var slice = [].slice;
+  var apply = slice.apply;
+
+

   if (ses.logger) {
     logger = ses.logger;
@@ -141,11 +154,33 @@
     //     we install an emulated bind.
     // </ul>

+    var forward = function(level, args) {
+      args = slice.call(args, 0);
+      // We don't do "console.apply" because "console" is not a function
+      // on IE 10 preview 2 and it has no apply method. But it is a
+      // callable that Function.prototype.apply can successfully apply.
+      // This code most work on ES3 where there's no bind. When we
+      // decide support defensiveness in contexts (frames) with mutable
+      // primordials, we will need to revisit the "call" below.
+      apply.call(console[level], console, [''].concat(args));
+
+      // See debug.js
+      var getStack = ses.getStack;
+      if (getStack) {
+        for (var i = 0, len = args.length; i < len; i++) {
+          var stack = getStack(args[i]);
+          if (stack) {
+            console[level]('', stack);
+          }
+        }
+      }
+    };
+
     logger = {
-      log:   function log(str)   { console.log(str); },
-      info:  function info(str)  { console.info(str); },
-      warn:  function warn(str)  { console.warn(str); },
-      error: function error(str) { console.error(str); }
+      log:   function log(var_args)   { forward('log', arguments); },
+      info:  function info(var_args)  { forward('info', arguments); },
+      warn:  function warn(var_args)  { forward('warn', arguments); },
+      error: function error(var_args) { forward('error', arguments); }
     };
   } else {
     logger = {
@@ -228,4 +263,5 @@
   }

   ses.logger = logger;
+
 })();
=======================================
--- /trunk/src/com/google/caja/ses/makeFarResourceMaker.js Mon Dec 19 23:54:05 2011 +++ /trunk/src/com/google/caja/ses/makeFarResourceMaker.js Mon Feb 13 14:48:15 2012
@@ -13,21 +13,25 @@
 // limitations under the License.

 /**
- * @fileoverview Makes a makeFarResource function using a given
+ * @fileoverview Makes a "makeFarResource" function using a given
  * serializer/unserializer pair. A makeFarResource function makes a
  * farPromise for an (assumed remote) resource for a given URL.
  *
+ * //provides ses.makeFarResourceMaker
  * @author Mark S. Miller, but interim only until I examine how
  * ref_send/web_send (Tyler Close), qcomm (Kris Kowal), and BCap (Mark
  * Lentczner, Arjun Guha, Joe Politz) deal with similar issues.
- * //provides makeFarResourceMaker
- * @requires Q, cajaVM, this
+ * @overrides ses
+ * @requires Q, cajaVM
  * @requires UniformRequest, AnonXMLHttpRequest, XMLHttpRequest
  */

-
-(function(global) {
+var ses;
+
+(function() {
    "use strict";
+
+   if (ses && !ses.ok()) { return; }

    var bind = Function.prototype.bind;
    // See
@@ -37,14 +41,8 @@
    var applyFn = uncurryThis(bind.apply);
    var mapFn = uncurryThis([].map);

-   var def;
-   if (typeof cajaVM !== 'undefined') {
-     def = cajaVM.def;
-   } else {
-     // Don't bother being properly defensive when run outside of Caja
-     // or SES.
-     def = Object.freeze;
-   }
+   var freeze = Object.freeze;
+   var constFunc = cajaVM.constFunc;

    var XHR;
    if (typeof UniformRequest !== 'undefined') {
@@ -114,7 +112,7 @@

              } else if (this.status === 410) {
                var broken = Q.reject(new Error('Resource Gone'));
-               nextSlot.resolve(def({value: broken}));
+               nextSlot.resolve(freeze({value: broken}));
                result.resolve(broken);

              } else {
@@ -136,8 +134,8 @@

        return Q.makeFar(farDispatch, nextSlot.promise);
      }
-     return def(makeFarResource);
-   }
-   global.makeFarResourceMaker = def(makeFarResourceMaker);
-
- })(this);
+     return constFunc(makeFarResource);
+   }
+   ses.makeFarResourceMaker = constFunc(makeFarResourceMaker);
+
+ })();
=======================================
--- /trunk/src/com/google/caja/ses/makeQ.js     Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/makeQ.js     Mon Feb 13 14:48:15 2012
@@ -17,14 +17,19 @@
  * http://wiki.ecmascript.org/doku.php?id=strawman:concurrency
  * strawman, securely when run a Caja or SES platform.
  *
+ * //provides ses.makeQ
  * @author Mark S. Miller, based on earlier designs by Tyler Close,
  * Kris Kowal, and Kevin Reid.
- * //provides makeQ
- * @requires WeakMap, cajaVM, this
+ * @overrides ses
+ * @requires WeakMap, cajaVM
  */

-(function(global) {
+var ses;
+
+(function() {
    "use strict";
+
+   if (ses && !ses.ok()) { return; }

    var bind = Function.prototype.bind;
    // See
@@ -36,31 +41,11 @@
    var sliceFn = uncurryThis([].slice);
    var toStringFn = uncurryThis({}.toString);

-   var def;
-   if (typeof cajaVM !== 'undefined') {
-     def = cajaVM.def;
-   } else {
-     // Don't bother being properly defensive when run outside of Caja
-     // or SES.
-     def = Object.freeze;
-   }
-
-   /**
-    * http://wiki.ecmascript.org/doku.php?id=harmony:egal
-    */
-   var is = Object.is || def(function(x, y) {
-     if (x === y) {
-       // 0 === -0, but they are not identical
-       return x !== 0 || 1 / x === 1 / y;
-     }
-
-     // NaN !== NaN, but they are identical.
-     // NaNs are the only non-reflexive value, i.e., if x !== x,
-     // then x is a NaN.
-     // isNaN is broken: it converts its argument to number, so
-     // isNaN("foo") => true
-     return x !== x && y !== y;
-   });
+   var freeze = Object.freeze;
+   var constFunc = cajaVM.constFunc;
+   var def = cajaVM.def;
+   var is = cajaVM.is;
+

    /**
     * Tests if the presumably thrown error is simply signaling the end
@@ -248,12 +233,12 @@
        nearer: function() { return this.promise; },

        dispatch: function(OP, args) {
-         if (OP === 'WHEN')  { return this.WHEN (args[0], args[1]); }
+         if (OP === 'WHEN') { return this.WHEN (args[0], args[1]); }
          return this.promise;
        },

        /** Just invoke fk, the failure continuation */
-       WHEN:  function(sk, fk)         { return fk(this.reason); }
+       WHEN:  function(sk, fk) { return fk(this.reason); }
      };

      /**
@@ -333,7 +318,13 @@
      function defer() {
        var buffer = [];
        function queue(messenger) {
-         buffer.push(messenger);
+         if (buffer) {
+           buffer.push(messenger);
+         } else {
+           // This case seems to have happened once but I have not yet
+           // been able to reproduce it.
+           debugger;
+         }
        }
        var promise = new Promise(UnresolvedHandler, queue);
        var handler = handle(promise);
@@ -365,9 +356,9 @@
          }
        }

-       return def({
+       return freeze({
          promise: promise,
-         resolve: resolve
+         resolve: constFunc(resolve)
        });
      }

@@ -392,10 +383,10 @@
      FarHandler.prototype = {
        stateName: 'far',

-       nearer: function()       { return this.promise; },
+       nearer: function() { return this.promise; },

        /** Just invoke sk, the success continuation */
-       WHEN:   function(sk, fk) { return sk(this.promise); }
+       WHEN: function(sk, fk) { return sk(this.promise); }
      };

      function makeFar(farDispatch, nextSlotP) {
@@ -483,7 +474,8 @@
       * a remote machine) or elsewhen (e.g., not yet computed).
       *
       * <p>The Promise constructor must not escape. Clients of this module
-      * use the Q function to make promises from non-promises.
+      * use the Q function to make promises from non-promises. Since
+      * Promise.prototype does escape, it must not point back at Promise.
       *
       * <p>The various methods on a genuine promise never execute "user
       * code", i.e., possibly untrusted client code, during the immediate
@@ -496,9 +488,14 @@
      function Promise(HandlerMaker, arg) {
        var handler = new HandlerMaker(this, arg);
        handlers.set(this, handler);
-       def(this);
-     }
-     Promise.prototype = {
+       freeze(this);
+     }
+     function DontMakePromise() {
+       throw new Error('Make promises by calling Q()');
+     }
+     DontMakePromise.prototype = Promise.prototype = {
+       constructor: DontMakePromise,
+
        toString: function() {
          return '[' + handle(this).stateName + ' promise]';
        },
@@ -560,6 +557,7 @@
          });
        }
      };
+     def(DontMakePromise);

      function nearer(target1) {
        var optHandler = handle(target1);
@@ -654,7 +652,7 @@
          }
          return resultP;
        }
-       return def(oneArgMemo);
+       return constFunc(oneArgMemo);
      };

      /**
@@ -665,7 +663,7 @@
       * explanation.
       */
      Q.async = function(generatorFunc) {
-       return function asyncFunc(var_args) {
+       function asyncFunc(var_args) {
          var args = sliceFn(arguments, 0);
          var generator = generatorFunc.apply(this, args);
          var callback = continuer.bind(void 0, 'send');
@@ -683,11 +681,12 @@
          }

          return callback(void 0);
-       };
+       }
+       return constFunc(asyncFunc);
      };

      return def(Q);
    };
    def(makeQ);
-   global.makeQ = makeQ;
- })(this);
+   ses.makeQ = makeQ;
+ })();
=======================================
--- /trunk/src/com/google/caja/ses/makeSimpleAMDLoader.js Mon Dec 19 23:54:05 2011 +++ /trunk/src/com/google/caja/ses/makeSimpleAMDLoader.js Mon Feb 13 14:48:15 2012
@@ -18,13 +18,14 @@
  * https://github.com/amdjs/amdjs-api/wiki/AMD . Based on
* http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#amd_loader_lite
  *
- * @author Mark S. Miller
  * //provides makeSimpleAMDLoader
- * @requires Q, StringMap, cajaVM, this
+ * @author Mark S. Miller
+ * @requires StringMap, cajaVM
+ * @requires this, compileExprLater, Q
  */


-(function(global){
+(function(imports){
    "use strict";

    var bind = Function.prototype.bind;
@@ -35,14 +36,9 @@
    var applyFn = uncurryThis(bind.apply);
    var mapFn = uncurryThis([].map);

-   var def;
-   if (typeof cajaVM !== 'undefined') {
-     def = cajaVM.def;
-   } else {
-     // Don't bother being properly defensive when run outside of Caja
-     // or SES.
-     def = Object.freeze;
-   }
+   var freeze = Object.freeze;
+   var constFunc = cajaVM.constFunc;
+

    /**
     * A pumpkin is a unique value that must never escape, and so may
@@ -59,7 +55,7 @@
     * means that it either immediately returns an X or it immediately
     * returns a promise that it eventually fulfills with an X. Unless
     * stated otherwise, we implicitly elide the error conditions from
-    * such statements. The more explicit statement append: "or it
+    * such statements. For the more explicit statement, append: "or it
     * throws, or it does not terminate, or it breaks the returned
     * promise, or it never resolves the returned promise."
     *
@@ -102,6 +98,7 @@

      function rawLoad(id) {
        return Q(fetch(id)).when(function(src) {
+
          var result = defineNotCalledPumpkin;
          function define(opt_id, deps, factory) {
            if (typeof opt_id === 'string') {
@@ -121,19 +118,26 @@
          }
          // TODO(erights): Once we're jQuery compatible, change
          // jQuery: to true.
-         define.amd = { lite: true, caja: true, jQuery: false };
-         def(define);
-
-         Function('define', src)(define);
-         if (result === defineNotCalledPumpkin) {
-           result = Q.reject(new Error('"define" not called by: ' + id));
-         }
-         return result;
+         define.amd = freeze({ lite: true, caja: true, jQuery: false });
+
+         var imports = cajaVM.makeImports();
+         cajaVM.copyToImports(imports, {define: constFunc(define)});
+
+         var compiledExprP = compileExprLater(
+           '(function(){' + src + '})()', id);
+         return Q(compiledExprP).when(function(compiledExpr) {
+
+           compiledExpr(imports);
+           if (result === defineNotCalledPumpkin) {
+             result = Q.reject(new Error('"define" not called by: ' + id));
+           }
+           return result;
+         });
        });
      }
      return loader = Q.memoize(rawLoad, moduleMap);
    }

-   global.makeSimpleAMDLoader = def(makeSimpleAMDLoader);
+   imports.makeSimpleAMDLoader = constFunc(makeSimpleAMDLoader);

  })(this);
=======================================
--- /trunk/src/com/google/caja/ses/repairES5.js Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/repairES5.js Mon Feb 13 14:48:15 2012
@@ -27,6 +27,11 @@
  * create it, use it, and delete it all within this module. But we
  * need to lie to the linter since it can't tell.
  *
+ * //provides ses.statuses, ses.ok, ses.is, ses.makeDelayedTamperProof
+ * //provides ses.makeCallerHarmless, ses.makeArgumentsHarmless
+ * //provides ses.severities, ses.maxSeverity, ses.updateMaxSeverity
+ * //provides ses.maxAcceptableSeverityName, ses.maxAcceptableSeverity
+ *
  * @author Mark S. Miller
  * @requires ___global_test_function___, ___global_valueOf_function___
  * @requires JSON, navigator, this, eval, document
@@ -279,6 +284,24 @@

   var builtInForEach = Array.prototype.forEach;

+  /**
+   * http://wiki.ecmascript.org/doku.php?id=harmony:egal
+   */
+  var is = ses.is = Object.is || function(x, y) {
+    if (x === y) {
+      // 0 === -0, but they are not identical
+      return x !== 0 || 1 / x === 1 / y;
+    }
+
+    // NaN !== NaN, but they are identical.
+    // NaNs are the only non-reflexive value, i.e., if x !== x,
+    // then x is a NaN.
+    // isNaN is broken: it converts its argument to number, so
+    // isNaN("foo") => true
+    return x !== x && y !== y;
+  };
+
+
   /**
    * By the time this module exits, either this is repaired to be a
    * function that is adequate to make the "caller" property of a
@@ -322,63 +345,117 @@
   };

   /**
+   * "makeTamperProof()" returns a "tamperProof(obj)" function that
+   * acts like "Object.freeze(obj)", except that, if obj is a
+   * <i>prototypical</i> object (defined below), it ensures that the
+   * effect of freezing properties of obj does not suppress the
+   * ability to override these properties on derived objects by simple
+   * assignment.
    *
+   * <p>Because of lack of sufficient foresight at the time, ES5
+   * unfortunately specified that a simple assignment to a
+   * non-existent property must fail if it would override a
+   * non-writable data property of the same name. (In retrospect, this
+   * was a mistake, but it is now too late and we must live with the
+   * consequences.) As a result, simply freezing an object to make it
+   * tamper proof has the unfortunate side effect of breaking
+   * previously correct code that is considered to have followed JS
+   * best practices, if this previous code used assignment to
+   * override.
+   *
+   * <p>To work around this mistake, tamperProof(obj) detects if obj
+   * is <i>prototypical</i>, i.e., is an object whose own
+ * "constructor" is a function whose "prototype" is this obj. For example,
+   * Object.prototype and Function.prototype are prototypical.  If so,
+   * then when tamper proofing it, prior to freezing, replace all its
+   * configurable own data properties with accessor properties which
+   * simulate what we should have specified -- that assignments to
+   * derived objects succeed if otherwise possible.
+   *
+   * <p>Some platforms (Chrome and Safari as of this writing)
+   * implement the assignment semantics ES5 should have specified
+   * rather than what it did specify.
+   * "test_ASSIGN_CAN_OVERRIDE_FROZEN()" below tests whether we are on
+   * such a platform. If so, "repair_ASSIGN_CAN_OVERRIDE_FROZEN()"
+   * replaces "makeTamperProof" with a function that simply returns
+   * "Object.freeze", since the complex workaround here is not needed
+   * on those platforms.
+   *
+   * <p>"makeTamperProof" should only be called after the trusted
+   * initialization has done all the monkey patching that it is going
+   * to do on the Object.* methods, but before any untrusted code runs
+   * in this context.
    */
-  ses.defendOne = function(obj) {
+  var makeTamperProof = function defaultMakeTamperProof() {
+
+    // Sample these after all trusted monkey patching initialization
+    // but before any untrusted code runs in this frame.
     var gopd = Object.getOwnPropertyDescriptor;
     var gopn = Object.getOwnPropertyNames;
     var getProtoOf = Object.getPrototypeOf;
+    var freeze = Object.freeze;
     var isFrozen = Object.isFrozen;
     var defProp = Object.defineProperty;

-    if (obj !== Object(obj)) { return obj; }
-    var func;
-    if (typeof obj === 'object' &&
-        !!gopd(obj, 'constructor') &&
-        typeof (func = obj.constructor) === 'function' &&
-        func.prototype === obj &&
-        !isFrozen(obj)) {
-      gopn(obj).forEach(function(name) {
-        var value;
-        function getter() {
-          if (obj === this) { return value; }
-          if (!this) { return void 0; }
-          if (!!gopd(this, name)) { return this[name]; }
-          return getter.call(getProtoOf(this));
-        }
-        function setter(newValue) {
-          if (obj === this) {
-            throw new TypeError('Cannot set virtually frozen property: ' +
-                                name);
-          }
-          if (!!gopd(this, name)) {
-            this[name] = newValue;
-          }
-          // TODO(erights): Do all the inherited property checks
-          defProp(this, name, {
-            value: newValue,
-            writable: true,
-            enumerable: true,
-            configurable: true
-          });
-        }
-        var desc = gopd(obj, name);
-        if (desc.configurable && 'value' in desc) {
-          value = desc.value;
-          defProp(obj, name, {
-            get: getter,
-            set: setter,
-            // We should be able to omit the enumerable line, since it
-            // should default to its existing setting.
-            enumerable: desc.enumerable,
-            configurable: false
-          });
-        }
-      });
-    }
-    return Object.freeze(obj);
+    function tamperProof(obj) {
+      if (obj !== Object(obj)) { return obj; }
+      var func;
+      if (typeof obj === 'object' &&
+          !!gopd(obj, 'constructor') &&
+          typeof (func = obj.constructor) === 'function' &&
+          func.prototype === obj &&
+          !isFrozen(obj)) {
+        strictForEachFn(gopn(obj), function(name) {
+          var value;
+          function getter() {
+            if (obj === this) { return value; }
+            if (this === void 0 || this === null) { return void 0; }
+            var thisObj = Object(this);
+            if (!!gopd(thisObj, name)) { return this[name]; }
+            // TODO(erights): If we can reliably uncurryThis() in
+            // repairES5.js, the next line should be:
+            //   return callFn(getter, getProtoOf(thisObj));
+            return getter.call(getProtoOf(thisObj));
+          }
+          function setter(newValue) {
+            if (obj === this) {
+ throw new TypeError('Cannot set virtually frozen property: ' +
+                                  name);
+            }
+            if (!!gopd(this, name)) {
+              this[name] = newValue;
+            }
+            // TODO(erights): Do all the inherited property checks
+            defProp(this, name, {
+              value: newValue,
+              writable: true,
+              enumerable: true,
+              configurable: true
+            });
+          }
+          var desc = gopd(obj, name);
+          if (desc.configurable && 'value' in desc) {
+            value = desc.value;
+            getter.prototype = null;
+            setter.prototype = null;
+            defProp(obj, name, {
+              get: getter,
+              set: setter,
+              // We should be able to omit the enumerable line, since it
+              // should default to its existing setting.
+              enumerable: desc.enumerable,
+              configurable: false
+            });
+          }
+        });
+      }
+      return freeze(obj);
+    }
+    return tamperProof;
   };

+
+  var needToTamperProof = [];
   /**
    * Various repairs may expose non-standard objects that are not
    * reachable from startSES's root, and therefore not freezable by
@@ -388,12 +465,26 @@
    * order to install hidden properties for its own use before the
    * object becomes non-extensible.
    */
-  var needToFreeze = [];
-  function delayedFreeze(obj) {
-    needToFreeze.push(obj);
-  }
-  ses.freezeDelayed = function freezeDelayed() {
-    needToFreeze.forEach(ses.defendOne);
+  function rememberToTamperProof(obj) {
+    needToTamperProof.push(obj);
+  }
+
+  /**
+   * Makes and returns a tamperProof(obj) function, and uses it to
+   * tamper proof all objects whose tamper proofing had been delayed.
+   *
+   * <p>"makeDelayedTamperProof()" must only be called once.
+   */
+  var makeDelayedTamperProofCalled = false;
+  ses.makeDelayedTamperProof = function makeDelayedTamperProof() {
+    if (makeDelayedTamperProofCalled) {
+      throw "makeDelayedTamperProof() must only be called once.";
+    }
+    var tamperProof = makeTamperProof();
+    strictForEachFn(needToTamperProof, tamperProof);
+    needToTamperProof = void 0;
+    makeDelayedTamperProofCalled = true;
+    return tamperProof;
   };

   /**
@@ -801,23 +892,28 @@
   }

   /**
-   * 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 [EMAIL PROTECTED] Object.defineProperty} to always
-   * install a dummy setter in lieu of the absent one.
+   * accessor property with a getter but no setter, Chrome <= 17 fails
+   * with <blockquote>Uncaught TypeError: Cannot set property ident___
+   * of #<Object> which has only a getter</blockquote>. So if
+   * necessary, this kludge overrides [EMAIL PROTECTED] Object.defineProperty} 
to
+   * always install a dummy setter in lieu of the absent one.
+   *
+   * <p>Since this problem seems to have gone away as of Chrome 18, it
+   * is no longer as important to isolate and report it.
    *
    * <p>TODO(erights): We should also override [EMAIL PROTECTED]
    * Object.getOwnPropertyDescriptor} to hide the presence of the
    * dummy setter, and instead report an absent setter.
    */
   function test_NEEDS_DUMMY_SETTER() {
-    return (typeof navigator !== 'undefined' &&
-            (/Chrome/).test(navigator.userAgent) &&
-            !NEEDS_DUMMY_SETTER_repaired);
+    if (NEEDS_DUMMY_SETTER_repaired) { return false; }
+    if (typeof navigator === 'undefined') { return false; }
+    var ChromeMajorVersionPattern = (/Chrome\/(\d*)\./);
+    var match = ChromeMajorVersionPattern.exec(navigator.userAgent);
+    if (!match) { return false; }
+    var ver = +match[1];
+    return ver <= 17;
   }
   /** we use this variable only because we haven't yet isolated a test
    * for the problem. */
@@ -1022,7 +1118,7 @@
       result = name in base;
     } catch (err) {
       logger.error('New symptom (a): (\'' +
-                   name + '\' in <' + baseDesc + '>) threw: ' + err);
+                   name + '\' in <' + baseDesc + '>) threw: ', err);
       // treat this as a safe absence
       result = false;
       return false;
@@ -1036,7 +1132,7 @@
     if (finallySkipped) {
       logger.error('New symptom (e): (\'' +
                    name + '\' in <' + baseDesc +
-                   '>) finally inner finally skipped');
+                   '>) inner finally skipped');
     }
     return !!result;
   }
@@ -1047,11 +1143,8 @@
     try {
       result = has(base, name, baseDesc);
     } catch (err) {
-      // This case should be already be reported as a failure of
-      // test_CANT_IN_CALLER or test_CANT_IN_ARGUMENTS, and so is no
-      // longer a new symptom.
-      // logger.error('New symptom (c): (\'' +
-      //              name + '\' in <' + baseDesc + '>) threw: ' + err);
+      logger.error('New symptom (c): (\'' +
+                   name + '\' in <' + baseDesc + '>) threw: ', err);
       // treat this as a safe absence
       result = false;
       return false;
@@ -1065,7 +1158,7 @@
     if (finallySkipped) {
       logger.error('New symptom (f): (\'' +
                    name + '\' in <' + baseDesc +
-                   '>) finally outer finally skipped');
+                   '>) outer finally skipped');
     }
     return !!result;
   }
@@ -1422,7 +1515,15 @@
   }

   /**
+   * Detects whether assignment can override an inherited
+   * non-writable, non-configurable data property.
    *
+   * <p>According to ES5.1, assignment should not be able to do so,
+   * which is unfortunate for SES, as the tamperProof function must
+   * kludge expensively to ensure that legacy assignments that don't
+   * violate best practices continue to work. Ironically, on platforms
+   * in which this bug is present, tamperProof can just be cheaply
+   * equivalent to Object.freeze.
    */
   function test_ASSIGN_CAN_OVERRIDE_FROZEN() {
     var x = Object.freeze({foo: 88});
@@ -1438,6 +1539,175 @@
     return 'Unexpected override outcome: ' + y.foo;
   }

+  /**
+   *
+   */
+  function test_CANT_REDEFINE_NAN_TO_ITSELF() {
+    var descNaN = Object.getOwnPropertyDescriptor(global, 'NaN');
+    try {
+      Object.defineProperty(global, 'NaN', descNaN);
+    } catch (err) {
+      if (err instanceof TypeError) { return true; }
+      return 'defineProperty of NaN failed with: ' + err;
+    }
+    return false;
+  }
+
+  /**
+   * These are all the own properties that appear on Error instances
+   * on various ES5 platforms as of this writing.
+   *
+   * <p>Due to browser bugs, some of these are absent from
+   * getOwnPropertyNames (gopn). TODO(erights): File bugs with various
+   * browser makers for any own properties that we know to be present
+   * but not reported by gopn.
+   *
+   * <p>TODO(erights): do intelligence with the various browser
+   * implementors to find out what other properties are provided by
+   * their implementation but absent from gopn, whether on Errors or
+   * anything else. Every one of these are potentially fatal to our
+   * security until we can examine these.
+   *
+   * <p>The source form is a list rather than a map so that we can list a
+   * name like "message" for each browser section we think it goes in.
+   *
+   * <p>We thank the following people, projects, and websites for
+   * providing some useful intelligence of what property names we
+   * should suspect:<ul>
+   * <li><a href="http://stacktracejs.org">stacktracejs.org</a>
+   * <li>TODO(erights): find message on es-discuss list re
+   * "   stack". credit author.
+   * </ul>
+   */
+  var errorInstanceWhitelist = [
+    // at least Chrome 16
+    'arguments',
+    'message',
+    'stack',
+    'type',
+
+    // at least FF 9
+    'fileName',
+    'lineNumber',
+    'message',
+    'stack',
+
+    // at least Safari, WebKit 5.1
+    'line',
+    'message',
+    'sourceId',
+    'sourceURL',
+
+    // at least IE 10 preview 2
+    'description',
+    'message',
+    'number',
+
+    // at least Opera 11.60
+    'message',
+    'stack',
+    'stacktrace'
+  ];
+
+  var errorInstanceBlacklist = [
+    // seen in a Firebug on FF
+    'category',
+    'context',
+    'href',
+    'lineNo',
+    'msgId',
+    'source',
+    'trace',
+    'correctSourcePoint',
+    'correctWithStackTrace',
+    'getSourceLine',
+    'resetSource'
+  ];
+
+  /** Return a fresh one so client can mutate freely */
+  function freshErrorInstanceWhiteMap() {
+    var result = Object.create(null);
+    strictForEachFn(errorInstanceWhitelist, function(name) {
+      // We cannot yet use StringMap so do it manually
+      // We do this naively here assuming we don't need to worry about
+      // __proto__
+      result[name] = true;
+    });
+    return result;
+  }
+
+  function freshHiddenPropertyCandidates() {
+    var result = freshErrorInstanceWhiteMap();
+    strictForEachFn(errorInstanceBlacklist, function(name) {
+      result[name] = true;
+    });
+    return result;
+  }
+
+  /**
+   * Do Error instances on those platform carry own properties that we
+   * haven't yet examined and determined to be SES-safe?
+   *
+   * <p>A new property should only be added to the
+   * errorInstanceWhitelist after inspecting the consequences of that
+   * property to determine that it does not compromise SES safety. If
+   * some platform maker does add an Error own property that does
+   * compromise SES safety, that might be a severe problem, if we
+   * can't find a way to deny untrusted code access to that property.
+   */
+  function test_UNEXPECTED_ERROR_PROPERTIES() {
+    var errs = [new Error('e1')];
+    try { null.foo = 3; } catch (err) { errs.push(err); }
+    var result = false;
+
+    var approvedNames = freshErrorInstanceWhiteMap();
+
+    strictForEachFn(errs, function(err) {
+      strictForEachFn(Object.getOwnPropertyNames(err), function(name) {
+         if (!(name in approvedNames)) {
+           result = 'Unexpected error instance property: ' + name;
+           // would be good to terminate early
+         }
+      });
+    });
+    return result;
+  }
+
+  /**
+   *
+   */
+  function test_GET_OWN_PROPERTY_NAME_LIES() {
+    var gopn = Object.getOwnPropertyNames;
+    var gopd = Object.getOwnPropertyDescriptor;
+
+    var suspects = [new Error('e1')];
+    try { null.foo = 3; } catch (err) { suspects.push(err); }
+
+    var unreported = Object.create(null);
+
+    strictForEachFn(suspects, function(suspect) {
+      var candidates = freshHiddenPropertyCandidates();
+      strictForEachFn(gopn(suspect), function(name) {
+        // Delete the candidates that are reported
+        delete candidates[name];
+      });
+      strictForEachFn(gopn(candidates), function(name) {
+        if (!gopd(suspect, name)) {
+          // Delete the candidates that are not own properties
+          delete candidates[name];
+        }
+      });
+      strictForEachFn(gopn(candidates), function(name) {
+        unreported[name] = true;
+      });
+    });
+
+    var unreportedNames = gopn(unreported);
+    if (unreportedNames.length === 0) { return false; }
+    return 'Error own properties unreported by getOwnPropertyNames: ' +
+      unreportedNames.sort().join(',');
+  }
+

   ////////////////////// Repairs /////////////////////
   //
@@ -1488,8 +1758,8 @@
           'value' in desc) {
         try {
           base.prototype = desc.value;
-        } catch (x) {
-          logger.warn('prototype fixup failed');
+        } catch (err) {
+          logger.warn('prototype fixup failed', err);
         }
       }
       return unsafeDefProp(base, name, desc);
@@ -1590,9 +1860,9 @@
     var BOGUS_BOUND_PROTOTYPE = {
       toString: function BBPToString() { return 'bogus bound prototype'; }
     };
-    delayedFreeze(BOGUS_BOUND_PROTOTYPE);
-    delayedFreeze(BOGUS_BOUND_PROTOTYPE.toString);
-    delayedFreeze(BOGUS_BOUND_PROTOTYPE.toString.prototype);
+    rememberToTamperProof(BOGUS_BOUND_PROTOTYPE);
+    BOGUS_BOUND_PROTOTYPE.toString.prototype = null;
+    rememberToTamperProof(BOGUS_BOUND_PROTOTYPE.toString);

     var defProp = Object.defineProperty;
     defProp(Function.prototype, 'bind', {
@@ -1725,54 +1995,53 @@
     function dummySetter(newValue) {
       throw new TypeError('no setter for assigning: ' + newValue);
     }
-    delayedFreeze(dummySetter.prototype);
-    delayedFreeze(dummySetter);
+    dummySetter.prototype = null;
+    rememberToTamperProof(dummySetter);

     defProp(Object, 'defineProperty', {
       value: function setSetterDefProp(base, name, desc) {
-        if (typeof desc.get === 'function' &&
-            desc.set === undefined &&
-            objToString.call(base) === '[object HTMLFormElement]' &&
-            gopd(base, name) === void 0) {
-          // This repair was triggering bug
-          // http://code.google.com/p/chromium/issues/detail?id=94666
-          // on Chrome, causing
-          // http://code.google.com/p/google-caja/issues/detail?id=1401
-          // so if base is an HTMLFormElement we skip this
-          // fix. Since this repair and this situation are both
-          // Chrome only, it is ok that we're conditioning this on
-          // the unspecified [[Class]] value of base.
-          //
-          // To avoid the further bug identified at Comment 2
-          // http://code.google.com/p/chromium/issues/detail?id=94666#c2
-          // We also have to reconstruct the requested desc so that
-          // the setter is absent. This is why we additionally
-          // condition this special case on the absence of an own
-          // name property on base.
-          var desc2 = { get: desc.get };
-          if ('enumerable' in desc) {
-            desc2.enumerable = desc.enumerable;
-          }
-          if ('configurable' in desc) {
-            desc2.configurable = desc.configurable;
-          }
-          var result = defProp(base, name, desc2);
-          var newDesc = gopd(base, name);
-          if (newDesc.get === desc.get) {
-            return result;
+        if (typeof desc.get === 'function' && desc.set === void 0) {
+          var oldDesc = gopd(base, name);
+          if (oldDesc) {
+            var testBase = {};
+            defProp(testBase, name, oldDesc);
+            defProp(testBase, name, desc);
+            desc = gopd(testBase, name);
+            if (desc.set === void 0) { desc.set = dummySetter; }
+          } else {
+            if (objToString.call(base) === '[object HTMLFormElement]') {
+              // This repair was triggering bug
+              // http://code.google.com/p/chromium/issues/detail?id=94666
+              // on Chrome, causing
+              // http://code.google.com/p/google-caja/issues/detail?id=1401
+              // so if base is an HTMLFormElement we skip this
+              // fix. Since this repair and this situation are both
+              // Chrome only, it is ok that we're conditioning this on
+              // the unspecified [[Class]] value of base.
+              //
+              // To avoid the further bug identified at Comment 2
+ // http://code.google.com/p/chromium/issues/detail?id=94666#c2
+              // We also have to reconstruct the requested desc so that
+              // the setter is absent. This is why we additionally
+              // condition this special case on the absence of an own
+              // name property on base.
+              var desc2 = { get: desc.get };
+              if ('enumerable' in desc) {
+                desc2.enumerable = desc.enumerable;
+              }
+              if ('configurable' in desc) {
+                desc2.configurable = desc.configurable;
+              }
+              var result = defProp(base, name, desc2);
+              var newDesc = gopd(base, name);
+              if (newDesc.get === desc.get) {
+                return result;
+              }
+            }
+            desc.set = 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 === void 0) {
-          fullDesc.set = dummySetter;
-        }
-        return defProp(base, name, fullDesc);
+        return defProp(base, name, desc);
       }
     });
     NEEDS_DUMMY_SETTER_repaired = true;
@@ -1923,6 +2192,7 @@
             (name === 'caller' || name === 'arguments')) {
           return (function(message) {
              function fakePoison() { throw new TypeError(message); }
+             fakePoison.prototype = null;
              return {
                get: fakePoison,
                set: fakePoison,
@@ -1951,6 +2221,7 @@
     function poison() {
       throw new TypeError('Cannot access property ' + path);
     }
+    poison.prototype = null;
     var desc = Object.getOwnPropertyDescriptor(func, magicName);
     if ((!desc && Object.isExtensible(func)) || desc.configurable) {
       try {
@@ -2090,9 +2361,35 @@
   }

   function repair_ASSIGN_CAN_OVERRIDE_FROZEN() {
-    ses.defendOne = Object.freeze;
+    makeTamperProof = function simpleMakeTamperProof() {
+      return Object.freeze;
+    };
   }

+  function repair_CANT_REDEFINE_NAN_TO_ITSELF() {
+    var defProp = Object.defineProperty;
+    // 'value' handled separately
+    var attrs = ['writable', 'get', 'set', 'enumerable', 'configurable'];
+
+    defProp(Object, 'defineProperty', {
+      value: function(base, name, desc) {
+        try {
+          return defProp(base, name, desc);
+        } catch (err) {
+          var oldDesc = Object.getOwnPropertyDescriptor(base, name);
+          for (var i = 0, len = attrs.length; i < len; i++) {
+            var attr = attrs[i];
+ if (attr in desc && desc[attr] !== oldDesc[attr]) { throw err; }
+          }
+          if (!('value' in desc) || is(desc.value, oldDesc.value)) {
+            return base;
+          }
+          throw err;
+        }
+      }
+    });
+  }
+

   ////////////////////// Kludge Records /////////////////////
   //
@@ -2171,14 +2468,14 @@
       tests: ['S10.4.3_A1']
     },
     {
-       description: 'Global leaks through strict this',
-       test: test_GLOBAL_LEAKS_FROM_STRICT_THIS,
-       repair: void 0,
-       preSeverity: severities.NOT_ISOLATED,
-       canRepair: false,
-       urls: [],
-       sections: ['10.4.3'],
-       tests: ['10.4.3-1-8gs', '10.4.3-1-8-s']
+      description: 'Global leaks through strict this',
+      test: test_GLOBAL_LEAKS_FROM_STRICT_THIS,
+      repair: void 0,
+      preSeverity: severities.NOT_ISOLATED,
+      canRepair: false,
+      urls: [],
+      sections: ['10.4.3'],
+      tests: ['10.4.3-1-8gs', '10.4.3-1-8-s']
     },
     {
       description: 'Global object leaks from built-in methods',
@@ -2597,11 +2894,43 @@
                '2011-November/017997.html'],
       sections: ['8.12.4'],
       tests: ['15.2.3.6-4-405']
+    },
+    {
+      description: 'Cannot redefine global NaN to itself',
+      test: test_CANT_REDEFINE_NAN_TO_ITSELF,
+      repair: repair_CANT_REDEFINE_NAN_TO_ITSELF,
+      preSeverity: severities.SAFE_SPEC_VIOLATION,
+      canRepair: true,
+      urls: [], // Seen on WebKit Nightly. TODO(erights): report
+      sections: ['8.12.9', '15.1.1.1'],
+      tests: [] // TODO(erights): Add to test262
+    },
+    {
+      description: 'Error instances have unexpected properties',
+      test: test_UNEXPECTED_ERROR_PROPERTIES,
+      repair: void 0,
+      preSeverity: severities.NEW_SYMPTOM,
+      canRepair: false,
+      urls: [],
+      sections: [],
+      tests: []
+    },
+    {
+      description: 'getOwnPropertyNames lies, hiding some own properties',
+      test: test_GET_OWN_PROPERTY_NAME_LIES,
+      repair: void 0,
+      preSeverity: severities.NOT_ISOLATED,
+      canRepair: false,
+      urls: [],
+      sections: [],
+      tests: []
     }
   ];

   ////////////////////// Testing, Repairing, Reporting ///////////

+  var aboutTo = void 0;
+
   /**
    * Run a set of tests & repairs, and report results.
    *
@@ -2613,6 +2942,7 @@
    */
   function testRepairReport(kludges) {
     var beforeFailures = strictMapFn(kludges, function(kludge) {
+      aboutTo = ['pre test: ', kludge.description];
       return kludge.test();
     });
     var repairs = [];
@@ -2620,12 +2950,14 @@
       if (beforeFailures[i]) {
         var repair = kludge.repair;
         if (repair && repairs.lastIndexOf(repair) === -1) {
+          aboutTo = ['repair: ', kludge.description];
           repair();
           repairs.push(repair);
         }
       }
     });
     var afterFailures = strictMapFn(kludges, function(kludge) {
+      aboutTo = ['post test: ', kludge.description];
       return kludge.test();
     });

@@ -2706,7 +3038,8 @@
     logger.reportRepairs(reports);
   } catch (err) {
     ses.updateMaxSeverity(ses.severities.NOT_SUPPORTED);
-    logger.error('ES5 Repair failed with: ' + err);
+    var during = aboutTo ? '(' + aboutTo.join('') + ') ' : '';
+    logger.error('ES5 Repair ' + during + 'failed with: ', err);
   }

   logger.reportMax();
=======================================
--- /trunk/src/com/google/caja/ses/startSES.js  Mon Jan  9 17:07:50 2012
+++ /trunk/src/com/google/caja/ses/startSES.js  Mon Feb 13 14:48:15 2012
@@ -18,7 +18,9 @@
  * <p>Assumes ES5 plus a WeakMap that conforms to the anticipated ES6
  * WeakMap spec. Compatible with ES5-strict or anticipated ES6.
  *
+ * //provides ses.startSES
  * @author Mark S. Miller,
+ * @author Jasvir Nagra
  * @requires WeakMap
  * @overrides ses, console, eval, Function, cajaVM
  */
@@ -248,6 +250,17 @@
   var freeze = Object.freeze;
   var create = Object.create;

+  /**
+   * Use to tamper proof a function which is not intended to ever be
+   * used as a constructor, since it nulls out the function's
+   * prototype first.
+   */
+  function constFunc(func) {
+    func.prototype = null;
+    return freeze(func);
+  }
+
+
   function fail(str) {
     debugger;
     throw new EvalError(str);
@@ -259,8 +272,8 @@


   if (EMULATE_LEGACY_GETTERS_SETTERS) {
-    defProp(Object.prototype, '__defineGetter__', {
-      value: function(sprop, getter) {
+    (function(){
+      function legacyDefineGetter(sprop, getter) {
         sprop = '' + sprop;
         if (hop.call(this, sprop)) {
           defProp(this, sprop, { get: getter });
@@ -272,13 +285,16 @@
             configurable: true
           });
         }
-      },
-      writable: false,
-      enumerable: false,
-      configurable: false
-    });
-    defProp(Object.prototype, '__defineSetter__', {
-      value: function(sprop, setter) {
+      }
+      legacyDefineGetter.prototype = null;
+      defProp(Object.prototype, '__defineGetter__', {
+        value: legacyDefineGetter,
+        writable: false,
+        enumerable: false,
+        configurable: false
+      });
+
+      function legacyDefineSetter(sprop, setter) {
         sprop = '' + sprop;
         if (hop.call(this, sprop)) {
           defProp(this, sprop, { set: setter });
@@ -290,33 +306,43 @@
             configurable: true
           });
         }
-      },
-      writable: false,
-      enumerable: false,
-      configurable: false
-    });
-    defProp(Object.prototype, '__lookupGetter__', {
-      value: function(sprop) {
+      }
+      legacyDefineSetter.prototype = null;
+      defProp(Object.prototype, '__defineSetter__', {
+        value: legacyDefineSetter,
+        writable: false,
+        enumerable: false,
+        configurable: false
+      });
+
+      function legacyLookupGetter(sprop) {
         sprop = '' + sprop;
         var base = this, desc = void 0;
while (base && !(desc = gopd(base, sprop))) { base = getProto(base); }
         return desc && desc.get;
-      },
-      writable: false,
-      enumerable: false,
-      configurable: false
-    });
-    defProp(Object.prototype, '__lookupSetter__', {
-      value: function(sprop) {
+      }
+      legacyLookupGetter.prototype = null;
+      defProp(Object.prototype, '__lookupGetter__', {
+        value: legacyLookupGetter,
+        writable: false,
+        enumerable: false,
+        configurable: false
+      });
+
+      function legacyLookupSetter(sprop) {
         sprop = '' + sprop;
         var base = this, desc = void 0;
while (base && !(desc = gopd(base, sprop))) { base = getProto(base); }
         return desc && desc.set;
-      },
-      writable: false,
-      enumerable: false,
-      configurable: false
-    });
+      }
+      legacyLookupSetter.prototype = null;
+      defProp(Object.prototype, '__lookupSetter__', {
+        value: legacyLookupSetter,
+        writable: false,
+        enumerable: false,
+        configurable: false
+      });
+    })();
   } else {
     delete Object.prototype.__defineGetter__;
     delete Object.prototype.__defineSetter__;
@@ -327,9 +353,10 @@

   /**
    * By this time, WeakMap has already monkey patched Object.freeze if
-   * necessary, so we can do the freezes delayed from repairES5.js
+   * necessary, so we can do the tamperProofing delayed from
+   * repairES5.js
    */
-  ses.freezeDelayed();
+  var tamperProof = ses.makeDelayedTamperProof();

   /**
    * Code being eval'ed by [EMAIL PROTECTED] cajaVM.eval} sees [EMAIL 
PROTECTED]
@@ -376,7 +403,12 @@
      * trick executes over 100x faster on V8.
      */
     function verifyStrictProgram(programSrc) {
-      UnsafeFunction('"use strict";' + programSrc);
+      try {
+        UnsafeFunction('"use strict";' + programSrc);
+      } catch (err) {
+        // debugger; // Useful for debugging -- to look at programSrc
+        throw err;
+      }
     }

     /**
@@ -461,7 +493,14 @@
      */
     function makeScopeObject(imports, freeNames) {
       var scopeObject = create(null);
-      freeNames.forEach(function(name) {
+      // Note: Although this loop is a bottleneck on some platforms,
+      // it does not help to turn it into a for(;;) loop, since we
+      // still need an enclosing function per accessor property
+      // created, to capture its own unique binding of
+      // "name". (Embarrasing fact: despite having often written about
+      // this very danger, I engaged in this mistake in a misbegotten
+      // optimization attempt here.)
+      freeNames.forEach(function interceptName(name) {
         var desc = gopd(imports, name);
         if (!desc || desc.writable !== false || desc.configurable) {
           // If there is no own property, or it isn't a non-writable
@@ -507,27 +546,35 @@


     /**
-     * Like [EMAIL PROTECTED] compileExpr} but does not freeze the function it
-     * returns.
+     * Given SES source text that must not be run directly using any
+     * of the built-in unsafe evaluators on this platform, we instead
+     * surround it with a prelude and postlude.
+     *
+     * <p>Evaluating the resulting expression return a function that
+     * <i>can</i>be called to execute the original expression safely,
+     * in a controlled scope. See "makeCompiledExpr" for precisely the
+     * pattern that must be followed to call the resulting function
+     * safely.
+     *
+     * Notice that the source text placed around [EMAIL PROTECTED] exprSrc}
+     * <ul>
+     * <li>brings no variable names into scope, avoiding any
+     *     non-hygienic name capture issues, and
+     * <li>does not introduce any newlines preceding exprSrc, so
+     *     that all line number which a debugger might report are
+     *     accurate wrt the original source text. And except for the
+     *     first line, all the column numbers are accurate too.
+     * </ul>
+     *
+     * <p>TODO(erights): Find out if any platforms have any way to
+     * associate a file name and line number with eval'ed text, so
+     * that we can do something useful with the optional [EMAIL PROTECTED]
+     * opt_sourcePosition} to better support debugging.
      */
-    function internalCompileExpr(exprSrc, opt_sourcePosition) {
-      if (dirty) { fail('Initial cleaning failed'); }
+    function securableWrapperSrc(exprSrc, opt_sourcePosition) {
       verifyStrictExpression(exprSrc);
-      var freeNames = atLeastFreeVarNames(exprSrc);
-
-      /**
-       * Notice that the source text placed around [EMAIL PROTECTED] exprSrc}
-       * <ul>
-       * <li>brings no variable names into scope, avoiding any
-       *     non-hygienic name capture issues, and
-       * <li>does not introduce any newlines preceding exprSrc, so
-       *     that all line number which a debugger might report are
-       *     accurate wrt the original source text. And except for the
-       *     first line, all the column numbers are accurate too.
-       * </ul>
-       */
-      var wrapperSrc =
-        '(function() { ' +
+
+      return '(function() { ' +
         // non-strict code, where this === scopeObject
         '  with (this) { ' +
         '    return function() { ' +
@@ -539,15 +586,32 @@
         '    };\n' +
         '  }\n' +
         '})';
-      var wrapper = unsafeEval(wrapperSrc);
+    }
+    ses.securableWrapperSrc = securableWrapperSrc;
+
+
+    /**
+     * Given a wrapper function, such as the result of evaluating the
+     * source that securableWrapperSrc returns, and a list of all the
+     * names that we want to intercept to redirect to the imports,
+     * return a corresponding <i>compiled expr</i> function.
+     *
+     * <p>A compiled expr function, when called on an imports
+     * object, evaluates the original expression in a context where
+     * all its free variable references that appear in freeNames are
+     * redirected to the corresponding property of imports.
+     */
+    function makeCompiledExpr(wrapper, freeNames) {
+      if (dirty) { fail('Initial cleaning failed'); }

       function compiledCode(imports) {
         var scopeObject = makeScopeObject(imports, freeNames);
         return wrapper.call(scopeObject).call(imports);
       };
-      freeze(compiledCode.prototype);
+      compiledCode.prototype = null;
       return compiledCode;
     }
+    ses.makeCompiledExpr = makeCompiledExpr;

     /**
      * Compiles [EMAIL PROTECTED] exprSrc} as a strict expression into a 
function
@@ -566,14 +630,12 @@
      * <p>Thanks to Mike Samuel and Ankur Taly for this trick of using
      * [EMAIL PROTECTED] with} together with RegExp matching to intercept free
      * variable access without parsing.
-     *
-     * <p>TODO(erights): Find out if any platforms have any way to
-     * associate a file name and line number with eval'ed text, so
-     * that we can do something useful with the optional [EMAIL PROTECTED]
-     * opt_sourcePosition} to better support debugging.
      */
     function compileExpr(exprSrc, opt_sourcePosition) {
-      var result = internalCompileExpr(exprSrc, opt_sourcePosition);
+      var wrapperSrc = securableWrapperSrc(exprSrc, opt_sourcePosition);
+      var wrapper = unsafeEval(wrapperSrc);
+      var freeNames = atLeastFreeVarNames(exprSrc);
+      var result = makeCompiledExpr(wrapper, freeNames);
       return freeze(result);
     }

@@ -637,9 +699,14 @@
      * from the text to be compiled.
      */
     function compileModule(modSrc, opt_sourcePosition) {
-      var moduleMaker = internalCompileExpr(
-        '(function() {' + modSrc + '}).call(this)',
-        opt_sourcePosition);
+      var exprSrc = '(function() {' + modSrc + '}).call(this)';
+
+      // Follow the pattern in compileExpr
+      var wrapperSrc = securableWrapperSrc(exprSrc, opt_sourcePosition);
+      var wrapper = unsafeEval(wrapperSrc);
+      var freeNames = atLeastFreeVarNames(exprSrc);
+      var moduleMaker = makeCompiledExpr(wrapper, freeNames);
+
       moduleMaker.requirements = getRequirements(modSrc);
       return freeze(moduleMaker);
     }
@@ -706,20 +773,27 @@
     var defended = WeakMap();
     var defending = WeakMap();
     /**
-     * To define a defended object is to freeze it and all objects
+     * To define a defended object is to tamperProof it and all objects
      * transitively reachable from it via transitive reflective
      * property and prototype traversal.
      */
     function def(node) {
       var defendingList = [];
       function recur(val) {
- if (val !== Object(val) || defended.get(val) || defending.get(val)) {
-          return;
-        }
+        if (!val) { return; }
+        var t = typeof val;
+ if (t === 'number' || t === 'string' || t === 'boolean') { return; }
+        if (defended.get(val) || defending.get(val)) { return; }
         defending.set(val, true);
         defendingList.push(val);
-        freeze(val);
+
+        tamperProof(val);
+
         recur(getProto(val));
+
+        // How to optimize? This is a performance sensitive loop, but
+        // forEach seems to be faster on Chrome 18 Canary but a
+        // for(;;) loop seems better on FF 12 Nightly.
         gopn(val).forEach(function(p) {
           if (typeof val === 'function' &&
               (p === 'caller' || p === 'arguments')) {
@@ -760,8 +834,7 @@
         var getter = lengthMap.get(this);
         return getter ? getter() : void 0;
       }
-      freeze(lengthGetter);
-      freeze(lengthGetter.prototype);
+      constFunc(lengthGetter);

       var nativeProxies = global.Proxy && (function () {
         var obj = {0: 'hi'};
@@ -794,13 +867,11 @@
             if (P === 'length') {
               return { get: lengthGetter };
             } else if (typeof P === 'number' || P === '' + (+P)) {
-              var get = freeze(function () {
-                var getter = itemMap.get(this);
-                return getter ? getter(+P) : void 0;
-              });
-              freeze(get.prototype);
               return {
-                get: get,
+                get: constFunc(function() {
+                  var getter = itemMap.get(this);
+                  return getter ? getter(+P) : void 0;
+                }),
                 enumerable: true,
                 configurable: true
               };
@@ -860,7 +931,7 @@
             'delete': del,
             fix: function() { return void 0; }
           }, Object.prototype);
-          freeze(ArrayLike);
+          tamperProof(ArrayLike);
           makeArrayLike = function() { return ArrayLike; };
         })();
       } else {
@@ -905,21 +976,20 @@
               // Install native numeric getters.
               for (var i = 0; i < len; i++) {
                 (function(j) {
-                  var get = freeze(function() {
+                  function get() {
                     return itemMap.get(this)(j);
-                  });
-                  freeze(get.prototype);
+                  }
                   defProp(BAL.prototype, j, {
-                    get: get,
+                    get: constFunc(get),
                     enumerable: true
                   });
                 })(i);
               }
               // Install native length getter.
               defProp(BAL.prototype, 'length', { get: lengthGetter });
-              // Freeze and cache the result
-              freeze(BAL);
-              freeze(BAL.prototype);
+              // TamperProof and cache the result
+              tamperProof(BAL);
+              tamperProof(BAL.prototype);
               BiggestArrayLike = BAL;
               maxLen = len;
             }
@@ -929,8 +999,15 @@
       }
     })();

-    global.cajaVM = {
-      log: function log(str) {
+    global.cajaVM = { // don't freeze here
+
+      /**
+       * This is about to be deprecated once we expose ses.logger.
+       *
+       * <p>In the meantime, privileged code should use ses.logger.log
+       * instead of cajaVM.log.
+       */
+      log: constFunc(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
@@ -939,28 +1016,33 @@
// console-log-and-others-are-callable-but-arent-typeof-function
           console.log(str);
         }
-      },
-
-      compileExpr: compileExpr,
-      compileModule: compileModule,
+      }),
+      tamperProof: constFunc(tamperProof),
+      constFunc: constFunc(constFunc),
+      // def: see below
+      is: constFunc(ses.is),
+
+      compileExpr: constFunc(compileExpr),
+      compileModule: constFunc(compileModule),
       // compileProgram: compileProgram, // Cannot be implemented in ES5.1.
-      eval: fakeEval,
-      Function: FakeFunction,
-
-      sharedImports: sharedImports,
-      makeImports: makeImports,
-      copyToImports: copyToImports,
-
-      makeArrayLike: makeArrayLike
+      eval: fakeEval,               // don't freeze here
+      Function: FakeFunction,       // don't freeze here,
+
+      sharedImports: sharedImports, // don't freeze here
+      makeImports: constFunc(makeImports),
+      copyToImports: constFunc(copyToImports),
+
+      makeArrayLike: constFunc(makeArrayLike)
     };
     var extensionsRecord = extensions();
     gopn(extensionsRecord).forEach(function (p) {
       defProp(cajaVM, p,
-          gopd(extensionsRecord, p));
+              gopd(extensionsRecord, p));
     });
+
     // Move this down here so it is not available during the call to
-    // extensions.
-    global.cajaVM.def = def;
+    // extensions().
+    global.cajaVM.def = constFunc(def);

   })();

@@ -977,70 +1059,41 @@
     });
     group.list.push(path);
   }
-
-  /**
-   * Read the current value of base[name], and freeze that property as
-   * a data property to ensure that all further reads of that same
-   * property from that base produce the same value.
-   *
-   * <p>The algorithms in [EMAIL PROTECTED] ses.startSES} traverse the graph of
-   * primordials multiple times. These algorithms rely on all these
-   * traversals seeing the same graph. By freezing these as data
-   * properties the first time they are read, we ensure that all
-   * traversals see the same graph.
-   *
-   * <p>The frozen property should preserve the enumerability of the
-   * original property.
-   */
-  function read(base, name) {
-    var desc = gopd(base, name);
-    if (!desc) { return undefined; }
-    if ('value' in desc && !desc.writable && !desc.configurable) {
-      return desc.value;
-    }
-
-    var result = base[name];
-    try {
-      defProp(base, name, {
-        value: result, writable: false, configurable: false
-      });
-    } catch (ex) {
-      reportProperty(ses.severities.NEW_SYMPTOM,
-                     'Cannot be neutered',
-                     '(a ' + typeof base + ').' + name);
-    }
-    return result;
-  }

   /**
    * Initialize accessible global variables and [EMAIL PROTECTED] 
sharedImports}.
    *
-   * For each of the whitelisted globals, we [EMAIL PROTECTED] read} its value,
-   * freeze that global property as a data property, and mirror that
-   * property with a frozen data property of the same name and value
-   * on [EMAIL PROTECTED] sharedImports}, but always non-enumerable. We make
-   * these non-enumerable since ES5.1 specifies that all these
-   * properties are non-enumerable on the global object.
+   * For each of the whitelisted globals, we read its value, freeze
+   * that global property as a data property, and mirror that property
+   * with a frozen data property of the same name and value on [EMAIL 
PROTECTED]
+   * sharedImports}, but always non-enumerable. We make these
+   * non-enumerable since ES5.1 specifies that all these properties
+   * are non-enumerable on the global object.
    */
   keys(whitelist).forEach(function(name) {
     var desc = gopd(global, name);
     if (desc) {
       var permit = whitelist[name];
       if (permit) {
-        var value = read(global, name);
-        defProp(sharedImports, name, {
-          value: value,
-          writable: true,
-          enumerable: false,
+        var newDesc = {
+          value: global[name],
+          writable: false,
           configurable: false
-        });
+        };
+        try {
+          defProp(global, name, newDesc);
+        } catch (err) {
+          reportProperty(ses.severities.NEW_SYMPTOM,
+ 'Global ' + name + ' cannot be made readonly: ' + err);
+        }
+        defProp(sharedImports, name, newDesc);
       }
     }
   });
   if (TAME_GLOBAL_EVAL) {
     defProp(sharedImports, 'eval', {
       value: cajaVM.eval,
-      writable: true,
+      writable: false,
       enumerable: false,
       configurable: false
     });
@@ -1051,10 +1104,6 @@
    * object to the permit object that describes how it should be
    * cleaned.
    *
-   * <p>To ensure that each subsequent traversal obtains the same
-   * values, these paths become paths of frozen data properties. See
-   * the doc-comment on [EMAIL PROTECTED] read}.
-   *
    * We initialize the whiteTable only so that [EMAIL PROTECTED] getPermit} can
    * process "*" and "skip" inheritance using the whitelist, by
    * walking actual superclass chains.
@@ -1072,7 +1121,7 @@
     whiteTable.set(value, permit);
     keys(permit).forEach(function(name) {
       if (permit[name] !== 'skip') {
-        var sub = read(value, name);
+        var sub = value[name];
         register(sub, permit[name]);
       }
     });
@@ -1178,7 +1227,7 @@
       });
     } catch (cantPoisonErr) {
       try {
-        // Perhaps it's writable non-configurable, it which case we
+        // Perhaps it's writable non-configurable, in which case we
         // should still be able to freeze it in a harmless state.
         var value = gopd(base, name).value;
         defProp(base, name, {
@@ -1233,17 +1282,21 @@
           reportProperty(ses.severities.SAFE,
                          'Skipped', path);
         } else {
-          var sub = read(value, name);
+          var sub = value[name];
           clean(sub, path);
         }
       } else {
         cleanProperty(value, name, path);
       }
     });
-    freeze(value);
   }
   clean(sharedImports, '');

+  /**
+   * This protection is now gathered here, so that a future version
+   * can skip it for non-defensive frames that must only be confined.
+   */
+  cajaVM.def(sharedImports);

   keys(propertyReports).sort().forEach(function(status) {
     var group = propertyReports[status];
=======================================
--- /trunk/src/com/google/caja/ses/useHTMLLogger.js     Fri Oct 21 18:37:00 2011
+++ /trunk/src/com/google/caja/ses/useHTMLLogger.js     Mon Feb 13 14:48:15 2012
@@ -46,6 +46,8 @@
 function useHTMLLogger(reportsElement, consoleElement) {
   "use strict";

+  var slice = [].slice;
+
   var maxElement = void 0;

   /**
@@ -84,10 +86,13 @@
     };
   }

-  var INFLATE = '[+] ';
-  var DEFLATE = '[-] ';
-  function deflate(toggler, inflatables) {
-    var icon = appendText(prependNew(toggler, 'tt'), INFLATE);
+  var INFLATE = '[+]';
+  var DEFLATE = '[-]';
+  function deflate(toggler, inflatables, opt_sep) {
+    var sep = opt_sep !== void 0 ? opt_sep : ' ';
+    var toggle = prependNew(toggler, 'tt');
+    var icon = appendText(toggle, INFLATE);
+    appendText(toggle, sep);
     forEach(inflatables, function(inflatable) {
       inflatable.style.display = 'none';
     });
@@ -106,12 +111,39 @@
     }, false);
     toggler.style.cursor = 'pointer';
   }
+
+  /** modeled on textAdder */
+  function makeLogFunc(parent, style) {
+    return function logFunc(var_args) {
+      var p = appendNew(parent, 'p');
+      var args = slice.call(arguments, 0);
+
+      // See debug.js
+      var getStack = ses.getStack;
+
+      for (var i = 0, len = args.length; i < len; i++) {
+        var span = appendNew(p, 'span');
+        appendText(span, '' + args[i]);
+
+        if (getStack) {
+          var stack = getStack(args[i]);
+          if (stack) {
+            var stackNode = appendNew(p, 'pre');
+            appendText(stackNode, stack);
+            deflate(span, [stackNode], '');
+          }
+        }
+      }
+      p.className = style;
+      return p;
+    };
+  }

   var logger = {
-    log:   textAdder(consoleElement, 'log'),
-    info:  textAdder(consoleElement, 'info'),
-    warn:  textAdder(consoleElement, 'warn'),
-    error: textAdder(consoleElement, 'error')
+    log:   makeLogFunc(consoleElement, 'log'),
+    info:  makeLogFunc(consoleElement, 'info'),
+    warn:  makeLogFunc(consoleElement, 'warn'),
+    error: makeLogFunc(consoleElement, 'error')
   };

   var TestIDPattern = /^(Sbp|S)?([\d\.]*)/;
=======================================
--- /trunk/src/com/google/caja/ses/whitelist.js Mon Dec 19 23:54:05 2011
+++ /trunk/src/com/google/caja/ses/whitelist.js Mon Feb 13 14:48:15 2012
@@ -21,6 +21,7 @@
  * <p>Assumes only ES3. Compatible with ES5, ES5-strict, or
  * anticipated ES6.
  *
+ * //provides ses.whitelist
  * @author Mark S. Miller,
  * @overrides ses, whitelistModule
  */
@@ -111,7 +112,10 @@
   ses.whitelist = {
     cajaVM: {                        // Caja support
       log: t,
+      tamperProof: t,
+      constFunc: t,
       def: t,
+      is: t,

       compileExpr: t,
       compileModule: t,              // experimental