// |reftest| skip -- not a test. // Any copyright is dedicated to the Public Domain. // http://creativecommons.org/licenses/publicdomain/ assertEq("defineProperty" in Object, true); assertEq(Object.defineProperty.length, 3); /* * Disable an assertion that is pathologically slow given the exhaustiveness of * these tests. */ if (typeof enableStackWalkingAssertion === "function") enableStackWalkingAssertion(false); if (!Object.prototype.toSource) { Object.defineProperty(Object.prototype, "toSource", { value: function toSource() { if (this instanceof RegExp) { var v = "new RegExp(" + uneval(this.source); var f = (this.multiline ? "m" : "") + (this.global ? "g" : "") + (this.ignoreCase ? "i" : ""); return v + (f ? ", '" + f + "'" : "") + ")"; } return JSON.stringify(this); }, enumerable: false, configurable: true, writable: true }); } if (!("uneval" in this)) { Object.defineProperty(this, "uneval", { value: function uneval(v) { if (v === null) return "null"; if (typeof v === "object") return v.toSource(); if (typeof v === "string") { v = JSON.stringify({v:v}); return v.substring(5, v.length - 1); } return "" + v; }, enumerable: false, configurable: true, writable: true }); } // reimplemented for the benefit of engines which don't have this helper function assertEq(v1, v2, m) { if (!SameValue(v1, v2)) { throw "assertion failed: " + "got " + uneval(v1) + ", expected " + uneval(v2) + (m ? ": " + m : ""); } } function SameValue(v1, v2) { if (v1 === 0 && v2 === 0) return 1 / v1 === 1 / v2; if (v1 !== v1 && v2 !== v2) return true; return v1 === v2; } function PropertyDescriptor(pd) { if (pd) this.update(pd); } PropertyDescriptor.prototype.update = function update(pd) { if ("get" in pd) this.get = pd.get; if ("set" in pd) this.set = pd.set; if ("configurable" in pd) this.configurable = pd.configurable; if ("writable" in pd) this.writable = pd.writable; if ("enumerable" in pd) this.enumerable = pd.enumerable; if ("value" in pd) this.value = pd.value; }; PropertyDescriptor.prototype.convertToDataDescriptor = function convertToDataDescriptor() { delete this.get; delete this.set; this.writable = false; this.value = undefined; }; PropertyDescriptor.prototype.convertToAccessorDescriptor = function convertToAccessorDescriptor() { delete this.writable; delete this.value; this.get = undefined; this.set = undefined; }; function compareDescriptors(d1, d2) { if (d1 === undefined) { assertEq(d2, undefined, "non-descriptors"); return; } if (d2 === undefined) { assertEq(true, false, "descriptor-equality mismatch: " + uneval(d1) + ", " + uneval(d2)); return; } var props = ["value", "get", "set", "enumerable", "configurable", "writable"]; for (var i = 0, sz = props.length; i < sz; i++) { var p = props[i]; assertEq(p in d1, p in d2, p + " different in d1/d2"); if (p in d1) assertEq(d1[p], d2[p], p); } } function examine(desc, field, allowDefault) { if (field in desc) return desc[field]; assertEq(allowDefault, true, "reimplementation error"); switch (field) { case "value": case "get": case "set": return undefined; case "writable": case "enumerable": case "configurable": return false; default: assertEq(true, false, "bad field name: " + field); } } function IsAccessorDescriptor(desc) { if (!desc) return false; if (!("get" in desc) && !("set" in desc)) return false; return true; } function IsDataDescriptor(desc) { if (!desc) return false; if (!("value" in desc) && !("writable" in desc)) return false; return true; } function IsGenericDescriptor(desc) { if (!desc) return false; if (!IsAccessorDescriptor(desc) && !IsDataDescriptor(desc)) return true; return false; } function CustomObject() { this.properties = {}; this.extensible = true; } CustomObject.prototype = { _reject: function _reject(throwing, msg) { if (throwing) throw new TypeError(msg + "; rejected!"); return false; }, defineOwnProperty: function defineOwnProperty(propname, desc, throwing) { assertEq(typeof propname, "string", "non-string propname"); // Step 1. var current = this.properties[propname]; // Step 2. var extensible = this.extensible; // Step 3. if (current === undefined && !extensible) return this._reject(throwing, "object not extensible"); // Step 4. if (current === undefined && extensible) { var p; // Step 4(a). if (IsGenericDescriptor(desc) || IsDataDescriptor(desc)) { p = new PropertyDescriptor(); p.value = examine(desc, "value", true); p.writable = examine(desc, "writable", true); p.enumerable = examine(desc, "enumerable", true); p.configurable = examine(desc, "configurable", true); } // Step 4(b). else { p = new PropertyDescriptor(); p.get = examine(desc, "get", true); p.set = examine(desc, "set", true); p.enumerable = examine(desc, "enumerable", true); p.configurable = examine(desc, "configurable", true); } this.properties[propname] = p; // Step 4(c). return true; } // Step 5. if (!("value" in desc) && !("get" in desc) && !("set" in desc) && !("writable" in desc) && !("enumerable" in desc) && !("configurable" in desc)) { return; } // Step 6. do { if ("value" in desc) { if (!("value" in current) || !SameValue(desc.value, current.value)) break; } if ("get" in desc) { if (!("get" in current) || !SameValue(desc.get, current.get)) break; } if ("set" in desc) { if (!("set" in current) || !SameValue(desc.set, current.set)) break; } if ("writable" in desc) { if (!("writable" in current) || !SameValue(desc.writable, current.writable)) { break; } } if ("enumerable" in desc) { if (!("enumerable" in current) || !SameValue(desc.enumerable, current.enumerable)) { break; } } if ("configurable" in desc) { if (!("configurable" in current) || !SameValue(desc.configurable, current.configurable)) { break; } } // all fields in desc also in current, with the same values return true; } while (false); // Step 7. if (!examine(current, "configurable")) { if ("configurable" in desc && examine(desc, "configurable")) return this._reject(throwing, "can't make configurable again"); if ("enumerable" in desc && examine(current, "enumerable") !== examine(desc, "enumerable")) { return this._reject(throwing, "can't change enumerability"); } } // Step 8. if (IsGenericDescriptor(desc)) { // do nothing } // Step 9. else if (IsDataDescriptor(current) !== IsDataDescriptor(desc)) { // Step 9(a). if (!examine(current, "configurable")) return this._reject(throwing, "can't change unconfigurable descriptor's type"); // Step 9(b). if (IsDataDescriptor(current)) current.convertToAccessorDescriptor(); // Step 9(c). else current.convertToDataDescriptor(); } // Step 10. else if (IsDataDescriptor(current) && IsDataDescriptor(desc)) { // Step 10(a) if (!examine(current, "configurable")) { // Step 10(a).i. if (!examine(current, "writable") && "writable" in desc && examine(desc, "writable")) { return this._reject(throwing, "can't make data property writable again"); } // Step 10(a).ii. if (!examine(current, "writable")) { if ("value" in desc && !SameValue(examine(desc, "value"), examine(current, "value"))) { return this._reject(throwing, "can't change value if not writable"); } } } // Step 10(b). else { assertEq(examine(current, "configurable"), true, "spec bug step 10(b)"); } } // Step 11. else { assertEq(IsAccessorDescriptor(current) && IsAccessorDescriptor(desc), true, "spec bug"); // Step 11(a). if (!examine(current, "configurable")) { // Step 11(a).i. if ("set" in desc && !SameValue(examine(desc, "set"), examine(current, "set"))) { return this._reject(throwing, "can't change setter if not configurable"); } // Step 11(a).ii. if ("get" in desc && !SameValue(examine(desc, "get"), examine(current, "get"))) { return this._reject(throwing, "can't change getter if not configurable"); } } } // Step 12. current.update(desc); // Step 13. return true; } }; function IsCallable(v) { return typeof v === "undefined" || typeof v === "function"; } var NativeTest = { newObject: function newObject() { return {}; }, defineProperty: function defineProperty(obj, propname, propdesc) { Object.defineProperty(obj, propname, propdesc); }, getDescriptor: function getDescriptor(obj, propname) { return Object.getOwnPropertyDescriptor(obj, propname); } }; var ReimplTest = { newObject: function newObject() { return new CustomObject(); }, defineProperty: function defineProperty(obj, propname, propdesc) { assertEq(obj instanceof CustomObject, true, "obj not instanceof CustomObject"); if ("get" in propdesc || "set" in propdesc) { if ("value" in propdesc || "writable" in propdesc) throw new TypeError("get/set and value/writable"); if (!IsCallable(propdesc.get)) throw new TypeError("get defined, uncallable"); if (!IsCallable(propdesc.set)) throw new TypeError("set defined, uncallable"); } return obj.defineOwnProperty(propname, propdesc, true); }, getDescriptor: function getDescriptor(obj, propname) { if (!(propname in obj.properties)) return undefined; return new PropertyDescriptor(obj.properties[propname]); } }; var JSVAL_INT_MAX = Math.pow(2, 30) - 1; var JSVAL_INT_MIN = -Math.pow(2, 30); function isValidDescriptor(propdesc) { if ("get" in propdesc || "set" in propdesc) { if ("value" in propdesc || "writable" in propdesc) return false; // We permit null here simply because this test's author believes the // implementation may sometime be susceptible to making mistakes in this // regard and would prefer to be cautious. if (propdesc.get !== null && propdesc.get !== undefined && !IsCallable(propdesc.get)) return false; if (propdesc.set !== null && propdesc.set !== undefined && !IsCallable(propdesc.set)) return false; } return true; } var OMIT = {}; var VALUES = [-Infinity, JSVAL_INT_MIN, -0, +0, 1.5, JSVAL_INT_MAX, Infinity, NaN, "foo", "bar", null, undefined, true, false, {}, /a/, OMIT]; var GETS = [undefined, function get1() { return 1; }, function get2() { return 2; }, null, 5, OMIT]; var SETS = [undefined, function set1() { return 1; }, function set2() { return 2; }, null, 5, OMIT]; var ENUMERABLES = [true, false, OMIT]; var CONFIGURABLES = [true, false, OMIT]; var WRITABLES = [true, false, OMIT]; function mapTestDescriptors(filter) { var descs = []; var desc = {}; function put(field, value) { if (value !== OMIT) desc[field] = value; } VALUES.forEach(function(value) { GETS.forEach(function(get) { SETS.forEach(function(set) { ENUMERABLES.forEach(function(enumerable) { CONFIGURABLES.forEach(function(configurable) { WRITABLES.forEach(function(writable) { desc = {}; put("value", value); put("get", get); put("set", set); put("enumerable", enumerable); put("configurable", configurable); put("writable", writable); if (filter(desc)) descs.push(desc); }); }); }); }); }); }); return descs; } var ALL_DESCRIPTORS = mapTestDescriptors(function(d) { return true; }); var VALID_DESCRIPTORS = mapTestDescriptors(isValidDescriptor); function TestRunner() { this._logLines = []; } TestRunner.prototype = { // MAIN METHODS runFunctionLengthTests: function runFunctionLengthTests() { var self = this; function functionLengthTests() { self._fullFunctionLengthTests(() => Function("one", "/* body */"), 1); self._fullFunctionLengthTests(() => function(one, two, three=null) { }, 2); self._fullFunctionLengthTests(() => (one, two, ...etc) => 0, 2); self._fullFunctionLengthTests(() => ({method(){}}.method), 0); self._fullFunctionLengthTests(() => Object.getOwnPropertyDescriptor({set x(v){}}, "x").set, 1); } this._runTestSet(functionLengthTests, "Function length tests completed!"); }, runNotPresentTests: function runNotPresentTests() { var self = this; function notPresentTests() { print("Running not-present tests now..."); for (var i = 0, sz = ALL_DESCRIPTORS.length; i < sz; i++) self._runSingleNotPresentTest(ALL_DESCRIPTORS[i]); }; this._runTestSet(notPresentTests, "Not-present length tests completed!"); }, runPropertyPresentTestsFraction: function runPropertyPresentTestsFraction(part, parts) { var self = this; function propertyPresentTests() { print("Running already-present tests now..."); var total = VALID_DESCRIPTORS.length; var start = Math.floor((part - 1) / parts * total); var end = Math.floor(part / parts * total); for (var i = start; i < end; i++) { var old = VALID_DESCRIPTORS[i]; print("Starting test with old descriptor " + old.toSource() + "..."); for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++) self._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j], []); } } this._runTestSet(propertyPresentTests, "Property-present fraction " + part + " of " + parts + " completed!"); }, runNonTerminalPropertyPresentTestsFraction: function runNonTerminalPropertyPresentTestsFraction(part, parts) { var self = this; /* * A plain old property to define on the object before redefining the * originally-added property, to test redefinition of a property that's * not also lastProperty. NB: we could loop over every possible * descriptor here if we wanted, even try adding more than one, but we'd * hit cubic complexity and worse, and SpiderMonkey only distinguishes by * the mere presence of the middle property, not its precise details. */ var middleDefines = [{ property: "middle", descriptor: { value: 17, writable: true, configurable: true, enumerable: true } }]; function nonTerminalPropertyPresentTests() { print("Running non-terminal already-present tests now..."); var total = VALID_DESCRIPTORS.length; var start = Math.floor((part - 1) / parts * total); var end = Math.floor(part / parts * total); for (var i = start; i < end; i++) { var old = VALID_DESCRIPTORS[i]; print("Starting test with old descriptor " + old.toSource() + "..."); for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++) { self._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j], middleDefines); } } } this._runTestSet(nonTerminalPropertyPresentTests, "Non-terminal property-present fraction " + part + " of " + parts + " completed!"); }, runDictionaryPropertyPresentTestsFraction: function runDictionaryPropertyPresentTestsFraction(part, parts) { var self = this; /* * Add and readd properties such that the scope for the object is in * dictionary mode. */ var middleDefines = [ { property: "mid1", descriptor: { value: 17, writable: true, configurable: true, enumerable: true } }, { property: "mid2", descriptor: { value: 17, writable: true, configurable: true, enumerable: true } }, { property: "mid1", descriptor: { get: function g() { }, set: function s(v){}, configurable: false, enumerable: true } }, ]; function dictionaryPropertyPresentTests() { print("Running dictionary already-present tests now..."); var total = VALID_DESCRIPTORS.length; var start = Math.floor((part - 1) / parts * total); var end = Math.floor(part / parts * total); for (var i = start; i < end; i++) { var old = VALID_DESCRIPTORS[i]; print("Starting test with old descriptor " + old.toSource() + "..."); for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++) { self._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j], middleDefines); } } } this._runTestSet(dictionaryPropertyPresentTests, "Dictionary property-present fraction " + part + " of " + parts + " completed!"); }, // HELPERS runPropertyPresentTests: function runPropertyPresentTests() { print("Running already-present tests now..."); for (var i = 0, sz = VALID_DESCRIPTORS.length; i < sz; i++) { var old = VALID_DESCRIPTORS[i]; print("Starting test with old descriptor " + old.toSource() + "..."); for (var j = 0, sz2 = VALID_DESCRIPTORS.length; j < sz2; j++) this._runSinglePropertyPresentTest(old, VALID_DESCRIPTORS[j], []); } }, _runTestSet: function _runTestSet(fun, completeMessage) { try { fun(); print(completeMessage); } catch (e) { print("ERROR, EXITING (line " + (e.lineNumber || -1) + "): " + e); throw e; } finally { this._reportAllErrors(); } }, _reportAllErrors: function _reportAllErrors() { var errorCount = this._logLines.length; print("Full accumulated number of errors: " + errorCount); if (errorCount > 0) throw errorCount + " errors detected, FAIL"; }, _fullFunctionLengthTests: function _fullFunctionLengthTests(funFactory, len) { print("Running Function.length (" + funFactory + ") tests now..."); for (var i = 0, sz = VALID_DESCRIPTORS.length; i < sz; i++) { var desc = VALID_DESCRIPTORS[i]; this._runSingleFunctionLengthTest(funFactory(), len, desc); } }, _log: function _log(v) { var m = "" + v; print(m); this._logLines.push(m); }, _runSingleNotPresentTest: function _runSingleNotPresentTest(desc) { var nativeObj = NativeTest.newObject(); var reimplObj = ReimplTest.newObject(); try { NativeTest.defineProperty(nativeObj, "foo", desc); } catch (e) { try { ReimplTest.defineProperty(reimplObj, "foo", desc); } catch (e2) { if (e.constructor !== e2.constructor) { this._log("Difference when comparing native/reimplementation " + "behavior for new descriptor " + desc.toSource() + ", native threw " + e + ", reimpl threw " + e2); } return; } this._log("Difference when comparing native/reimplementation " + "behavior for new descriptor " + desc.toSource() + ", error " + e); return; } try { ReimplTest.defineProperty(reimplObj, "foo", desc); } catch (e) { this._log("Reimpl threw defining new descriptor " + desc.toSource() + ", error: " + e); return; } var nativeDesc = NativeTest.getDescriptor(nativeObj, "foo"); var reimplDesc = ReimplTest.getDescriptor(reimplObj, "foo"); try { compareDescriptors(nativeDesc, reimplDesc); } catch (e) { this._log("Difference comparing returned descriptors for new " + "property defined with descriptor " + desc.toSource() + "; error: " + e); return; } }, _runSinglePropertyPresentTest: function _runSinglePropertyPresentTest(old, add, middleDefines) { var nativeObj = NativeTest.newObject(); var reimplObj = ReimplTest.newObject(); try { NativeTest.defineProperty(nativeObj, "foo", old); } catch (e) { if (!SameValue(NativeTest.getDescriptor(nativeObj, "foo"), undefined)) { this._log("defining bad property descriptor: " + old.toSource()); return; } try { ReimplTest.defineProperty(reimplObj, "foo", old); } catch (e2) { if (!SameValue(ReimplTest.getDescriptor(reimplObj, "foo"), undefined)) { this._log("defining bad property descriptor: " + old.toSource() + "; reimplObj: " + uneval(reimplObj)); } if (e.constructor !== e2.constructor) { this._log("Different errors defining bad property descriptor: " + old.toSource() + "; native threw " + e + ", reimpl " + "threw " + e2); } return; } this._log("Difference defining a property with descriptor " + old.toSource() + ", error " + e); return; } try { ReimplTest.defineProperty(reimplObj, "foo", old); } catch (e) { this._log("Difference when comparing native/reimplementation " + "behavior when adding descriptor " + add.toSource() + ", error: " + e); return; } // Now add (or even readd) however many properties were specified between // the original property to add and the new one, to test redefining // non-last-properties and properties in scopes in dictionary mode. for (var i = 0, sz = middleDefines.length; i < sz; i++) { var middle = middleDefines[i]; var prop = middle.property; var desc = middle.descriptor; try { NativeTest.defineProperty(nativeObj, prop, desc); ReimplTest.defineProperty(reimplObj, prop, desc); } catch (e) { this._log("failure defining middle descriptor: " + desc.toSource() + ", error " + e); return; } // Sanity check var nativeDesc = NativeTest.getDescriptor(nativeObj, prop); var reimplDesc = ReimplTest.getDescriptor(reimplObj, prop); compareDescriptors(nativeDesc, reimplDesc); compareDescriptors(nativeDesc, desc); } try { NativeTest.defineProperty(nativeObj, "foo", add); } catch (e) { try { ReimplTest.defineProperty(reimplObj, "foo", add); } catch (e2) { if (e.constructor !== e2.constructor) { this._log("Difference when comparing native/reimplementation " + "behavior for descriptor " + add.toSource() + " overwriting descriptor " + old.toSource() + "; " + "native threw " + e + ", reimpl threw " + e2); } return; } this._log("Difference when comparing native/reimplementation " + "behavior for added descriptor " + add.toSource() + ", " + "initial was " + old.toSource() + "; error: " + e); return; } try { ReimplTest.defineProperty(reimplObj, "foo", add); } catch (e) { this._log("Difference when comparing native/reimplementation " + "behavior for readded descriptor " + add.toSource() + ", " + "initial was " + old.toSource() + "; native readd didn't " + "throw, reimpl add did, error: " + e); return; } var nativeDesc = NativeTest.getDescriptor(nativeObj, "foo"); var reimplDesc = ReimplTest.getDescriptor(reimplObj, "foo"); try { compareDescriptors(nativeDesc, reimplDesc); } catch (e) { this._log("Difference comparing returned descriptors for readded " + "property defined with descriptor " + add.toSource() + "; " + "initial was " + old.toSource() + "; error: " + e); return; } }, _runSingleFunctionLengthTest: function _runSingleFunctionLengthTest(fun, len, desc) { var nativeObj = fun; var reimplObj = ReimplTest.newObject(); ReimplTest.defineProperty(reimplObj, "length", { value: len, enumerable: false, configurable: true, writable: false }); try { NativeTest.defineProperty(nativeObj, "length", desc); } catch (e) { try { ReimplTest.defineProperty(reimplObj, "length", desc); } catch (e2) { if (e.constructor !== e2.constructor) { this._log("Difference when comparing native/reimplementation " + "behavior defining fun.length with " + desc.toSource() + "; native threw " + e + ", reimpl threw " + e2); } return; } this._log("Difference when comparing Function.length native/reimpl " + "behavior for descriptor " + desc.toSource() + ", native impl threw error " + e); return; } try { ReimplTest.defineProperty(reimplObj, "length", desc); } catch (e) { this._log("Difference defining new Function.length descriptor: impl " + "succeeded, reimpl threw for descriptor " + desc.toSource() + ", error: " + e); return; } var nativeDesc = NativeTest.getDescriptor(nativeObj, "length"); var reimplDesc = ReimplTest.getDescriptor(reimplObj, "length"); try { compareDescriptors(nativeDesc, reimplDesc); } catch (e) { this._log("Difference comparing returned descriptors for " + "Function.length with descriptor " + desc.toSource() + "; error: " + e); return; } } }; function runDictionaryPropertyPresentTestsFraction(PART, PARTS) { var testfile = '15.2.3.6-dictionary-redefinition-' + PART + '-of-' + PARTS + '.js'; var BUGNUMBER = 560566; var summary = 'ES5 Object.defineProperty(O, P, Attributes): dictionary redefinition ' + PART + ' of ' + PARTS; print(BUGNUMBER + ": " + summary); try { new TestRunner().runDictionaryPropertyPresentTestsFraction(PART, PARTS); } catch (e) { throw "Error thrown during testing: " + e + " at line " + e.lineNumber + "\n" + (e.stack ? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n" : ""); } if (typeof reportCompare === "function") reportCompare(true, true); print("Tests complete!"); } function runNonTerminalPropertyPresentTestsFraction(PART, PARTS) { var BUGNUMBER = 560566; var summary = 'ES5 Object.defineProperty(O, P, Attributes): middle redefinition ' + PART + ' of ' + PARTS; print(BUGNUMBER + ": " + summary); /************** * BEGIN TEST * **************/ try { new TestRunner().runNonTerminalPropertyPresentTestsFraction(PART, PARTS); } catch (e) { throw "Error thrown during testing: " + e + " at line " + e.lineNumber + "\n" + (e.stack ? "Stack: " + e.stack.split("\n").slice(2).join("\n") + "\n" : ""); } /******************************************************************************/ if (typeof reportCompare === "function") reportCompare(true, true); print("Tests complete!"); }