Revision: 4295
Author: metaweta
Date: Thu Oct 7 11:11:50 2010
Log: Defer creation of per-instance function properties
http://codereview.appspot.com/2374041
Installs getters on Function.prototype for length, prototype, and
name that whitelist those properties on the instance on first access.
R=jasvir
http://code.google.com/p/google-caja/source/detail?r=4295
Modified:
/trunk/src/com/google/caja/es53.js
/trunk/tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
=======================================
--- /trunk/src/com/google/caja/es53.js Thu Sep 30 16:14:28 2010
+++ /trunk/src/com/google/caja/es53.js Thu Oct 7 11:11:50 2010
@@ -36,7 +36,7 @@
* @overrides escape, JSON
*/
-var ___, es53, safeJSON, AS_TAMED___, AS_FERAL___;
+var ___, cajaVM, safeJSON, AS_TAMED___, AS_FERAL___;
(function () {
// For computing the [[Class]] internal property
@@ -167,6 +167,14 @@
* the inherited {...@code AS_TAMED___}
* method.
* {...@code ___.untame(obj)} is similar, but goes the other
way.
+ *
+ * Since creating function instances is a common pattern and reading
+ * properties of a function instance is not, we defer whitelisting the
+ * prototype, length, and name properties.
+ *
+ * {...@code f.name___} holds the value of the deferred
name
+ * property of a function instance
until
+ * it's installed.
*/
// We have to define it even on Firefox, since the built-in slice doesn't
@@ -174,7 +182,7 @@
Array.slice = markFunc(function (dis, startIndex) { // , endIndex
dis = ToObject(dis);
if (arguments.length > 2) {
- var edIndex = aguments[2];
+ var edIndex = arguments[2];
return slice.call(dis, startIndex, endIndex);
} else {
return slice.call(dis, startIndex);
@@ -727,6 +735,78 @@
obj[name + '_w___'] = false;
obj[name + '_m___'] = obj;
}
+
+ /**
+ * We defer the creation of these properties until they're asked for.
+ */
+ function installFunctionInstanceProps(f) {
+ var name = f.name___;
+ delete f.name___;
+ // Object.prototype.DefineOwnProperty___ may not be defined yet
+ f.prototype_v___ = f;
+ f.prototype_w___ = f;
+ f.prototype_gw___ = f;
+ f.prototype_c___ = false;
+ f.prototype_e___ = false;
+ f.prototype_g___ = void 0;
+ f.prototype_s___ = void 0;
+ f.prototype_m___ = false;
+ f.length_v___ = f;
+ f.length_w___ = false;
+ f.length_gw___ = false;
+ f.length_c___ = false;
+ f.length_e___ = false;
+ f.length_g___ = void 0;
+ f.length_s___ = void 0;
+ f.length_m___ = false;
+ // Rhino prohibits setting the name property of function instances,
+ // so we install a getter instead.
+ f.name_v___ = false;
+ f.name_w___ = false;
+ f.name_gw___ = false;
+ f.name_c___ = false;
+ f.name_e___ = false;
+ f.name_g___ = markFunc(function() {return name;});
+ f.name_s___ = void 0;
+ f.name_m___ = false;
+ }
+
+ function deferredV(name) {
+ delete this.v___;
+ delete this.w___;
+ delete this.c___;
+ delete this.DefineOwnProperty___;
+ installFunctionInstanceProps(this);
+ // Object.prototype.v___ may not be defined yet
+ return this.v___ ? this.v___(name) : void 0;
+ }
+
+ function deferredW(name, val) {
+ delete this.v___;
+ delete this.w___;
+ delete this.c___;
+ delete this.DefineOwnProperty___;
+ installFunctionInstanceProps(this);
+ return this.w___(name, val);
+ }
+
+ function deferredC(name) {
+ delete this.v___;
+ delete this.w___;
+ delete this.c___;
+ delete this.DefineOwnProperty___;
+ installFunctionInstanceProps(this);
+ return this.c___(name);
+ }
+
+ function deferredDOP(name, desc) {
+ delete this.v___;
+ delete this.w___;
+ delete this.c___;
+ delete this.DefineOwnProperty___;
+ installFunctionInstanceProps(this);
+ return this.DefineOwnProperty___(name, desc);
+ }
/**
* For taming a simple function or a safe exophoric function (only reads
@@ -742,30 +822,11 @@
}
fn.f___ = fn.apply;
fn.new___ = fn;
- if (fn.DefineOwnProperty___) {
- fn.DefineOwnProperty___('prototype', {
- value: fn.prototype,
- writable: true,
- enumerable: false,
- configurable: false
- });
- fn.DefineOwnProperty___('length', {
- value: fn.length,
- writable: false,
- enumerable: false,
- configurable: false
- });
- if (name) {
- name = '' + name;
- // name is not writable on Rhino
- fn.DefineOwnProperty___('name', {
- get: markFunc(function () { return name; }),
- set: void 0,
- enumerable: false,
- configurable: false
- });
- }
- }
+ fn.name___ = '' + name;
+ fn.v___ = deferredV;
+ fn.w___ = deferredW;
+ fn.c___ = deferredC;
+ fn.DefineOwnProperty___ = deferredDOP;
return fn;
}
@@ -2556,22 +2617,18 @@
return this.f___(___.USELESS, slice.call(arguments, 0));
};
Function.prototype.new___ = callFault;
- Function.prototype.DefineOwnProperty___(
- 'arguments',
- {
- enumerable: false,
- configurable: false,
- get: poisonFuncArgs,
- set: poisonFuncArgs
- });
- Function.prototype.DefineOwnProperty___(
- 'caller',
- {
- enumerable: false,
- configurable: false,
- get: poisonFuncCaller,
- set: poisonFuncCaller
- });
+ Function.prototype.DefineOwnProperty___('arguments', {
+ enumerable: false,
+ configurable: false,
+ get: poisonFuncArgs,
+ set: poisonFuncArgs
+ });
+ Function.prototype.DefineOwnProperty___('caller', {
+ enumerable: false,
+ configurable: false,
+ get: poisonFuncCaller,
+ set: poisonFuncCaller
+ });
// 11.2.4
var poisonArgsCallee = makePoisonPill('arguments.callee');
@@ -2876,6 +2933,11 @@
// fail like any other attempt to change the properties.
// Tamed setters should check before changing a property.
if (obj.z___ === obj) { return obj; }
+ // Allow function instances to install their instance properties
+ // before freezing them.
+ if (obj.v___ === deferredV) {
+ obj.v___('length');
+ }
obj.ne___ = obj;
for (var i in obj) {
if (!guestHasOwnProperty(obj,i)) { continue; }
=======================================
--- /trunk/tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
Wed Sep 22 12:10:53 2010
+++ /trunk/tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
Thu Oct 7 11:11:50 2010
@@ -404,6 +404,28 @@
"pt.x = 8;" +
"Object.freeze(pt);" +
"assertThrows(function(){pt.y = 9;});");
+ // Check that deferred creation of prototype property doesn't make it
+ // writable.
+ rewriteAndExecute(
+ "function f(){}" +
+ "Object.freeze(f);" +
+ "assertThrows(function() { f.prototype = {}; });");
+ }
+
+ /**
+ * Tests that the {...@code prototype}, {...@code name}, and {...@code
length}
+ * properties of function instances are set properly.
+ */
+ public final void testFunctionInstance() throws Exception {
+ rewriteAndExecute(
+ "assertTrue(!!((function(){}).prototype));");
+ rewriteAndExecute(
+ "assertEquals((function(a,b,c){}).length, 3);");
+ rewriteAndExecute(
+ "assertEquals((function x(a,b,c){}).name, 'x');");
+ // Check frozen functions created early in es53.js
+ rewriteAndExecute(
+ "assertTrue(!!(cajaVM.USELESS.toString.prototype));");
}
/**