Commit ea653021 authored by iposva@chromium.org's avatar iposva@chromium.org

- 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
parent 9f4eced3
......@@ -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
......
......@@ -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);
}
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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.
......
......@@ -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;
}
......
......@@ -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() {
......
......@@ -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);
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment