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

Reply via email to