- Allow hidden properties to be set on any JSObject through the V8 C++ API.
authoriposva@chromium.org <iposva@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 19 Mar 2009 18:50:00 +0000 (18:50 +0000)
committeriposva@chromium.org <iposva@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 19 Mar 2009 18:50:00 +0000 (18:50 +0000)
- Use the hidden properties to expose a IdentityHash accessor.

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

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

include/v8.h
src/api.cc
src/factory.h
src/handles.cc
src/handles.h
src/heap.cc
src/heap.h
src/objects-inl.h
src/objects.cc
src/objects.h
test/cctest/test-api.cc

index 666408b..ce1e9b6 100644 (file)
@@ -1075,6 +1075,22 @@ class V8EXPORT Object : public Value {
    * access check info, the object cannot be accessed by anyone.
    */
   void TurnOnAccessCheck();
+  
+  /**
+   * Returns the identity hash for this object. The current implemenation uses
+   * a hidden property on the object to store the identity hash.
+   */
+  int GetIdentityHash();
+  
+  /**
+   * Access hidden properties on JavaScript objects. These properties are
+   * hidden from the executing JavaScript and only accessible through the V8
+   * C++ API. Hidden properties introduced by V8 internally (for example the
+   * identity hash) are prefixed with "v8::".
+   */
+  bool SetHiddenValue(Handle<String> key, Handle<Value> value);
+  Local<Value> GetHiddenValue(Handle<String> key);
+  bool DeleteHiddenValue(Handle<String> key);
 
   /**
    * Clone this object with a fast but shallow copy.  Values will point
index fa72f0a..7e0e460 100644 (file)
@@ -1930,6 +1930,77 @@ Local<v8::Object> v8::Object::Clone() {
 }
 
 
+int v8::Object::GetIdentityHash() {
+  ON_BAILOUT("v8::Object::GetIdentityHash()", return 0);
+  i::Handle<i::JSObject> self = Utils::OpenHandle(this);
+  i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, true));
+  i::Handle<i::Object> hash_symbol = i::Factory::identity_hash_symbol();
+  i::Handle<i::Object> hash = i::GetProperty(hidden_props, hash_symbol);
+  int hash_value;
+  if (hash->IsSmi()) {
+    hash_value = i::Smi::cast(*hash)->value();
+  } else {
+    hash_value = random() & i::Smi::kMaxValue;  // Limit range to fit a smi.
+    i::SetProperty(hidden_props,
+                   hash_symbol,
+                   i::Handle<i::Object>(i::Smi::FromInt(hash_value)),
+                   static_cast<PropertyAttributes>(None));
+  }
+  return hash_value;
+}
+
+
+bool v8::Object::SetHiddenValue(v8::Handle<v8::String> key,
+                                v8::Handle<v8::Value> value) {
+  ON_BAILOUT("v8::Object::SetHiddenValue()", return false);
+  i::Handle<i::JSObject> self = Utils::OpenHandle(this);
+  i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, true));
+  i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
+  i::Handle<i::Object> value_obj = Utils::OpenHandle(*value);
+  EXCEPTION_PREAMBLE();
+  i::Handle<i::Object> obj = i::SetProperty(
+      hidden_props,
+      key_obj,
+      value_obj,
+      static_cast<PropertyAttributes>(None));
+  has_pending_exception = obj.is_null();
+  EXCEPTION_BAILOUT_CHECK(false);
+  return true;
+}
+
+
+v8::Local<v8::Value> v8::Object::GetHiddenValue(v8::Handle<v8::String> key) {
+  ON_BAILOUT("v8::Object::GetHiddenValue()", return Local<v8::Value>());
+  i::Handle<i::JSObject> self = Utils::OpenHandle(this);
+  i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, false));
+  if (hidden_props->IsUndefined()) {
+    return v8::Local<v8::Value>();
+  }
+  i::Handle<i::String> key_obj = Utils::OpenHandle(*key);
+  EXCEPTION_PREAMBLE();
+  i::Handle<i::Object> result = i::GetProperty(hidden_props, key_obj);
+  has_pending_exception = result.is_null();
+  EXCEPTION_BAILOUT_CHECK(v8::Local<v8::Value>());
+  if (result->IsUndefined()) {
+    return v8::Local<v8::Value>();
+  }
+  return Utils::ToLocal(result);
+}
+
+
+bool v8::Object::DeleteHiddenValue(v8::Handle<v8::String> key) {
+  ON_BAILOUT("v8::DeleteHiddenValue()", return false);
+  i::Handle<i::JSObject> self = Utils::OpenHandle(this);
+  i::Handle<i::JSObject> hidden_props(
+      i::JSObject::cast(*i::GetHiddenProperties(self, false)));
+  if (hidden_props->IsUndefined()) {
+    return false;
+  }
+  i::Handle<i::String> key_obj = Utils::OpenHandle(*key);
+  return i::DeleteProperty(hidden_props, key_obj)->IsTrue();
+}
+
+
 Local<v8::Object> Function::NewInstance() const {
   return NewInstance(0, NULL);
 }
index 38d1a5b..f014ba5 100644 (file)
@@ -299,6 +299,10 @@ class Factory : public AllStatic {
   static Handle<String> name() { return Handle<String>(&Heap::name##_); }
   SYMBOL_LIST(SYMBOL_ACCESSOR)
 #undef SYMBOL_ACCESSOR
+  
+  static Handle<String> hidden_symbol() {
+    return Handle<String>(&Heap::hidden_symbol_);
+  }
 
   static Handle<SharedFunctionInfo> NewSharedFunctionInfo(Handle<String> name);
 
index 3772dcf..2382706 100644 (file)
@@ -261,6 +261,12 @@ Handle<Object> GetPrototype(Handle<Object> obj) {
 }
 
 
+Handle<Object> GetHiddenProperties(Handle<JSObject> obj,
+                                   bool create_if_needed) {
+  CALL_HEAP_FUNCTION(obj->GetHiddenProperties(create_if_needed), Object);
+}
+
+
 Handle<Object> DeleteElement(Handle<JSObject> obj,
                              uint32_t index) {
   CALL_HEAP_FUNCTION(obj->DeleteElement(index), Object);
index 302dc12..c8e534e 100644 (file)
@@ -223,6 +223,8 @@ Handle<Object> GetPropertyWithInterceptor(Handle<JSObject> receiver,
 
 Handle<Object> GetPrototype(Handle<Object> obj);
 
+Handle<Object> GetHiddenProperties(Handle<JSObject> obj, bool create_if_needed);
+
 Handle<Object> DeleteElement(Handle<JSObject> obj, uint32_t index);
 Handle<Object> DeleteProperty(Handle<JSObject> obj, Handle<String> prop);
 
index e8950ee..ce60ace 100644 (file)
@@ -56,6 +56,8 @@ namespace v8 { namespace internal {
   SYMBOL_LIST(SYMBOL_ALLOCATION)
 #undef SYMBOL_ALLOCATION
 
+String* Heap::hidden_symbol_;
+
 NewSpace Heap::new_space_;
 OldSpace* Heap::old_pointer_space_ = NULL;
 OldSpace* Heap::old_data_space_ = NULL;
@@ -1202,6 +1204,16 @@ bool Heap::CreateInitialObjects() {
   (name##_) = String::cast(obj);
   SYMBOL_LIST(SYMBOL_INITIALIZE)
 #undef SYMBOL_INITIALIZE
+  
+  // Allocate the hidden symbol which is used to identify the hidden properties
+  // in JSObjects. The hash code has a special value so that it will not match
+  // the empty string when searching for the property. It cannot be part of the
+  // SYMBOL_LIST because it needs to be allocated manually with the special
+  // hash code in place. The hash code for the hidden_symbol is zero to ensure
+  // that it will always be at the first entry in property descriptors.
+  obj = AllocateSymbol(CStrVector(""), 0, String::kHashComputedMask);
+  if (obj->IsFailure()) return false;
+  hidden_symbol_ = String::cast(obj);
 
   // Allocate the proxy for __proto__.
   obj = AllocateProxy((Address) &Accessors::ObjectPrototype);
@@ -2630,6 +2642,7 @@ void Heap::IterateStrongRoots(ObjectVisitor* v) {
   v->VisitPointer(bit_cast<Object**, String**>(&name##_));
   SYMBOL_LIST(SYMBOL_ITERATE)
 #undef SYMBOL_ITERATE
+  v->VisitPointer(bit_cast<Object**, String**>(&hidden_symbol_));
   SYNCHRONIZE_TAG("symbol");
 
   Bootstrapper::Iterate(v);
index 6979f43..f8b104a 100644 (file)
@@ -195,7 +195,8 @@ namespace v8 { namespace internal {
   V(space_symbol, " ")                                                   \
   V(exec_symbol, "exec")                                                 \
   V(zero_symbol, "0")                                                    \
-  V(global_eval_symbol, "GlobalEval")
+  V(global_eval_symbol, "GlobalEval")                                    \
+  V(identity_hash_symbol, "v8::IdentityHash")
 
 
 // Forward declaration of the GCTracer class.
@@ -643,6 +644,10 @@ class Heap : public AllStatic {
 #define SYMBOL_ACCESSOR(name, str) static String* name() { return name##_; }
   SYMBOL_LIST(SYMBOL_ACCESSOR)
 #undef SYMBOL_ACCESSOR
+  
+  // The hidden_symbol is special because it is the empty string, but does
+  // not match the empty string.
+  static String* hidden_symbol() { return hidden_symbol_; }
 
   // Iterates over all roots in the heap.
   static void IterateRoots(ObjectVisitor* v);
@@ -886,6 +891,10 @@ class Heap : public AllStatic {
 #define SYMBOL_DECLARATION(name, str) static String* name##_;
   SYMBOL_LIST(SYMBOL_DECLARATION)
 #undef SYMBOL_DECLARATION
+  
+  // The special hidden symbol which is an empty string, but does not match
+  // any string when looked up in properties.
+  static String* hidden_symbol_;
 
   // GC callback function, called before and after mark-compact GC.
   // Allocations in the callback function are disallowed.
index bfa92ee..1e1f359 100644 (file)
@@ -2444,10 +2444,15 @@ void StringHasher::AddCharacterNoIndex(uc32 c) {
 
 
 uint32_t StringHasher::GetHash() {
+  // Get the calculated raw hash value and do some more bit ops to distribute
+  // the hash further. Ensure that we never return zero as the hash value.
   uint32_t result = raw_running_hash_;
   result += (result << 3);
   result ^= (result >> 11);
   result += (result << 15);
+  if (result == 0) {
+    result = 27;
+  }
   return result;
 }
 
index 565a50c..3847e6c 100644 (file)
@@ -3192,8 +3192,12 @@ int DescriptorArray::BinarySearch(String* name, int low, int high) {
 
 
 int DescriptorArray::LinearSearch(String* name, int len) {
+  uint32_t hash = name->Hash();
   for (int number = 0; number < len; number++) {
-    if (name->Equals(GetKey(number)) && !is_null_descriptor(number)) {
+    String* entry = GetKey(number);
+    if ((entry->Hash() == hash) &&
+        name->Equals(entry) &&
+        !is_null_descriptor(number)) {
       return number;
     }
   }
@@ -4237,7 +4241,7 @@ bool String::IsEqualTo(Vector<const char> str) {
 
 
 uint32_t String::ComputeAndSetHash() {
-  // Should only be call if hash code has not yet been computed.
+  // Should only be called if hash code has not yet been computed.
   ASSERT(!(length_field() & kHashComputedMask));
 
   // Compute the hash code.
@@ -4249,7 +4253,9 @@ uint32_t String::ComputeAndSetHash() {
 
   // Check the hash code is there.
   ASSERT(length_field() & kHashComputedMask);
-  return field >> kHashShift;
+  uint32_t result = field >> kHashShift;
+  ASSERT(result != 0);  // Ensure that the hash value of 0 is never computed.
+  return result;
 }
 
 
@@ -5127,6 +5133,45 @@ bool JSObject::HasLocalElement(uint32_t index) {
 }
 
 
+Object* JSObject::GetHiddenProperties(bool create_if_needed) {
+  String* key = Heap::hidden_symbol();
+  if (this->HasFastProperties()) {
+    // If the object has fast properties, check whether the first slot in the
+    // descriptor array matches the hidden symbol. Since the hidden symbols
+    // hash code is zero it will always occupy the first entry if present.
+    DescriptorArray* descriptors = this->map()->instance_descriptors();
+    if (descriptors->number_of_descriptors() > 0) {
+      if (descriptors->GetKey(0) == key) {
+#ifdef DEBUG
+        PropertyDetails details(descriptors->GetDetails(0));
+        ASSERT(details.type() == FIELD);
+#endif // DEBUG
+        Object* value = descriptors->GetValue(0);
+        return FastPropertyAt(Descriptor::IndexFromValue(value));
+      }
+    }
+  }
+
+  // Only attempt to find the hidden properties in the local object and not
+  // in the prototype chain.
+  if (!this->HasLocalProperty(key)) {
+    // Hidden properties object not found. Allocate a new hidden properties
+    // object if requested. Otherwise return the undefined value.
+    if (create_if_needed) {
+      Object* obj = Heap::AllocateJSObject(
+          Top::context()->global_context()->object_function());
+      if (obj->IsFailure()) {
+        return obj;
+      }
+      return this->SetProperty(key, obj, DONT_ENUM);
+    } else {
+      return Heap::undefined_value();
+    }
+  }
+  return this->GetProperty(key);
+}
+
+
 bool JSObject::HasElementWithReceiver(JSObject* receiver, uint32_t index) {
   // Check access rights if needed.
   if (IsAccessCheckNeeded() &&
@@ -6021,13 +6066,20 @@ class NumberKey : public HashTableKey {
 // StringKey simply carries a string object as key.
 class StringKey : public HashTableKey {
  public:
-  explicit StringKey(String* string) : string_(string) { }
+  explicit StringKey(String* string) :
+      string_(string),
+      hash_(StringHash(string)) { }
 
   bool IsMatch(Object* string) {
+    // We know that all entries in a hash table had their hash keys created.
+    // Use that knowledge to have fast failure.
+    if (hash_ != StringHash(string)) {
+      return false;
+    }
     return string_->Equals(String::cast(string));
   }
 
-  uint32_t Hash() { return StringHash(string_); }
+  uint32_t Hash() { return hash_; }
 
   HashFunction GetHashFunction() { return StringHash; }
 
@@ -6040,6 +6092,7 @@ class StringKey : public HashTableKey {
   bool IsStringKey() { return true; }
 
   String* string_;
+  uint32_t hash_;
 };
 
 
@@ -6166,7 +6219,9 @@ class Utf8SymbolKey : public HashTableKey {
                                       static_cast<unsigned>(string_.length()));
     chars_ = buffer.Length();
     length_field_ = String::ComputeLengthAndHashField(&buffer, chars_);
-    return length_field_ >> String::kHashShift;
+    uint32_t result = length_field_ >> String::kHashShift;
+    ASSERT(result != 0);  // Ensure that the hash value of 0 is never computed.
+    return result;
   }
 
   Object* GetObject() {
index 165d350..6f78f65 100644 (file)
@@ -1247,6 +1247,11 @@ class JSObject: public HeapObject {
   // Return the object's prototype (might be Heap::null_value()).
   inline Object* GetPrototype();
 
+  // Return the object's hidden properties object. If the object has no hidden
+  // properties and create_if_needed is true, then a new hidden property object
+  // will be allocated. Otherwise the Heap::undefined_value is returned.
+  Object* GetHiddenProperties(bool create_if_needed);
+
   // Tells whether the index'th element is present.
   inline bool HasElement(uint32_t index);
   bool HasElementWithReceiver(JSObject* receiver, uint32_t index);
index 428b636..882db05 100644 (file)
@@ -1255,6 +1255,78 @@ THREADED_TEST(InternalFields) {
 }
 
 
+THREADED_TEST(IdentityHash) {
+  v8::HandleScope scope;
+  LocalContext env;
+
+  // Ensure that the test starts with an fresh heap to test whether the hash
+  // code is based on the address.
+  i::Heap::CollectAllGarbage();
+  Local<v8::Object> obj = v8::Object::New();
+  int hash = obj->GetIdentityHash();
+  int hash1 = obj->GetIdentityHash();
+  CHECK_EQ(hash, hash1);
+  int hash2 = v8::Object::New()->GetIdentityHash();
+  // Since the identity hash is essentially a random number two consecutive
+  // objects should not be assigned the same hash code. If the test below fails
+  // the random number generator should be evaluated.
+  CHECK_NE(hash, hash2);
+  i::Heap::CollectAllGarbage();
+  int hash3 = v8::Object::New()->GetIdentityHash();
+  // Make sure that the identity hash is not based on the initial address of
+  // the object alone. If the test below fails the random number generator
+  // should be evaluated.
+  CHECK_NE(hash, hash3);
+  int hash4 = obj->GetIdentityHash();
+  CHECK_EQ(hash, hash4);
+}
+
+
+THREADED_TEST(HiddenProperties) {
+  v8::HandleScope scope;
+  LocalContext env;
+
+  v8::Local<v8::Object> obj = v8::Object::New();
+  v8::Local<v8::String> key = v8_str("api-test::hidden-key");
+  v8::Local<v8::String> empty = v8_str("");
+  v8::Local<v8::String> prop_name = v8_str("prop_name");
+  
+  i::Heap::CollectAllGarbage();
+
+  CHECK(obj->SetHiddenValue(key, v8::Integer::New(1503)));
+  CHECK_EQ(1503, obj->GetHiddenValue(key)->Int32Value());
+  CHECK(obj->SetHiddenValue(key, v8::Integer::New(2002)));
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  
+  i::Heap::CollectAllGarbage();
+
+  // Make sure we do not find the hidden property.
+  CHECK(!obj->Has(empty));
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  CHECK(obj->Get(empty)->IsUndefined());
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  CHECK(obj->Set(empty, v8::Integer::New(2003)));
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  CHECK_EQ(2003, obj->Get(empty)->Int32Value());
+  
+  i::Heap::CollectAllGarbage();
+
+  // Add another property and delete it afterwards to force the object in
+  // slow case.
+  CHECK(obj->Set(prop_name, v8::Integer::New(2008)));
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  CHECK_EQ(2008, obj->Get(prop_name)->Int32Value());
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  CHECK(obj->Delete(prop_name));
+  CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value());
+  
+  i::Heap::CollectAllGarbage();
+
+  CHECK(obj->DeleteHiddenValue(key));
+  CHECK(obj->GetHiddenValue(key).IsEmpty());
+}
+
+
 THREADED_TEST(External) {
   v8::HandleScope scope;
   int x = 3;