Added Object.defineProperty + needed internal functionality:
authorricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 3 Feb 2010 13:10:03 +0000 (13:10 +0000)
committerricow@chromium.org <ricow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 3 Feb 2010 13:10:03 +0000 (13:10 +0000)
  DefineOwnProperty (changed to allow for redefinition of existing property)
  SameValue
  Extra info on propertydescriptor
  GetProperty
  HasProperty

Currently the DefineOrRedefineAccessorProperty deletes the existing
property on the object if it is a dataproperty (FIELD or NORMAL) and
adds a new one. This can potentially be optimized.

Review URL: http://codereview.chromium.org/555149

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3786 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/messages.js
src/runtime.cc
src/runtime.h
src/runtime.js
src/v8natives.js
test/cctest/test-api.cc
test/es5conform/es5conform.status
test/mjsunit/object-define-property.js [new file with mode: 0644]

index df008c9..ca82afe 100644 (file)
@@ -162,6 +162,8 @@ function FormatMessage(message) {
       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",
+      redefine_disallowed:          "Cannot redefine property: %0",
+      define_disallowed:            "Cannot define property, object is not extensible: %0",
       // RangeError
       invalid_array_length:         "Invalid array length",
       stack_overflow:               "Maximum call stack size exceeded",
index 515343b..3da4212 100644 (file)
@@ -596,8 +596,9 @@ static Object* Runtime_GetOwnProperty(Arguments args) {
 
   if (result.type() == CALLBACKS) {
     Object* structure = result.GetCallbackObject();
-    if (structure->IsProxy()) {
-      // Property that is internally implemented as a callback.
+    if (structure->IsProxy() || structure->IsAccessorInfo()) {
+      // Property that is internally implemented as a callback or
+      // an API defined callback.
       Object* value = obj->GetPropertyWithCallback(
           obj, structure, name, result.holder());
       elms->set(0, Heap::false_value());
@@ -609,7 +610,6 @@ static Object* Runtime_GetOwnProperty(Arguments args) {
       elms->set(1, FixedArray::cast(structure)->get(0));
       elms->set(2, FixedArray::cast(structure)->get(1));
     } else {
-      // TODO(ricow): Handle API callbacks.
       return Heap::undefined_value();
     }
   } else {
@@ -619,7 +619,7 @@ static Object* Runtime_GetOwnProperty(Arguments args) {
   }
 
   elms->set(3, Heap::ToBoolean(!result.IsDontEnum()));
-  elms->set(4, Heap::ToBoolean(!result.IsReadOnly()));
+  elms->set(4, Heap::ToBoolean(!result.IsDontDelete()));
   return *desc;
 }
 
@@ -2888,6 +2888,66 @@ static Object* Runtime_KeyedGetProperty(Arguments args) {
 }
 
 
+static Object* Runtime_DefineOrRedefineAccessorProperty(Arguments args) {
+  ASSERT(args.length() == 5);
+  HandleScope scope;
+  Handle<JSObject> obj = args.at<JSObject>(0);
+  CONVERT_CHECKED(String, name, args[1]);
+  CONVERT_CHECKED(Smi, flag_setter, args[2]);
+  CONVERT_CHECKED(JSFunction, fun, args[3]);
+  CONVERT_CHECKED(Smi, flag_attr, args[4]);
+  int unchecked = flag_attr->value();
+  RUNTIME_ASSERT((unchecked & ~(READ_ONLY | DONT_ENUM | DONT_DELETE)) == 0);
+
+  LookupResult result;
+  obj->LocalLookupRealNamedProperty(name, &result);
+
+  PropertyAttributes attr = static_cast<PropertyAttributes>(unchecked);
+  // If an existing property is either FIELD, NORMAL or CONSTANT_FUNCTION
+  // delete it to avoid running into trouble in DefineAccessor, which
+  // handles this incorrectly if the property is readonly (does nothing)
+  if (result.type() == FIELD || result.type() == NORMAL
+      || result.type() == CONSTANT_FUNCTION)
+    obj->DeleteProperty(name, JSObject::NORMAL_DELETION);
+
+  return obj->DefineAccessor(name, flag_setter->value() == 0, fun, attr);
+}
+
+static Object* Runtime_DefineOrRedefineDataProperty(Arguments args) {
+  ASSERT(args.length() == 4);
+  HandleScope scope;
+  Handle<Object> obj = args.at<Object>(0);
+  Handle<Object> name = args.at<Object>(1);
+  Handle<Object> obj_value = args.at<Object>(2);
+  Handle<JSObject> js_object = Handle<JSObject>::cast(obj);
+  Handle<String> key_string = Handle<String>::cast(name);
+
+  CONVERT_CHECKED(Smi, flag, args[3]);
+  int unchecked = flag->value();
+  RUNTIME_ASSERT((unchecked & ~(READ_ONLY | DONT_ENUM | DONT_DELETE)) == 0);
+
+  LookupResult result;
+  js_object->LocalLookupRealNamedProperty(*key_string, &result);
+
+  PropertyAttributes attr = static_cast<PropertyAttributes>(unchecked);
+
+  // Take special care when attributes are different and there is already
+  // a property. For simplicity we normalize the property which enables us
+  // to not worry about changing the instance_descriptor and creating a new
+  // map. The current version of SetObjectProperty does not handle attributes
+  // correctly in the case where a property is a field and is reset with
+  // new attributes.
+  if (result.IsProperty() && attr != result.GetAttributes()) {
+    PropertyDetails details = PropertyDetails(attr, NORMAL);
+    // New attributes - normalize to avoid writing to instance descriptor
+    js_object->NormalizeProperties(KEEP_INOBJECT_PROPERTIES, 0);
+    return js_object->SetNormalizedProperty(*key_string, *obj_value, details);
+  }
+
+  return Runtime::SetObjectProperty(js_object, name, obj_value, attr);
+}
+
+
 Object* Runtime::SetObjectProperty(Handle<Object> object,
                                    Handle<Object> key,
                                    Handle<Object> value,
index b2b8609..96796d0 100644 (file)
@@ -215,6 +215,8 @@ namespace internal {
   F(ResolvePossiblyDirectEval, 3, 2) \
   \
   F(SetProperty, -1 /* 3 or 4 */, 1) \
+  F(DefineOrRedefineDataProperty, 4, 1) \
+  F(DefineOrRedefineAccessorProperty, 5, 1) \
   F(IgnoreAttributesAndSetProperty, -1 /* 3 or 4 */, 1) \
   \
   /* Arrays */ \
index c4c855e..10ef98e 100644 (file)
@@ -506,6 +506,16 @@ function ToPrimitive(x, hint) {
 }
 
 
+// ECMA-262, section 9.2, page 30
+function ToBoolean(x) {
+  if (IS_BOOLEAN(x)) return x;
+  if (IS_STRING(x)) return x.length != 0;
+  if (x == null) return false;
+  if (IS_NUMBER(x)) return !((x == 0) || NUMBER_IS_NAN(x));
+  return true;
+}
+
+
 // ECMA-262, section 9.3, page 31.
 function ToNumber(x) {
   if (IS_NUMBER(x)) return x;
@@ -526,16 +536,6 @@ function ToString(x) {
 }
 
 
-// ... where did this come from?
-function ToBoolean(x) {
-  if (IS_BOOLEAN(x)) return x;
-  if (IS_STRING(x)) return x.length != 0;
-  if (x == null) return false;
-  if (IS_NUMBER(x)) return !((x == 0) || NUMBER_IS_NAN(x));
-  return true;
-}
-
-
 // ECMA-262, section 9.9, page 36.
 function ToObject(x) {
   if (IS_STRING(x)) return new $String(x);
@@ -569,6 +569,25 @@ function ToInt32(x) {
 }
 
 
+// ES5, section 9.12
+function SameValue(x, y) {
+  if (typeof x != typeof y) return false;
+  if (IS_NULL_OR_UNDEFINED(x)) return true;
+  if (IS_NUMBER(x)) {
+    if (NUMBER_IS_NAN(x) && NUMBER_IS_NAN(y)) return true;
+    // x is +0 and y is -0 or vice versa
+    if (x === 0 && y === 0 && !%_IsSmi(x) && !%_IsSmi(y) && 
+        ((1 / x < 0 && 1 / y > 0) || (1 / x > 0 && 1 / y < 0))) {
+      return false;
+    }
+    return x == y;    
+  }
+  if (IS_STRING(x)) return %StringEquals(x, y);
+  if (IS_BOOLEAN(x))return %NumberEquals(%ToNumber(x),%ToNumber(y));
+
+  return %_ObjectEquals(x, y);
+}
+
 
 /* ---------------------------------
    - - -   U t i l i t i e s   - - -
index 7475065..08bdf8b 100644 (file)
@@ -307,7 +307,7 @@ function IsInconsistentDescriptor(desc) {
 
 // ES5 8.10.4
 function FromPropertyDescriptor(desc) {
-  if(IS_UNDEFINED(desc)) return desc;
+  if (IS_UNDEFINED(desc)) return desc;
   var obj = new $Object();
   if (IsDataDescriptor(desc)) {
     obj.value = desc.getValue();
@@ -333,7 +333,6 @@ function ToPropertyDescriptor(obj) {
     desc.setEnumerable(ToBoolean(obj.enumerable));
   }
 
-
   if ("configurable" in obj) {
     desc.setConfigurable(ToBoolean(obj.configurable));
   }
@@ -377,7 +376,9 @@ function PropertyDescriptor() {
   this.writable_ = false;
   this.hasWritable_ = false;
   this.enumerable_ = false;
+  this.hasEnumerable_ = false;
   this.configurable_ = false;
+  this.hasConfigurable_ = false;
   this.get_ = void 0;
   this.hasGetter_ = false;
   this.set_ = void 0;
@@ -396,8 +397,14 @@ PropertyDescriptor.prototype.getValue = function() {
 }
 
 
+PropertyDescriptor.prototype.hasValue = function() {
+  return this.hasValue_;
+}
+
+
 PropertyDescriptor.prototype.setEnumerable = function(enumerable) {
   this.enumerable_ = enumerable;
+  this.hasEnumerable_ = true;
 }
 
 
@@ -406,6 +413,11 @@ PropertyDescriptor.prototype.isEnumerable = function () {
 }
 
 
+PropertyDescriptor.prototype.hasEnumerable = function() {
+  return this.hasEnumerable_;
+}
+
+
 PropertyDescriptor.prototype.setWritable = function(writable) {
   this.writable_ = writable;
   this.hasWritable_ = true;
@@ -419,6 +431,12 @@ PropertyDescriptor.prototype.isWritable = function() {
 
 PropertyDescriptor.prototype.setConfigurable = function(configurable) {
   this.configurable_ = configurable;
+  this.hasConfigurable_ = true;
+}
+
+
+PropertyDescriptor.prototype.hasConfigurable = function() {
+  return this.hasConfigurable_;
 }
 
 
@@ -438,6 +456,11 @@ PropertyDescriptor.prototype.getGet = function() {
 }
 
 
+PropertyDescriptor.prototype.hasGetter = function() {
+  return this.hasGetter_;
+}
+
+
 PropertyDescriptor.prototype.setSet = function(set) {
   this.set_ = set;
   this.hasSetter_ = true;
@@ -449,6 +472,12 @@ PropertyDescriptor.prototype.getSet = function() {
 }
 
 
+PropertyDescriptor.prototype.hasSetter = function() {
+  return this.hasSetter_;
+}
+
+
+
 // ES5 section 8.12.1.
 function GetOwnProperty(obj, p) {
   var desc = new PropertyDescriptor();
@@ -458,8 +487,7 @@ function GetOwnProperty(obj, p) {
   //  obj is an accessor [true, Get, Set, Enumerable, Configurable]
   var props = %GetOwnProperty(ToObject(obj), ToString(p));
 
-  if (IS_UNDEFINED(props))
-    return void 0;
+  if (IS_UNDEFINED(props)) return void 0;
 
   // This is an accessor
   if (props[0]) {
@@ -476,16 +504,89 @@ function GetOwnProperty(obj, p) {
 }
 
 
-// ES5 8.12.9.  This version cannot cope with the property p already
-// being present on obj.
+// ES5 section 8.12.2.
+function GetProperty(obj, p) {
+  var prop = GetOwnProperty(obj);
+  if (!IS_UNDEFINED(prop)) return prop;
+  var proto = obj.__proto__;
+  if (IS_NULL(proto)) return void 0;
+  return GetProperty(proto, p);
+}
+
+
+// ES5 section 8.12.6
+function HasProperty(obj, p) {
+  var desc = GetProperty(obj, p);
+  return IS_UNDEFINED(desc) ? false : true;
+}
+
+
+// ES5 8.12.9.  
 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);
+  var current = GetOwnProperty(obj, p);
+  var extensible = %IsExtensible(ToObject(obj));
+
+  // Error handling according to spec.
+  // Step 3
+  if (IS_UNDEFINED(current) && !extensible)
+    throw MakeTypeError("define_disallowed", ["defineProperty"]);
+
+  if (!IS_UNDEFINED(current) && !current.isConfigurable()) {
+    // Step 7
+    if (desc.isConfigurable() ||  desc.isEnumerable() != current.isEnumerable())
+      throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+    // Step 9
+    if (IsDataDescriptor(current) != IsDataDescriptor(desc))
+      throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+    // Step 10
+    if (IsDataDescriptor(current) && IsDataDescriptor(desc)) {
+      if (!current.isWritable() && desc.isWritable())
+        throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+      if (!current.isWritable() && desc.hasValue() &&
+          !SameValue(desc.getValue(), current.getValue())) {
+        throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+      }
+    }
+    // Step 11
+    if (IsAccessorDescriptor(desc) && IsAccessorDescriptor(current)) {
+      if (desc.hasSetter() && !SameValue(desc.getSet(), current.getSet())){
+        throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+      }
+      if (desc.hasGetter() && !SameValue(desc.getGet(),current.getGet()))
+        throw MakeTypeError("redefine_disallowed", ["defineProperty"]);
+    }
+  }
+
+  // Send flags - enumerable and configurable are common - writable is 
+  // only send to the data descriptor.
+  // Take special care if enumerable and configurable is not defined on
+  // desc (we need to preserve the existing values from current).
+  var flag = NONE;
+  if (desc.hasEnumerable()) {
+    flag |= desc.isEnumerable() ? 0 : DONT_ENUM;
+  } else if (!IS_UNDEFINED(current)) {
+    flag |= current.isEnumerable() ? 0 : DONT_ENUM;
   } 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);
+    flag |= DONT_ENUM;
+  }
+
+  if (desc.hasConfigurable()) {
+    flag |= desc.isConfigurable() ? 0 : DONT_DELETE;
+  } else if (!IS_UNDEFINED(current)) {
+    flag |= current.isConfigurable() ? 0 : DONT_DELETE;
+  } else
+    flag |= DONT_DELETE;
+
+  if (IsDataDescriptor(desc) || IsGenericDescriptor(desc)) {
+    flag |= desc.isWritable() ? 0 : READ_ONLY;
+    %DefineOrRedefineDataProperty(obj, p, desc.getValue(), flag);
+  } else {
+    if (desc.hasGetter() && IS_FUNCTION(desc.getGet())) {
+       %DefineOrRedefineAccessorProperty(obj, p, GETTER, desc.getGet(), flag);
+    }
+    if (desc.hasSetter() && IS_FUNCTION(desc.getSet())) {
+      %DefineOrRedefineAccessorProperty(obj, p, SETTER, desc.getSet(), flag);
+    }
   }
   return true;
 }
@@ -558,10 +659,21 @@ function ObjectCreate(proto, properties) {
 }
 
 
-// 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.
+// ES5 section 15.2.3.6.
+function ObjectDefineProperty(obj, p, attributes) {
+  if ((!IS_OBJECT(obj) || IS_NULL_OR_UNDEFINED(obj)) && !IS_FUNCTION(obj))
+    throw MakeTypeError("obj_ctor_property_non_object", ["defineProperty"]);
+  var name = ToString(p);
+  var desc = ToPropertyDescriptor(attributes);
+  DefineOwnProperty(obj, name, desc, true);
+  return obj;
+}
+
+
+// ES5 section 15.2.3.7.
 function ObjectDefineProperties(obj, properties) {
+ if ((!IS_OBJECT(obj) || IS_NULL_OR_UNDEFINED(obj)) && !IS_FUNCTION(obj))
+    throw MakeTypeError("obj_ctor_property_non_object", ["defineProperties"]);
   var props = ToObject(properties);
   var key_values = [];
   for (var key in props) {
@@ -611,6 +723,8 @@ function SetupObject() {
   InstallFunctions($Object, DONT_ENUM, $Array(
     "keys", ObjectKeys,
     "create", ObjectCreate,
+    "defineProperty", ObjectDefineProperty,
+    "defineProperties", ObjectDefineProperties,
     "getPrototypeOf", ObjectGetPrototypeOf,
     "getOwnPropertyDescriptor", ObjectGetOwnPropertyDescriptor,
     "getOwnPropertyNames", ObjectGetOwnPropertyNames
index f71b325..4aa0280 100644 (file)
@@ -2297,6 +2297,103 @@ THREADED_TEST(SimplePropertyRead) {
   }
 }
 
+THREADED_TEST(DefinePropertyOnAPIAccessor) {
+  v8::HandleScope scope;
+  Local<ObjectTemplate> templ = ObjectTemplate::New();
+  templ->SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut"));
+  LocalContext context;
+  context->Global()->Set(v8_str("obj"), templ->NewInstance());
+
+  // Uses getOwnPropertyDescriptor to check the configurable status
+  Local<Script> script_desc
+    = Script::Compile(v8_str("var prop =Object.getOwnPropertyDescriptor( "
+                             "obj, 'x');"
+                             "prop.configurable;"));
+  Local<Value> result = script_desc->Run();
+  CHECK_EQ(result->BooleanValue(), true);
+
+  // Redefine get - but still configurable
+  Local<Script> script_define
+    = Script::Compile(v8_str("var desc = { get: function(){return 42; },"
+                             "            configurable: true };"
+                             "Object.defineProperty(obj, 'x', desc);"
+                             "obj.x"));
+  result = script_define->Run();
+  CHECK_EQ(result, v8_num(42));
+
+  // Check that the accessor is still configurable
+  result = script_desc->Run();
+  CHECK_EQ(result->BooleanValue(), true);
+
+  // Redefine to a non-configurable
+  script_define
+    = Script::Compile(v8_str("var desc = { get: function(){return 43; },"
+                             "             configurable: false };"
+                             "Object.defineProperty(obj, 'x', desc);"
+                             "obj.x"));
+  result = script_define->Run();
+  CHECK_EQ(result, v8_num(43));
+  result = script_desc->Run();
+  CHECK_EQ(result->BooleanValue(), false);
+
+  // Make sure that it is not possible to redefine again
+  v8::TryCatch try_catch;
+  result = script_define->Run();
+  CHECK(try_catch.HasCaught());
+  String::AsciiValue exception_value(try_catch.Exception());
+  CHECK_EQ(*exception_value,
+           "TypeError: Cannot redefine property: defineProperty");
+}
+
+THREADED_TEST(DefinePropertyOnDefineGetterSetter) {
+  v8::HandleScope scope;
+  Local<ObjectTemplate> templ = ObjectTemplate::New();
+  templ->SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut"));
+  LocalContext context;
+  context->Global()->Set(v8_str("obj"), templ->NewInstance());
+
+  Local<Script> script_desc = Script::Compile(v8_str("var prop ="
+                                    "Object.getOwnPropertyDescriptor( "
+                                    "obj, 'x');"
+                                    "prop.configurable;"));
+  Local<Value> result = script_desc->Run();
+  CHECK_EQ(result->BooleanValue(), true);
+
+  Local<Script> script_define =
+    Script::Compile(v8_str("var desc = {get: function(){return 42; },"
+                           "            configurable: true };"
+                           "Object.defineProperty(obj, 'x', desc);"
+                           "obj.x"));
+  result = script_define->Run();
+  CHECK_EQ(result, v8_num(42));
+
+
+  result = script_desc->Run();
+  CHECK_EQ(result->BooleanValue(), true);
+
+
+  script_define =
+    Script::Compile(v8_str("var desc = {get: function(){return 43; },"
+                           "            configurable: false };"
+                           "Object.defineProperty(obj, 'x', desc);"
+                           "obj.x"));
+  result = script_define->Run();
+  CHECK_EQ(result, v8_num(43));
+  result = script_desc->Run();
+
+  CHECK_EQ(result->BooleanValue(), false);
+
+  v8::TryCatch try_catch;
+  result = script_define->Run();
+  CHECK(try_catch.HasCaught());
+  String::AsciiValue exception_value(try_catch.Exception());
+  CHECK_EQ(*exception_value,
+           "TypeError: Cannot redefine property: defineProperty");
+}
+
+
+
+
 
 v8::Persistent<Value> xValue;
 
index a755016..c11d7dc 100644 (file)
@@ -39,8 +39,6 @@ chapter14: UNIMPLEMENTED
 chapter15/15.1: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.1: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.5: UNIMPLEMENTED
-chapter15/15.2/15.2.3/15.2.3.6: UNIMPLEMENTED
-chapter15/15.2/15.2.3/15.2.3.7: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.8: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.9: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.10: UNIMPLEMENTED
@@ -48,24 +46,6 @@ chapter15/15.2/15.2.3/15.2.3.11: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.12: UNIMPLEMENTED
 chapter15/15.2/15.2.3/15.2.3.13: UNIMPLEMENTED
 
-# Object.getPrototypeOf
-chapter15/15.2/15.2.3/15.2.3.2: PASS
-
-# Object.getOwnPropertyDescriptor
-chapter15/15.2/15.2.3/15.2.3.3: PASS
-
-# NOT IMPLEMENTED: defineProperty
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-3: FAIL_OK
-
-# NOT IMPLEMENTED: getOwnPropertyNames
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-16: FAIL_OK
-
-# NOT IMPLEMENTED: defineProperty
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-18: FAIL_OK
-
-# NOT IMPLEMENTED: defineProperties
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-19: FAIL_OK
-
 # NOT IMPLEMENTED: seal
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-20: FAIL_OK
 
@@ -87,37 +67,24 @@ chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-25: FAIL_OK
 # NOT IMPLEMENTED: bind
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-38: FAIL_OK
 
-# Built-ins have wrong descriptor (should all be false)
+# NaN is writable
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-178: FAIL_OK
+# Infinity is writable
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-179: FAIL_OK
+# undefined is writable
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-180: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-182: FAIL_OK
 
 # Our Function object has a "arguments" property which is used as a non
 # property in in the test
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-183: FAIL_OK
 
-
 # Our Function object has a "caller" property which is used as a non
 # property in in the test
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-184: FAIL_OK
 
-# Built-ins have wrong descriptor (should all be false)
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-185: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-186: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-187: FAIL_OK
+# Our function object has a name property which is used as a non
+# property in the test
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-188: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-189: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-190: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-191: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-192: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-193: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-194: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-195: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-201: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-210: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-211: FAIL_OK
-
 
 # NOT IMPLEMENTED: RegExp.prototype.source
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-212: FAIL_OK
@@ -131,18 +98,6 @@ chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-214: FAIL_OK
 # NOT IMPLEMENTED: RegExp.prototype.multiline
 chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-215: FAIL_OK
 
-# Errors have wrong descriptor (should all be false)
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-216: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-217: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-218: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-219: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-220: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-221: FAIL_OK
-chapter15/15.2/15.2.3/15.2.3.3/15.2.3.3-4-222: FAIL_OK
-
-# Object.getOwnPropertyNames
-chapter15/15.2/15.2.3/15.2.3.4: PASS
-
 # All of the tests below marked SUBSETFAIL (in 15.2.3.4) fail because 
 # the tests assumes that objects can not have more properties
 # than those described in the spec - but according to spec they can 
@@ -252,12 +207,9 @@ chapter15/15.2/15.2.3/15.2.3.4/15.2.3.4-4-b-1: FAIL_OK
 
 
 
-# Object.keys
-chapter15/15.2/15.2.3/15.2.3.14: PASS
-
 # We fail this because Object.keys returns numbers for element indices
 # rather than strings.
-chapter15/15.2/15.2.3/15.2.3.14/15.2.3.14-3-3: FAIL_OK
+#chapter15/15.2/15.2.3/15.2.3.14/15.2.3.14-3-3: FAIL_OK
 
 chapter15/15.3: UNIMPLEMENTED
 
@@ -267,9 +219,6 @@ chapter15/15.4/15.4.4/15.4.4.20: UNIMPLEMENTED
 chapter15/15.4/15.4.4/15.4.4.21: UNIMPLEMENTED
 chapter15/15.4/15.4.4/15.4.4.22: UNIMPLEMENTED
 
-# Array.prototype.every
-chapter15/15.4/15.4.4/15.4.4.16: PASS
-
 # Wrong test - because this is not given as argument to arr.every
 # this._15_4_4_16_5_1 evaluates to undefined
 chapter15/15.4/15.4.4/15.4.4.16/15.4.4.16-5-1: FAIL_OK
@@ -285,10 +234,6 @@ chapter15/15.4/15.4.4/15.4.4.16/15.4.4.16-7-7: FAIL_OK
 # if (val>1) in test should be if (val>2)
 chapter15/15.4/15.4.4/15.4.4.16/15.4.4.16-8-10: FAIL_OK
 
-
-# Array.prototype.some
-chapter15/15.4/15.4.4/15.4.4.17: PASS
-
 # Wrong assumption - according to spec some returns a Boolean, not a number
 chapter15/15.4/15.4.4/15.4.4.17/15.4.4.17-4-9: FAIL_OK
 
@@ -304,20 +249,12 @@ chapter15/15.4/15.4.4/15.4.4.17/15.4.4.17-7-7: FAIL_OK
 # Same as 15.4.4.16-10-8
 chapter15/15.4/15.4.4/15.4.4.17/15.4.4.17-8-10: FAIL_OK
 
-
-# Array.prototype.forEach
-chapter15/15.4/15.4.4/15.4.4.18: PASS
-
 # Same as 15.4.4.16-5-1
 chapter15/15.4/15.4.4/15.4.4.18/15.4.4.18-5-1: FAIL_OK
 
 # Same as 15.4.4.16-7-7
 chapter15/15.4/15.4.4/15.4.4.18/15.4.4.18-7-6: FAIL_OK
 
-
-# Array.prototype.map
-chapter15/15.4/15.4.4/15.4.4.19: PASS
-
 # Same as 15.4.4.16-5-1
 chapter15/15.4/15.4.4/15.4.4.19/15.4.4.19-5-1: FAIL_OK
 
diff --git a/test/mjsunit/object-define-property.js b/test/mjsunit/object-define-property.js
new file mode 100644 (file)
index 0000000..089fb7e
--- /dev/null
@@ -0,0 +1,454 @@
+// Copyright 2010 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.
+
+// Tests the object.defineProperty method - ES 15.2.3.6
+
+
+
+// Check that an exception is thrown when null is passed as object.
+try {
+  Object.defineProperty(null, null, null);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/called on non-object/.test(e));
+}
+
+// Check that an exception is thrown when undefined is passed as object.
+try {
+  Object.defineProperty(undefined, undefined, undefined);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/called on non-object/.test(e));
+}
+
+// Check that an exception is thrown when non-object is passed as object.
+try {
+  Object.defineProperty(0, "foo", undefined);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/called on non-object/.test(e));
+}
+
+// Object
+var obj1 = {};
+
+// Values
+var val1 = 0;
+var val2 = 0;
+var val3 = 0;
+
+// Descriptors
+var emptyDesc = {};
+
+var accessorConfigurable = { 
+    set: function() { val1++; },
+    get: function() { return val1; },
+    configurable: true
+};
+
+var accessorNoConfigurable = {
+    set: function() { val2++; },
+    get: function() { return val2; },
+    configurable: false 
+};
+
+var accessorOnlySet = {
+  set: function() { val3++; },
+  configurable: true
+};
+
+var accessorOnlyGet = {
+  get: function() { return val3; },
+  configurable: true
+};
+
+var accessorDefault = {set: function(){} };
+
+var dataConfigurable = { value: 1000, configurable: true };
+
+var dataNoConfigurable = { value: 2000, configurable: false };
+
+var dataWritable = { value: 3000, writable: true};
+
+
+// Check that we can't add property with undefined attributes.
+try {
+  Object.defineProperty(obj1, "foo", undefined);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/must be an object/.test(e));
+}
+
+// Make sure that we can add a property with an empty descriptor and
+// that it has the default descriptor values.
+Object.defineProperty(obj1, "foo", emptyDesc);
+
+// foo should be undefined as it has no get, set or value
+assertEquals(undefined, obj1.foo);
+
+// We should, however, be able to retrieve the propertydescriptor which should
+// have all default values (according to 8.6.1).
+var desc = Object.getOwnPropertyDescriptor(obj1, "foo");
+assertFalse(desc.configurable);
+assertFalse(desc.enumerable);
+assertFalse(desc.writable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+assertEquals(desc.value, undefined);
+
+// Make sure that getOwnPropertyDescriptor does not return a descriptor
+// with default values if called with non existing property (otherwise
+// the test above is invalid).
+desc = Object.getOwnPropertyDescriptor(obj1, "bar");
+assertEquals(desc, undefined);
+
+// Make sure that foo can't be reset (as configurable is false).
+try {
+  Object.defineProperty(obj1, "foo", accessorConfigurable);
+} catch (e) {
+  assertTrue(/Cannot redefine property/.test(e));
+}
+
+
+// Accessor properties
+
+Object.defineProperty(obj1, "bar", accessorConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "bar");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorConfigurable.get);
+assertEquals(desc.set, accessorConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj1.bar = 1);
+assertEquals(1, val1);
+assertEquals(1, obj1.bar = 1);
+assertEquals(2, val1);
+assertEquals(2, obj1.bar);
+
+// Redefine bar with non configurable test
+Object.defineProperty(obj1, "bar", accessorNoConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "bar");
+assertFalse(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorNoConfigurable.get);
+assertEquals(desc.set, accessorNoConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj1.bar = 1);
+assertEquals(2, val1);
+assertEquals(1, val2);
+assertEquals(1, obj1.bar = 1)
+assertEquals(2, val1);
+assertEquals(2, val2);
+assertEquals(2, obj1.bar);
+
+// Try to redefine bar again - should fail as configurable is false.
+try {
+  Object.defineProperty(obj1, "bar", accessorConfigurable);
+  assertTrue(false);
+} catch(e) {
+  assertTrue(/Cannot redefine property/.test(e));
+}
+
+// Try to redefine bar again using the data descriptor - should fail.
+try {
+  Object.defineProperty(obj1, "bar", dataConfigurable);
+  assertTrue(false);
+} catch(e) {
+  assertTrue(/Cannot redefine property/.test(e));
+}
+
+// Redefine using same descriptor - should succeed.
+Object.defineProperty(obj1, "bar", accessorNoConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "bar");
+assertFalse(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorNoConfigurable.get);
+assertEquals(desc.set, accessorNoConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj1.bar = 1);
+assertEquals(2, val1);
+assertEquals(3, val2);
+assertEquals(1, obj1.bar = 1)
+assertEquals(2, val1);
+assertEquals(4, val2);
+assertEquals(4, obj1.bar);
+
+// Define an accessor that has only a setter
+Object.defineProperty(obj1, "setOnly", accessorOnlySet);
+desc = Object.getOwnPropertyDescriptor(obj1, "setOnly");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.set, accessorOnlySet.set);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.value, undefined);
+assertEquals(desc.get, undefined);
+assertEquals(1, obj1.setOnly = 1);
+assertEquals(1, val3);
+
+// Add a getter - should not touch the setter
+Object.defineProperty(obj1, "setOnly", accessorOnlyGet);
+desc = Object.getOwnPropertyDescriptor(obj1, "setOnly");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, accessorOnlyGet.get);
+assertEquals(desc.set, accessorOnlySet.set);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj1.setOnly = 1);
+assertEquals(2, val3);
+
+// The above should also work if redefining just a getter or setter on 
+// an existing property with both a getter and a setter.
+Object.defineProperty(obj1, "both", accessorConfigurable);
+
+Object.defineProperty(obj1, "both", accessorOnlySet);
+desc = Object.getOwnPropertyDescriptor(obj1, "both");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.set, accessorOnlySet.set);
+assertEquals(desc.get, accessorConfigurable.get);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj1.both = 1);
+assertEquals(3, val3);
+
+
+// Data properties
+
+Object.defineProperty(obj1, "foobar", dataConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "foobar");
+assertEquals(obj1.foobar, 1000);
+assertEquals(desc.value, 1000);
+assertTrue(desc.configurable);
+assertFalse(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+//Try writing to non writable attribute - should remain 1000
+obj1.foobar = 1001;
+assertEquals(obj1.foobar, 1000);
+
+
+// Redefine to writable descriptor - now writing to foobar should be allowed
+Object.defineProperty(obj1, "foobar", dataWritable);
+desc = Object.getOwnPropertyDescriptor(obj1, "foobar");
+assertEquals(obj1.foobar, 3000);
+assertEquals(desc.value, 3000);
+// Note that since dataWritable does not define configurable the configurable
+// setting from the redefined property (in this case true) is used.
+assertTrue(desc.configurable);
+assertTrue(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+// Writing to the property should now be allowed
+obj1.foobar = 1001;
+assertEquals(obj1.foobar, 1001);
+
+
+// Redefine with non configurable data property.
+Object.defineProperty(obj1, "foobar", dataNoConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "foobar");
+assertEquals(obj1.foobar, 2000);
+assertEquals(desc.value, 2000);
+assertFalse(desc.configurable);
+assertFalse(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+
+// Try redefine again - shold fail because configurable is now false.
+try {
+  Object.defineProperty(obj1, "foobar", dataConfigurable);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/Cannot redefine property/.test(e));
+}
+
+// Try redefine again with accessor property - shold also fail.
+try {
+  Object.defineProperty(obj1, "foobar", dataConfigurable);
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/Cannot redefine property/.test(e));
+}
+
+
+// Redifine with the same descriptor - should succeed (step 6).
+Object.defineProperty(obj1, "foobar", dataNoConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj1, "foobar");
+assertEquals(obj1.foobar, 2000);
+assertEquals(desc.value, 2000);
+assertFalse(desc.configurable);
+assertFalse(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+
+
+// New object
+var obj2 = {};
+
+// Make accessor - redefine to data
+Object.defineProperty(obj2, "foo", accessorConfigurable);
+
+// Redefine to data property
+Object.defineProperty(obj2, "foo", dataConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj2, "foo");
+assertEquals(obj2.foo, 1000);
+assertEquals(desc.value, 1000);
+assertTrue(desc.configurable);
+assertFalse(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+
+
+// Redefine back to accessor
+Object.defineProperty(obj2, "foo", accessorConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj2, "foo");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorConfigurable.get);
+assertEquals(desc.set, accessorConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj2.foo = 1);
+assertEquals(3, val1);
+assertEquals(4, val2);
+assertEquals(3, obj2.foo);
+
+// Make data - redefine to accessor
+Object.defineProperty(obj2, "bar", dataConfigurable)
+
+// Redefine to accessor property
+Object.defineProperty(obj2, "bar", accessorConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj2, "bar");
+assertTrue(desc.configurable);
+assertFalse(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorConfigurable.get);
+assertEquals(desc.set, accessorConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj2.bar = 1);
+assertEquals(4, val1);
+assertEquals(4, val2);
+assertEquals(4, obj2.foo);
+
+// Redefine back to data property
+Object.defineProperty(obj2, "bar", dataConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj2, "bar");
+assertEquals(obj2.bar, 1000);
+assertEquals(desc.value, 1000);
+assertTrue(desc.configurable);
+assertFalse(desc.writable);
+assertFalse(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+
+
+// Redefinition of an accessor defined using __defineGetter__ and 
+// __defineSetter__
+function get(){return this.x}
+function set(x){this.x=x};
+
+var obj3 = {x:1000};
+obj3.__defineGetter__("foo", get);
+obj3.__defineSetter__("foo", set);
+
+desc = Object.getOwnPropertyDescriptor(obj3, "foo");
+assertTrue(desc.configurable);
+assertTrue(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, get);
+assertEquals(desc.set, set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj3.foo = 1);
+assertEquals(1, obj3.x);
+assertEquals(1, obj3.foo);
+
+// Redefine to accessor property (non configurable) - note that enumerable
+// which we do not redefine should remain the same (true).
+Object.defineProperty(obj3, "foo", accessorNoConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj3, "foo");
+assertFalse(desc.configurable);
+assertTrue(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorNoConfigurable.get);
+assertEquals(desc.set, accessorNoConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj3.foo = 1);
+assertEquals(5, val2);
+assertEquals(5, obj3.foo);
+
+
+obj3.__defineGetter__("bar", get);
+obj3.__defineSetter__("bar", set);
+
+
+// Redefine back to data property
+Object.defineProperty(obj3, "bar", dataConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj3, "bar");
+assertEquals(obj3.bar, 1000);
+assertEquals(desc.value, 1000);
+assertTrue(desc.configurable);
+assertFalse(desc.writable);
+assertTrue(desc.enumerable);
+assertEquals(desc.get, undefined);
+assertEquals(desc.set, undefined);
+
+
+var obj4 = {};
+var func = function (){return 42;};
+obj4.bar = func;
+assertEquals(42, obj4.bar());
+
+Object.defineProperty(obj4, "bar", accessorConfigurable);
+desc = Object.getOwnPropertyDescriptor(obj4, "bar");
+assertTrue(desc.configurable);
+assertTrue(desc.enumerable);
+assertEquals(desc.writable, undefined);
+assertEquals(desc.get, accessorConfigurable.get);
+assertEquals(desc.set, accessorConfigurable.set);
+assertEquals(desc.value, undefined);
+assertEquals(1, obj4.bar = 1);
+assertEquals(5, val1);
+assertEquals(5, obj4.bar);
+
+// Make sure an error is thrown when trying to access to redefined function
+try {
+  obj4.bar();
+  assertTrue(false);
+} catch (e) {
+  assertTrue(/is not a function/.test(e));
+}
+
+
+