Revision: 3438
Author: [email protected]
Date: Wed Dec 9 05:56:58 2009
Log: Add Object.create from ECMAScript5. Supports value, writable,
enumerable, get
and set. Doesn't support configurable yet. See
http://code.google.com/p/v8/issues/detail?id=460
Review URL: http://codereview.chromium.org/463040
http://code.google.com/p/v8/source/detail?r=3438
Added:
/branches/bleeding_edge/test/mjsunit/object-create.js
Modified:
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/v8natives.js
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/object-create.js Wed Dec 9
05:56:58 2009
@@ -0,0 +1,250 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Test ES5 sections 15.2.3.5 Object.create.
+// We do not support nonconfigurable properties on objects so that is not
+// tested. We do test getters, setters, writable, enumerable and value.
+
+// Check that no exceptions are thrown.
+Object.create(null);
+Object.create(null, undefined);
+
+// Check that the right exception is thrown.
+try {
+ Object.create(4);
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Object or null/.test(e));
+}
+
+try {
+ Object.create("foo");
+ print(2);
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Object or null/.test(e));
+}
+
+var ctr = 0;
+var ctr2 = 0;
+var ctr3 = 0;
+var ctr4 = 0;
+var ctr5 = 0;
+var ctr6 = 1000;
+
+var protoFoo = { foo: function() { ctr++; }};
+var fooValue = { foo: { writable: true, value: function() { ctr2++; }}};
+var fooGetter = { foo: { get: function() { return ctr3++; }}};
+var fooSetter = { foo: { set: function() { return ctr4++; }}};
+var fooAmbiguous = { foo: { get: function() { return ctr3++; },
+ value: 3 }};
+
+function valueGet() { ctr5++; return 3 };
+function getterGet() { ctr5++; return function() { return ctr6++; }; };
+
+// Simple object with prototype, no properties added.
+Object.create(protoFoo).foo();
+assertEquals(1, ctr);
+
+// Simple object with object with prototype, no properties added.
+Object.create(Object.create(protoFoo)).foo();
+assertEquals(2, ctr);
+
+// Add a property foo that returns a function.
+var v = Object.create(protoFoo, fooValue);
+v.foo();
+assertEquals(2, ctr);
+assertEquals(1, ctr2);
+
+// Ensure the property is writable.
+v.foo = 42;
+assertEquals(42, v.foo);
+assertEquals(2, ctr);
+assertEquals(1, ctr2);
+
+// Ensure by default properties are not writable.
+v = Object.create(null, { foo: {value: 103}});
+assertEquals(103, v.foo);
+v.foo = 42;
+assertEquals(103, v.foo);
+
+// Add a getter foo that returns a counter value.
+assertEquals(0, Object.create(protoFoo, fooGetter).foo);
+assertEquals(2, ctr);
+assertEquals(1, ctr2);
+assertEquals(1, ctr3);
+
+// Add a setter foo that runs a function.
+assertEquals(1, Object.create(protoFoo, fooSetter).foo = 1);
+assertEquals(2, ctr);
+assertEquals(1, ctr2);
+assertEquals(1, ctr3);
+assertEquals(1, ctr4);
+
+// Make sure that trying to add both a value and a getter
+// will result in an exception.
+try {
+ Object.create(protoFoo, fooAmbiguous);
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Invalid property/.test(e));
+}
+assertEquals(2, ctr);
+assertEquals(1, ctr2);
+assertEquals(1, ctr3);
+assertEquals(1, ctr4);
+
+var ctr7 = 0;
+
+var metaProps = {
+ enumerable: { get: function() {
+ assertEquals(0, ctr7++);
+ return true;
+ }},
+ configurable: { get: function() {
+ assertEquals(1, ctr7++);
+ return true;
+ }},
+ value: { get: function() {
+ assertEquals(2, ctr7++);
+ return 4;
+ }},
+ writable: { get: function() {
+ assertEquals(3, ctr7++);
+ return true;
+ }},
+ get: { get: function() {
+ assertEquals(4, ctr7++);
+ return function() { };
+ }},
+ set: { get: function() {
+ assertEquals(5, ctr7++);
+ return function() { };
+ }}
+};
+
+
+// Instead of a plain props object, let's use getters to return its
properties.
+var magicValueProps = { foo: Object.create(null, { value: { get: valueGet
}})};
+var magicGetterProps = { foo: Object.create(null, { get: { get: getterGet
}})};
+var magicAmbiguousProps = { foo: Object.create(null, metaProps) };
+
+assertEquals(3, Object.create(null, magicValueProps).foo);
+assertEquals(1, ctr5);
+
+assertEquals(1000, Object.create(null, magicGetterProps).foo);
+assertEquals(2, ctr5);
+
+// See if we do the steps in ToPropertyDescriptor in the right order.
+// We shouldn't throw the exception for an ambiguous properties object
+// before we got all the values out.
+try {
+ Object.create(null, magicAmbiguousProps);
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Invalid property/.test(e));
+ assertEquals(6, ctr7);
+}
+
+var magicWritableProps = {
+ foo: Object.create(null, { value: { value: 4 },
+ writable: { get: function() {
+ ctr6++;
+ return false;
+ }}})};
+
+var fooNotWritable = Object.create(null, magicWritableProps)
+assertEquals(1002, ctr6);
+assertEquals(4, fooNotWritable.foo);
+fooNotWritable.foo = 5;
+assertEquals(4, fooNotWritable.foo);
+
+
+// Test enumerable flag.
+
+var fooNotEnumerable =
+ Object.create({fizz: 14}, {foo: {value: 3, enumerable: false},
+ bar: {value: 4, enumerable: true},
+ baz: {value: 5}});
+var sum = 0;
+for (x in fooNotEnumerable) {
+ assertTrue(x === 'bar' || x === 'fizz');
+ sum += fooNotEnumerable[x];
+}
+assertEquals(18, sum);
+
+
+try {
+ Object.create(null, {foo: { get: 0 }});
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Getter must be a function/.test(e));
+}
+
+try {
+ Object.create(null, {foo: { set: 0 }});
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Setter must be a function/.test(e));
+}
+
+try {
+ Object.create(null, {foo: { set: 0, get: 0 }});
+ assertTrue(false);
+} catch (e) {
+ assertTrue(/Getter must be a function/.test(e));
+}
+
+
+// Ensure that only enumerable own properties on the descriptor are used.
+var tricky = Object.create(
+ { foo: { value: 1, enumerable: true }},
+ { bar: { value: { value: 2, enumerable: true }, enumerable: false },
+ baz: { value: { value: 4, enumerable: false }, enumerable: true },
+ fizz: { value: { value: 8, enumerable: false }, enumerable: false },
+ buzz: { value: { value: 16, enumerable: true }, enumerable: true }});
+
+assertEquals(1, tricky.foo.value);
+assertEquals(2, tricky.bar.value);
+assertEquals(4, tricky.baz.value);
+assertEquals(8, tricky.fizz.value);
+assertEquals(16, tricky.buzz.value);
+
+var sonOfTricky = Object.create(null, tricky);
+
+assertFalse("foo" in sonOfTricky);
+assertFalse("bar" in sonOfTricky);
+assertTrue("baz" in sonOfTricky);
+assertFalse("fizz" in sonOfTricky);
+assertTrue("buzz" in sonOfTricky);
+
+var sum = 0;
+for (x in sonOfTricky) {
+ assertTrue(x === 'buzz');
+ sum += sonOfTricky[x];
+}
+assertEquals(16, sum);
=======================================
--- /branches/bleeding_edge/src/messages.js Tue Dec 1 06:36:45 2009
+++ /branches/bleeding_edge/src/messages.js Wed Dec 9 05:56:58 2009
@@ -157,6 +157,11 @@
instanceof_nonobject_proto: "Function has non-object
prototype '%0' in instanceof check",
null_to_object: "Cannot convert null to object",
reduce_no_initial: "Reduce of empty array with no initial
value",
+ getter_must_be_callable: "Getter must be a function: %0",
+ setter_must_be_callable: "Setter must be a function: %0",
+ value_and_accessor: "Invalid property. A property cannot
both have accessors and be writable or have a value: %0",
+ proto_object_or_null: "Object prototype may only be an
Object or null",
+ property_desc_object: "Property description must be an
object: %0",
// RangeError
invalid_array_length: "Invalid array length",
stack_overflow: "Maximum call stack size exceeded",
=======================================
--- /branches/bleeding_edge/src/v8natives.js Tue Nov 17 05:54:05 2009
+++ /branches/bleeding_edge/src/v8natives.js Wed Dec 9 05:56:58 2009
@@ -41,6 +41,7 @@
const $isNaN = GlobalIsNaN;
const $isFinite = GlobalIsFinite;
+
//
----------------------------------------------------------------------------
@@ -87,7 +88,7 @@
// ECMA-262 - 15.1.2.2
function GlobalParseInt(string, radix) {
- if (radix === void 0) {
+ if (IS_UNDEFINED(radix)) {
// Some people use parseInt instead of Math.floor. This
// optimization makes parseInt on a Smi 12 times faster (60ns
// vs 800ns). The following optimization makes parseInt on a
@@ -278,6 +279,207 @@
throw MakeTypeError('object_keys_non_object', [obj]);
return %LocalKeys(obj);
}
+
+
+// ES5 8.10.1.
+function IsAccessorDescriptor(desc) {
+ if (IS_UNDEFINED(desc)) return false;
+ return desc.hasGetter_ || desc.hasSetter_;
+}
+
+
+// ES5 8.10.2.
+function IsDataDescriptor(desc) {
+ if (IS_UNDEFINED(desc)) return false;
+ return desc.hasValue_ || desc.hasWritable_;
+}
+
+
+// ES5 8.10.3.
+function IsGenericDescriptor(desc) {
+ return !(IsAccessorDescriptor(desc) || IsDataDescriptor(desc));
+}
+
+
+function IsInconsistentDescriptor(desc) {
+ return IsAccessorDescriptor(desc) && IsDataDescriptor(desc);
+}
+
+
+// ES5 8.10.5.
+function ToPropertyDescriptor(obj) {
+ if (!IS_OBJECT(obj)) {
+ throw MakeTypeError("property_desc_object", [obj]);
+ }
+ var desc = new PropertyDescriptor();
+
+ if ("enumerable" in obj) {
+ desc.setEnumerable(ToBoolean(obj.enumerable));
+ }
+
+
+ if ("configurable" in obj) {
+ desc.setConfigurable(ToBoolean(obj.configurable));
+ }
+
+ if ("value" in obj) {
+ desc.setValue(obj.value);
+ }
+
+ if ("writable" in obj) {
+ desc.setWritable(ToBoolean(obj.writable));
+ }
+
+ if ("get" in obj) {
+ var get = obj.get;
+ if (!IS_UNDEFINED(get) && !IS_FUNCTION(get)) {
+ throw MakeTypeError("getter_must_be_callable", [get]);
+ }
+ desc.setGet(get);
+ }
+
+ if ("set" in obj) {
+ var set = obj.set;
+ if (!IS_UNDEFINED(set) && !IS_FUNCTION(set)) {
+ throw MakeTypeError("setter_must_be_callable", [set]);
+ }
+ desc.setSet(set);
+ }
+
+ if (IsInconsistentDescriptor(desc)) {
+ throw MakeTypeError("value_and_accessor", [obj]);
+ }
+ return desc;
+}
+
+
+function PropertyDescriptor() {
+ // Initialize here so they are all in-object and have the same map.
+ // Default values from ES5 8.6.1.
+ this.value_ = void 0;
+ this.hasValue_ = false;
+ this.writable_ = false;
+ this.hasWritable_ = false;
+ this.enumerable_ = false;
+ this.configurable_ = false;
+ this.get_ = void 0;
+ this.hasGetter_ = false;
+ this.set_ = void 0;
+ this.hasSetter_ = false;
+}
+
+
+PropertyDescriptor.prototype.setValue = function(value) {
+ this.value_ = value;
+ this.hasValue_ = true;
+}
+
+
+PropertyDescriptor.prototype.getValue = function() {
+ return this.value_;
+}
+
+
+PropertyDescriptor.prototype.setEnumerable = function(enumerable) {
+ this.enumerable_ = enumerable;
+}
+
+
+PropertyDescriptor.prototype.isEnumerable = function () {
+ return this.enumerable_;
+}
+
+
+PropertyDescriptor.prototype.setWritable = function(writable) {
+ this.writable_ = writable;
+ this.hasWritable_ = true;
+}
+
+
+PropertyDescriptor.prototype.isWritable = function() {
+ return this.writable_;
+}
+
+
+PropertyDescriptor.prototype.setConfigurable = function(configurable) {
+ this.configurable_ = configurable;
+}
+
+
+PropertyDescriptor.prototype.isConfigurable = function() {
+ return this.configurable_;
+}
+
+
+PropertyDescriptor.prototype.setGet = function(get) {
+ this.get_ = get;
+ this.hasGetter_ = true;
+}
+
+
+PropertyDescriptor.prototype.getGet = function() {
+ return this.get_;
+}
+
+
+PropertyDescriptor.prototype.setSet = function(set) {
+ this.set_ = set;
+ this.hasSetter_ = true;
+}
+
+
+PropertyDescriptor.prototype.getSet = function() {
+ return this.set_;
+}
+
+
+// ES5 8.12.9. This version cannot cope with the property p already
+// being present on obj.
+function DefineOwnProperty(obj, p, desc, should_throw) {
+ var flag = desc.isEnumerable() ? 0 : DONT_ENUM;
+ if (IsDataDescriptor(desc)) {
+ flag |= desc.isWritable() ? 0 : (DONT_DELETE | READ_ONLY);
+ %SetProperty(obj, p, desc.getValue(), flag);
+ } else {
+ if (IS_FUNCTION(desc.getGet())) %DefineAccessor(obj, p, GETTER,
desc.getGet(), flag);
+ if (IS_FUNCTION(desc.getSet())) %DefineAccessor(obj, p, SETTER,
desc.getSet(), flag);
+ }
+ return true;
+}
+
+
+// ES5 section 15.2.3.5.
+function ObjectCreate(proto, properties) {
+ if (!IS_OBJECT(proto) && !IS_NULL(proto)) {
+ throw MakeTypeError("proto_object_or_null", [proto]);
+ }
+ var obj = new $Object();
+ obj.__proto__ = proto;
+ if (!IS_UNDEFINED(properties)) ObjectDefineProperties(obj, properties);
+ return obj;
+}
+
+
+// ES5 section 15.2.3.7. This version cannot cope with the properies
already
+// being present on obj. Therefore it is not exposed as
+// Object.defineProperties yet.
+function ObjectDefineProperties(obj, properties) {
+ var props = ToObject(properties);
+ var key_values = [];
+ for (var key in props) {
+ if (%HasLocalProperty(props, key)) {
+ key_values.push(key);
+ var value = props[key];
+ var desc = ToPropertyDescriptor(value);
+ key_values.push(desc);
+ }
+ }
+ for (var i = 0; i < key_values.length; i += 2) {
+ var key = key_values[i];
+ var desc = key_values[i + 1];
+ DefineOwnProperty(obj, key, desc, true);
+ }
+}
%SetCode($Object, function(x) {
@@ -309,7 +511,8 @@
"__lookupSetter__", ObjectLookupSetter
));
InstallFunctions($Object, DONT_ENUM, $Array(
- "keys", ObjectKeys
+ "keys", ObjectKeys,
+ "create", ObjectCreate
));
}
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev