From ea653021fc78133cb8a3adcf85b5f45b1d94b70a Mon Sep 17 00:00:00 2001 From: "iposva@chromium.org" Date: Thu, 19 Mar 2009 18:50:00 +0000 Subject: [PATCH] - Allow hidden properties to be set on any JSObject through the V8 C++ API. - 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 | 16 +++++++++ src/api.cc | 71 ++++++++++++++++++++++++++++++++++++++++ src/factory.h | 4 +++ src/handles.cc | 6 ++++ src/handles.h | 2 ++ src/heap.cc | 13 ++++++++ src/heap.h | 11 ++++++- src/objects-inl.h | 5 +++ src/objects.cc | 67 ++++++++++++++++++++++++++++++++++---- src/objects.h | 5 +++ test/cctest/test-api.cc | 72 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 265 insertions(+), 7 deletions(-) diff --git a/include/v8.h b/include/v8.h index 666408b3b..ce1e9b6b1 100644 --- a/include/v8.h +++ b/include/v8.h @@ -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 key, Handle value); + Local GetHiddenValue(Handle key); + bool DeleteHiddenValue(Handle key); /** * Clone this object with a fast but shallow copy. Values will point diff --git a/src/api.cc b/src/api.cc index fa72f0af8..7e0e460ea 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1930,6 +1930,77 @@ Local v8::Object::Clone() { } +int v8::Object::GetIdentityHash() { + ON_BAILOUT("v8::Object::GetIdentityHash()", return 0); + i::Handle self = Utils::OpenHandle(this); + i::Handle hidden_props(i::GetHiddenProperties(self, true)); + i::Handle hash_symbol = i::Factory::identity_hash_symbol(); + i::Handle 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::Smi::FromInt(hash_value)), + static_cast(None)); + } + return hash_value; +} + + +bool v8::Object::SetHiddenValue(v8::Handle key, + v8::Handle value) { + ON_BAILOUT("v8::Object::SetHiddenValue()", return false); + i::Handle self = Utils::OpenHandle(this); + i::Handle hidden_props(i::GetHiddenProperties(self, true)); + i::Handle key_obj = Utils::OpenHandle(*key); + i::Handle value_obj = Utils::OpenHandle(*value); + EXCEPTION_PREAMBLE(); + i::Handle obj = i::SetProperty( + hidden_props, + key_obj, + value_obj, + static_cast(None)); + has_pending_exception = obj.is_null(); + EXCEPTION_BAILOUT_CHECK(false); + return true; +} + + +v8::Local v8::Object::GetHiddenValue(v8::Handle key) { + ON_BAILOUT("v8::Object::GetHiddenValue()", return Local()); + i::Handle self = Utils::OpenHandle(this); + i::Handle hidden_props(i::GetHiddenProperties(self, false)); + if (hidden_props->IsUndefined()) { + return v8::Local(); + } + i::Handle key_obj = Utils::OpenHandle(*key); + EXCEPTION_PREAMBLE(); + i::Handle result = i::GetProperty(hidden_props, key_obj); + has_pending_exception = result.is_null(); + EXCEPTION_BAILOUT_CHECK(v8::Local()); + if (result->IsUndefined()) { + return v8::Local(); + } + return Utils::ToLocal(result); +} + + +bool v8::Object::DeleteHiddenValue(v8::Handle key) { + ON_BAILOUT("v8::DeleteHiddenValue()", return false); + i::Handle self = Utils::OpenHandle(this); + i::Handle hidden_props( + i::JSObject::cast(*i::GetHiddenProperties(self, false))); + if (hidden_props->IsUndefined()) { + return false; + } + i::Handle key_obj = Utils::OpenHandle(*key); + return i::DeleteProperty(hidden_props, key_obj)->IsTrue(); +} + + Local Function::NewInstance() const { return NewInstance(0, NULL); } diff --git a/src/factory.h b/src/factory.h index 38d1a5bc5..f014ba5c3 100644 --- a/src/factory.h +++ b/src/factory.h @@ -299,6 +299,10 @@ class Factory : public AllStatic { static Handle name() { return Handle(&Heap::name##_); } SYMBOL_LIST(SYMBOL_ACCESSOR) #undef SYMBOL_ACCESSOR + + static Handle hidden_symbol() { + return Handle(&Heap::hidden_symbol_); + } static Handle NewSharedFunctionInfo(Handle name); diff --git a/src/handles.cc b/src/handles.cc index 3772dcf3f..23827065b 100644 --- a/src/handles.cc +++ b/src/handles.cc @@ -261,6 +261,12 @@ Handle GetPrototype(Handle obj) { } +Handle GetHiddenProperties(Handle obj, + bool create_if_needed) { + CALL_HEAP_FUNCTION(obj->GetHiddenProperties(create_if_needed), Object); +} + + Handle DeleteElement(Handle obj, uint32_t index) { CALL_HEAP_FUNCTION(obj->DeleteElement(index), Object); diff --git a/src/handles.h b/src/handles.h index 302dc126c..c8e534eec 100644 --- a/src/handles.h +++ b/src/handles.h @@ -223,6 +223,8 @@ Handle GetPropertyWithInterceptor(Handle receiver, Handle GetPrototype(Handle obj); +Handle GetHiddenProperties(Handle obj, bool create_if_needed); + Handle DeleteElement(Handle obj, uint32_t index); Handle DeleteProperty(Handle obj, Handle prop); diff --git a/src/heap.cc b/src/heap.cc index e8950ee6a..ce60aceb6 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -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(&name##_)); SYMBOL_LIST(SYMBOL_ITERATE) #undef SYMBOL_ITERATE + v->VisitPointer(bit_cast(&hidden_symbol_)); SYNCHRONIZE_TAG("symbol"); Bootstrapper::Iterate(v); diff --git a/src/heap.h b/src/heap.h index 6979f43f2..f8b104a5f 100644 --- a/src/heap.h +++ b/src/heap.h @@ -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. diff --git a/src/objects-inl.h b/src/objects-inl.h index bfa92ee6a..1e1f359b5 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -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; } diff --git a/src/objects.cc b/src/objects.cc index 565a50cc2..3847e6cec 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -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 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(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() { diff --git a/src/objects.h b/src/objects.h index 165d350d3..6f78f6532 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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); diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 428b6365b..882db05e6 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -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 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 obj = v8::Object::New(); + v8::Local key = v8_str("api-test::hidden-key"); + v8::Local empty = v8_str(""); + v8::Local 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; -- 2.34.1