* 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
}
+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);
}
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);
}
+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);
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);
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;
(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);
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);
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.
#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);
#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.
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;
}
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;
}
}
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.
// 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;
}
}
+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() &&
// 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; }
bool IsStringKey() { return true; }
String* string_;
+ uint32_t hash_;
};
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() {
// 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);
}
+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;